1 // dear imgui, v1.61 WIP
2 // (main code and documentation)
3
4 // Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code.
5 // Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase.
6 // Get latest version at https://github.com/ocornut/imgui
7 // Releases change-log at https://github.com/ocornut/imgui/releases
8 // Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269
9 // Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
10 // This library is free but I need your support to sustain development and maintenance.
11 // If you work for a company, please consider financial support, see README. For individuals: https://www.patreon.com/imgui
12
13 // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
14 // Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
15 // modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
16 // come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
17 // to a better solution or official support for them.
18
19 /*
20
21 Index
22 - MISSION STATEMENT
23 - END-USER GUIDE
24 - PROGRAMMER GUIDE (read me!)
25 - Read first
26 - How to update to a newer version of Dear ImGui
27 - Getting started with integrating Dear ImGui in your code/engine
28 - Using gamepad/keyboard navigation controls [BETA]
29 - API BREAKING CHANGES (read me when you update!)
30 - ISSUES & TODO LIST
31 - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
32 - How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
33 - How can I display an image? What is ImTextureID, how does it works?
34 - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack.
35 - How can I load a different font than the default?
36 - How can I easily use icons in my application?
37 - How can I load multiple fonts?
38 - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
39 - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
40 - I integrated Dear ImGui in my engine and the text or lines are blurry..
41 - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
42 - How can I help?
43 - ISSUES & TODO-LIST
44 - CODE
45
46
47 MISSION STATEMENT
48 =================
49
50 - Easy to use to create code-driven and data-driven tools
51 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools
52 - Easy to hack and improve
53 - Minimize screen real-estate usage
54 - Minimize setup and maintenance
55 - Minimize state storage on user side
56 - Portable, minimize dependencies, run on target (consoles, phones, etc.)
57 - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window,
58 opening a tree node for the first time, etc. but a typical frame should not allocate anything)
59
60 Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes:
61 - Doesn't look fancy, doesn't animate
62 - Limited layout features, intricate layouts are typically crafted in code
63
64
65 END-USER GUIDE
66 ==============
67
68 - Double-click on title bar to collapse window.
69 - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
70 - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
71 - Click and drag on any empty space to move window.
72 - TAB/SHIFT+TAB to cycle through keyboard editable fields.
73 - CTRL+Click on a slider or drag box to input value as text.
74 - Use mouse wheel to scroll.
75 - Text editor:
76 - Hold SHIFT or use mouse to select text.
77 - CTRL+Left/Right to word jump.
78 - CTRL+Shift+Left/Right to select words.
79 - CTRL+A our Double-Click to select all.
80 - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
81 - CTRL+Z,CTRL+Y to undo/redo.
82 - ESCAPE to revert text to its original value.
83 - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
84 - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
85 - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
86 - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://goo.gl/9LgVZW
87
88
89 PROGRAMMER GUIDE
90 ================
91
92 READ FIRST
93
94 - Read the FAQ below this section!
95 - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction
96 or destruction steps, less data retention on your side, less state duplication, less state synchronization, less bugs.
97 - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
98 - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861
99
100 HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
101
102 - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h)
103 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
104 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed
105 from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will
106 likely be a comment about it. Please report any issue to the GitHub page!
107 - Try to keep your copy of dear imgui reasonably up to date.
108
109 GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
110
111 - Run and study the examples and demo to get acquainted with the library.
112 - Add the Dear ImGui source files to your projects, using your preferred build system.
113 It is recommended you build the .cpp files as part of your project and not as a library.
114 - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types.
115 - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/ folder.
116 - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.
117
118 - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize
119 (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic
120 integration you don't need to worry about it all.
121 - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory.
122 - Every frame:
123 - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.)
124 - Call ImGui::NewFrame() to begin the frame
125 - You can use any ImGui function you want between NewFrame() and Render()
126 - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler.
127 (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break)
128 - All rendering information are stored into command-lists until ImGui::Render() is called.
129 - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide.
130 - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases
131 of your own application.
132 - Refer to the examples applications in the examples/ folder for instruction on how to setup your code.
133 - A minimal application skeleton may be:
134
135 // Application init
136 ImGui::CreateContext();
137 ImGuiIO& io = ImGui::GetIO();
138 io.DisplaySize.x = 1920.0f;
139 io.DisplaySize.y = 1280.0f;
140 // TODO: Fill others settings of the io structure later.
141
142 // Load texture atlas (there is a default font so you don't need to care about choosing a font yet)
143 unsigned char* pixels;
144 int width, height;
145 io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height);
146 // TODO: At this points you've got the texture data and you need to upload that your your graphic system:
147 MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA)
148 // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer.
149 io.Fonts->TexID = (void*)texture;
150
151 // Application main loop
152 while (true)
153 {
154 // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.)
155 ImGuiIO& io = ImGui::GetIO();
156 io.DeltaTime = 1.0f/60.0f;
157 io.MousePos = mouse_pos;
158 io.MouseDown[0] = mouse_button_0;
159 io.MouseDown[1] = mouse_button_1;
160
161 // Call NewFrame(), after this point you can use ImGui::* functions anytime
162 ImGui::NewFrame();
163
164 // Most of your application code here
165 MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
166 MyGameRender(); // may use any ImGui functions as well!
167
168 // Render & swap video buffers
169 ImGui::Render();
170 MyImGuiRenderFunction(ImGui::GetDrawData());
171 SwapBuffers();
172 }
173
174 // Shutdown
175 ImGui::DestroyContext();
176
177
178 - A minimal render function skeleton may be:
179
180 void void MyRenderFunction(ImDrawData* draw_data)
181 {
182 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
183 // TODO: Setup viewport, orthographic projection matrix
184 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
185 for (int n = 0; n < draw_data->CmdListsCount; n++)
186 {
187 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui
188 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui
189 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
190 {
191 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
192 if (pcmd->UserCallback)
193 {
194 pcmd->UserCallback(cmd_list, pcmd);
195 }
196 else
197 {
198 // The texture for the draw call is specified by pcmd->TextureId.
199 // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization.
200 MyEngineBindTexture(pcmd->TextureId);
201
202 // We are using scissoring to clip some objects. All low-level graphics API supports it.
203 // If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
204 // (some elements visible outside their bounds) but you can fix that once everywhere else works!
205 MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y));
206
207 // Render 'pcmd->ElemCount/3' indexed triangles.
208 // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices.
209 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
210 }
211 idx_buffer += pcmd->ElemCount;
212 }
213 }
214 }
215
216 - The examples/ folders contains many functional implementation of the pseudo-code above.
217 - When calling NewFrame(), the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags are updated.
218 They tell you if ImGui intends to use your inputs. When a flag is set you want to hide the corresponding inputs from the rest of your application.
219 However, in both cases you need to pass on the inputs to imgui. Read the FAQ below for more information about those flags.
220 - Please read the FAQ above. Amusingly, it is called a FAQ because people frequently have the same issues!
221
222 USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS [BETA]
223
224 - The gamepad/keyboard navigation is in Beta. Ask questions and report issues at https://github.com/ocornut/imgui/issues/787
225 - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
226 - Gamepad:
227 - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
228 - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame().
229 Note that io.NavInputs[] is cleared by EndFrame().
230 - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values:
231 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
232 - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
233 Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.).
234 - You can download PNG/PSD files depicting the gamepad controls for common controllers at: goo.gl/9LgVZW.
235 - 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
236 to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
237 - Keyboard:
238 - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
239 NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays.
240 - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag
241 will be set. For more advanced uses, you may want to read from:
242 - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
243 - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
244 - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions.
245 Please reach out if you think the game vs navigation input sharing could be improved.
246 - Mouse:
247 - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
248 - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your console/tablet/phone app) to share your PC mouse/keyboard.
249 - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag.
250 Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements.
251 When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved.
252 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.
253 (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse as moving back and forth!)
254 (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
255 to set a boolean to ignore your other external mouse positions until the external source is moved again.)
256
257
258 API BREAKING CHANGES
259 ====================
260
261 Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix.
262 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.
263 Also read releases logs https://github.com/ocornut/imgui/releases for more details.
264
265 - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows passing r-value.
266 - 2018/03/20 (1.60) - Renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only honored by some binding ahead of merging the Nav branch).
267 - 2018/03/12 (1.60) - Removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now.
268 - 2018/03/08 (1.60) - Changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically.
269 - 2018/03/03 (1.60) - Renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums.
270 - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment.
271 - 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.
272 - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
273 - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.
274 - removed Shutdown() function, as DestroyContext() serve this purpose.
275 - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwhise CreateContext() will create its own font atlas instance.
276 - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.
277 - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.
278 - 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.
279 - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).
280 - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).
281 - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.
282 - 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.
283 - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).
284 - 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
285 - 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.
286 - 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.
287 - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).
288 - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).
289 - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
290 - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).
291 - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).
292 - 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.
293 - 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.
294 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.
295 - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
296 - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
297 - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
298 - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
299 - 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.
300 - 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.
301 - 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.
302 removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.
303 - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!
304 - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).
305 - 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).
306 - 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)".
307 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
308 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
309 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
310 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
311 - 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.
312 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame.
313 - 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.
314 - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete).
315 - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete).
316 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
317 - 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.
318 - 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.
319 - 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))'
320 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
321 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
322 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
323 - 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().
324 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
325 - 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.
326 - 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.
327 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
328 If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you.
329 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.
330 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.
331 ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col)
332 {
333 float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a;
334 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);
335 }
336 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.
337 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
338 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
339 - 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).
340 - 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.
341 - 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).
342 - 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)
343 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
344 - 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.
345 - 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.
346 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
347 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
348 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
349 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.
350 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!
351 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
352 - 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.
353 - 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
354 - 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.
355 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
356 - 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.
357 this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
358 - 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.
359 - the signature of the io.RenderDrawListsFn handler has changed!
360 old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
361 new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
362 argument: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount'
363 ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new.
364 ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'.
365 - 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.
366 - 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!
367 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
368 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
369 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
370 - 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.
371 - 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
372 - 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!
373 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
374 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
375 - 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.
376 - 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.
377 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
378 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
379 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
380 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
381 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
382 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
383 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
384 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
385 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
386 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
387 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
388 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
389 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
390 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
391 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
392 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
393 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
394 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
395 font init: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); <..Upload texture to GPU..>
396 became: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); <..Upload texture to GPU>; io.Fonts->TexId = YourTextureIdentifier;
397 you now more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs.
398 it is now recommended that you sample the font texture with bilinear interpolation.
399 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID.
400 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
401 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
402 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
403 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
404 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
405 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
406 - 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)
407 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
408 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
409 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
410 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
411 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
412 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
413
414
415 ISSUES & TODO-LIST
416 ==================
417 See TODO.txt
418
419
420 FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
421 ======================================
422
423 Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
424 A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure.
425 - When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application.
426 - When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application.
427 - 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).
428 Note: you should always pass your mouse/keyboard inputs to imgui, even when the io.WantCaptureXXX flag are set false.
429 This is because imgui needs to detect that you clicked in the void to unfocus its windows.
430 Note: The 'io.WantCaptureMouse' is more accurate that any attempt to "check if the mouse is hovering a window" (don't do that!).
431 It handle mouse dragging correctly (both dragging that started over your application or over an imgui window) and handle e.g. modal windows blocking inputs.
432 Those flags are updated by ImGui::NewFrame(). Preferably read the flags after calling NewFrame() if you can afford it, but reading them before is also
433 perfectly fine, as the bool toggle fairly rarely. If you have on a touch device, you might find use for an early call to NewFrameUpdateHoveredWindowAndCaptureFlags().
434 Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event that your application receive will typically
435 have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs
436 were targetted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)
437
438 Q: How can I display an image? What is ImTextureID, how does it works?
439 A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function.
440 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!
441 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.
442 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.
443 Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing.
444 (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!)
445 To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions.
446 Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use.
447 You may call ImGui::ShowMetricsWindow() to explore active draw lists and visualize/understand how the draw data is generated.
448 It is your responsibility to get textures uploaded to your GPU.
449
450 Q: How can I have multiple widgets with the same label or without a label?
451 A: A primer on labels and the ID Stack...
452
453 - Elements that are typically not clickable, such as Text() items don't need an ID.
454
455 - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui
456 often needs to remember what is the "active" widget). To do so they need a unique ID. Unique ID
457 are typically derived from a string label, an integer index or a pointer.
458
459 Button("OK"); // Label = "OK", ID = top of id stack + hash of "OK"
460 Button("Cancel"); // Label = "Cancel", ID = top of id stack + hash of "Cancel"
461
462 - ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having
463 two buttons labeled "OK" in different windows or different tree locations is fine.
464
465 - If you have a same ID twice in the same location, you'll have a conflict:
466
467 Button("OK");
468 Button("OK"); // ID collision! Interacting with either button will trigger the first one.
469
470 Fear not! this is easy to solve and there are many ways to solve it!
471
472 - Solving ID conflict in a simple/local context:
473 When passing a label you can optionally specify extra ID information within string itself.
474 Use "##" to pass a complement to the ID that won't be visible to the end-user.
475 This helps solving the simple collision cases when you know e.g. at compilation time which items
476 are going to be created:
477
478 Button("Play"); // Label = "Play", ID = top of id stack + hash of "Play"
479 Button("Play##foo1"); // Label = "Play", ID = top of id stack + hash of "Play##foo1" (different from above)
480 Button("Play##foo2"); // Label = "Play", ID = top of id stack + hash of "Play##foo2" (different from above)
481
482 - If you want to completely hide the label, but still need an ID:
483
484 Checkbox("##On", &b); // Label = "", ID = top of id stack + hash of "##On" (no label!)
485
486 - Occasionally/rarely you might want change a label while preserving a constant ID. This allows
487 you to animate labels. For example you may want to include varying information in a window title bar,
488 but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID:
489
490 Button("Hello###ID"; // Label = "Hello", ID = top of id stack + hash of "ID"
491 Button("World###ID"; // Label = "World", ID = top of id stack + hash of "ID" (same as above)
492
493 sprintf(buf, "My game (%f FPS)###MyGame", fps);
494 Begin(buf); // Variable label, ID = hash of "MyGame"
495
496 - Solving ID conflict in a more general manner:
497 Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts
498 within the same window. This is the most convenient way of distinguishing ID when iterating and
499 creating many UI elements programmatically.
500 You can push a pointer, a string or an integer value into the ID stack.
501 Remember that ID are formed from the concatenation of _everything_ in the ID stack!
502
503 for (int i = 0; i < 100; i++)
504 {
505 PushID(i);
506 Button("Click"); // Label = "Click", ID = top of id stack + hash of integer + hash of "Click"
507 PopID();
508 }
509
510 for (int i = 0; i < 100; i++)
511 {
512 MyObject* obj = Objects[i];
513 PushID(obj);
514 Button("Click"); // Label = "Click", ID = top of id stack + hash of pointer + hash of "Click"
515 PopID();
516 }
517
518 for (int i = 0; i < 100; i++)
519 {
520 MyObject* obj = Objects[i];
521 PushID(obj->Name);
522 Button("Click"); // Label = "Click", ID = top of id stack + hash of string + hash of "Click"
523 PopID();
524 }
525
526 - More example showing that you can stack multiple prefixes into the ID stack:
527
528 Button("Click"); // Label = "Click", ID = top of id stack + hash of "Click"
529 PushID("node");
530 Button("Click"); // Label = "Click", ID = top of id stack + hash of "node" + hash of "Click"
531 PushID(my_ptr);
532 Button("Click"); // Label = "Click", ID = top of id stack + hash of "node" + hash of ptr + hash of "Click"
533 PopID();
534 PopID();
535
536 - Tree nodes implicitly creates a scope for you by calling PushID().
537
538 Button("Click"); // Label = "Click", ID = top of id stack + hash of "Click"
539 if (TreeNode("node"))
540 {
541 Button("Click"); // Label = "Click", ID = top of id stack + hash of "node" + hash of "Click"
542 TreePop();
543 }
544
545 - When working with trees, ID are used to preserve the open/close state of each tree node.
546 Depending on your use cases you may want to use strings, indices or pointers as ID.
547 e.g. when following a single pointer that may change over time, using a static string as ID
548 will preserve your node open/closed state when the targeted object change.
549 e.g. when displaying a list of objects, using indices or pointers as ID will preserve the
550 node open/closed state differently. See what makes more sense in your situation!
551
552 Q: How can I load a different font than the default?
553 A: Use the font atlas to load the TTF/OTF file you want:
554 ImGuiIO& io = ImGui::GetIO();
555 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
556 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
557 (default is ProggyClean.ttf, rendered at size 13, embedded in dear imgui's source code)
558
559 New programmers: remember that in C/C++ and most programming languages if you want to use a
560 backslash \ within a string literal, you need to write it double backslash "\\":
561 io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG (you are escape the M here!)
562 io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT
563 io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT
564
565 Q: How can I easily use icons in my application?
566 A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you
567 main font. Then you can refer to icons within your strings. Read 'How can I load multiple fonts?'
568 and the file 'misc/fonts/README.txt' for instructions and useful header files.
569
570 Q: How can I load multiple fonts?
571 A: Use the font atlas to pack them into a single texture:
572 (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.)
573
574 ImGuiIO& io = ImGui::GetIO();
575 ImFont* font0 = io.Fonts->AddFontDefault();
576 ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
577 ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels);
578 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
579 // the first loaded font gets used by default
580 // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime
581
582 // Options
583 ImFontConfig config;
584 config.OversampleH = 3;
585 config.OversampleV = 1;
586 config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up
587 config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters
588 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
589
590 // Combine multiple fonts into one (e.g. for icon fonts)
591 ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
592 ImFontConfig config;
593 config.MergeMode = true;
594 io.Fonts->AddFontDefault();
595 io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font
596 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs
597
598 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
599 A: When loading a font, pass custom Unicode ranges to specify the glyphs to load.
600
601 // Add default Japanese ranges
602 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese());
603
604 // 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)
605 ImVector<ImWchar> ranges;
606 ImFontAtlas::GlyphRangesBuilder builder;
607 builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters)
608 builder.AddChar(0x7262); // Add a specific character
609 builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges
610 builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
611 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data);
612
613 All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8
614 by using the u8"hello" syntax. Specifying literal in your source code using a local code page
615 (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work!
616 Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8.
617
618 Text input: it is up to your application to pass the right character code by calling
619 io.AddInputCharacter(). The applications in examples/ are doing that. For languages relying
620 on an Input Method Editor (IME), on Windows you can copy the Hwnd of your application in the
621 io.ImeWindowHandle field. The default implementation of io.ImeSetInputScreenPosFn() will set
622 your Microsoft IME position correctly.
623
624 Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
625 A: - You can create a dummy window. Call SetNextWindowBgAlpha(0.0f), call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flags.
626 Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like.
627 - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows.
628 - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData.
629
630 Q: I integrated Dear ImGui in my engine and the text or lines are blurry..
631 A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f).
632 Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension.
633
634 Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
635 A: You are probably mishandling the clipping rectangles in your render function.
636 Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height).
637
638 Q: How can I help?
639 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!
640 - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README.
641 - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
642 You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers.
643 But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions.
644 - 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).
645
646 - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window.
647 this is also useful to set yourself in the context of another window (to get/set other settings)
648 - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug".
649 - 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
650 of a deep nested inner loop in your code.
651 - tip: you can call Render() multiple times (e.g for VR renders).
652 - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui!
653
654 */
655
656 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
657 #define _CRT_SECURE_NO_WARNINGS
658 #endif
659
660 #include "imgui.h"
661 #define IMGUI_DEFINE_MATH_OPERATORS
662 #include "imgui_internal.h"
663
664 #include <ctype.h> // toupper, isprint
665 #include <stdlib.h> // NULL, malloc, free, qsort, atoi
666 #include <stdio.h> // vsnprintf, sscanf, printf
667 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
668 #include <stddef.h> // intptr_t
669 #else
670 #include <stdint.h> // intptr_t
671 #endif
672
673 #define IMGUI_DEBUG_NAV_SCORING 0
674 #define IMGUI_DEBUG_NAV_RECTS 0
675
676 // Visual Studio warnings
677 #ifdef _MSC_VER
678 #pragma warning (disable: 4127) // condition expression is constant
679 #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
680 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
681 #endif
682
683 // Clang warnings with -Weverything
684 #if defined(__clang__) && !defined(__INTEL_COMPILER)
685 #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!
686 #pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.
687 #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.
688 #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.
689 #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.
690 #pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it.
691 #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
692 #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.
693 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int'
694 #elif defined(__GNUC__)
695 #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
696 #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
697 #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
698 #pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
699 #pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
700 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
701 #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false
702 #endif
703
704 // Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall
705 #ifdef _MSC_VER
706 #define IMGUI_CDECL __cdecl
707 #else
708 #define IMGUI_CDECL
709 #endif
710
711 //-------------------------------------------------------------------------
712 // Forward Declarations
713 //-------------------------------------------------------------------------
714
715 static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true);
716
717 static ImFont* GetDefaultFont();
718 static void SetCurrentWindow(ImGuiWindow* window);
719 static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x);
720 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y);
721 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond);
722 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond);
723 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond);
724 static ImGuiWindow* FindHoveredWindow();
725 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
726 static void CheckStacksSize(ImGuiWindow* window, bool write);
727 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
728
729 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
730 static void AddWindowToDrawData(ImVector<ImDrawList*>* out_list, ImGuiWindow* window);
731 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
732
733 static ImGuiWindowSettings* AddWindowSettings(const char* name);
734
735 static void LoadIniSettingsFromDisk(const char* ini_filename);
736 static void LoadIniSettingsFromMemory(const char* buf);
737 static void SaveIniSettingsToDisk(const char* ini_filename);
738 static void SaveIniSettingsToMemory(ImVector<char>& out_buf);
739 static void MarkIniSettingsDirty(ImGuiWindow* window);
740
741 static ImRect GetViewportRect();
742
743 static void ClosePopupToLevel(int remaining);
744
745 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data);
746 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
747 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);
748
749 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size);
750 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size);
751 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
752 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format);
753
754 namespace ImGui
755 {
756 static void NavUpdate();
757 static void NavUpdateWindowing();
758 static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id);
759
760 static void NewFrameUpdateMovingWindow();
761 static void NewFrameUpdateMouseInputs();
762 static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]);
763 static void FocusFrontMostActiveWindow(ImGuiWindow* ignore_window);
764 }
765
766 //-----------------------------------------------------------------------------
767 // Platform dependent default implementations
768 //-----------------------------------------------------------------------------
769
770 static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
771 static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
772 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
773
774 //-----------------------------------------------------------------------------
775 // Context
776 //-----------------------------------------------------------------------------
777
778 // Current context pointer. Implicitely used by all ImGui functions. Always assumed to be != NULL.
779 // CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext().
780 // If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file.
781 // 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:
782 // - 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
783 // - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts)
784 #ifndef GImGui
785 ImGuiContext* GImGui = NULL;
786 #endif
787
788 // Memory Allocator functions. Use SetAllocatorFunctions() to change them.
789 // If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file.
790 // 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.
791 #ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
MallocWrapper(size_t size,void * user_data)792 static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); }
FreeWrapper(void * ptr,void * user_data)793 static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); }
794 #else
MallocWrapper(size_t size,void * user_data)795 static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; }
FreeWrapper(void * ptr,void * user_data)796 static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); }
797 #endif
798
799 static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper;
800 static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper;
801 static void* GImAllocatorUserData = NULL;
802 static size_t GImAllocatorActiveAllocationsCount = 0;
803
804 //-----------------------------------------------------------------------------
805 // User facing structures
806 //-----------------------------------------------------------------------------
807
ImGuiStyle()808 ImGuiStyle::ImGuiStyle()
809 {
810 Alpha = 1.0f; // Global alpha applies to everything in ImGui
811 WindowPadding = ImVec2(8,8); // Padding within a window
812 WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows
813 WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
814 WindowMinSize = ImVec2(32,32); // Minimum window size
815 WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text
816 ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
817 ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
818 PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
819 PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.
820 FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets)
821 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
822 FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
823 ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines
824 ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
825 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!
826 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
827 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns
828 ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
829 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
830 GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar
831 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
832 ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
833 DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows.
834 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.
835 MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
836 AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU.
837 AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.)
838 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.
839
840 // Default theme
841 ImGui::StyleColorsDark(this);
842 }
843
844 // 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.
845 // 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)846 void ImGuiStyle::ScaleAllSizes(float scale_factor)
847 {
848 WindowPadding = ImFloor(WindowPadding * scale_factor);
849 WindowRounding = ImFloor(WindowRounding * scale_factor);
850 WindowMinSize = ImFloor(WindowMinSize * scale_factor);
851 ChildRounding = ImFloor(ChildRounding * scale_factor);
852 PopupRounding = ImFloor(PopupRounding * scale_factor);
853 FramePadding = ImFloor(FramePadding * scale_factor);
854 FrameRounding = ImFloor(FrameRounding * scale_factor);
855 ItemSpacing = ImFloor(ItemSpacing * scale_factor);
856 ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor);
857 TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor);
858 IndentSpacing = ImFloor(IndentSpacing * scale_factor);
859 ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor);
860 ScrollbarSize = ImFloor(ScrollbarSize * scale_factor);
861 ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor);
862 GrabMinSize = ImFloor(GrabMinSize * scale_factor);
863 GrabRounding = ImFloor(GrabRounding * scale_factor);
864 DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
865 DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
866 MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
867 }
868
ImGuiIO()869 ImGuiIO::ImGuiIO()
870 {
871 // Most fields are initialized with zero
872 memset(this, 0, sizeof(*this));
873
874 // Settings
875 ConfigFlags = 0x00;
876 BackendFlags = 0x00;
877 DisplaySize = ImVec2(-1.0f, -1.0f);
878 DeltaTime = 1.0f/60.0f;
879 IniSavingRate = 5.0f;
880 IniFilename = "imgui.ini";
881 LogFilename = "imgui_log.txt";
882 MouseDoubleClickTime = 0.30f;
883 MouseDoubleClickMaxDist = 6.0f;
884 for (int i = 0; i < ImGuiKey_COUNT; i++)
885 KeyMap[i] = -1;
886 KeyRepeatDelay = 0.250f;
887 KeyRepeatRate = 0.050f;
888 UserData = NULL;
889
890 Fonts = NULL;
891 FontGlobalScale = 1.0f;
892 FontDefault = NULL;
893 FontAllowUserScaling = false;
894 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
895 DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f);
896
897 // Advanced/subtle behaviors
898 #ifdef __APPLE__
899 OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
900 #else
901 OptMacOSXBehaviors = false;
902 #endif
903 OptCursorBlink = true;
904
905 // Settings (User Functions)
906 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
907 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
908 ClipboardUserData = NULL;
909 ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl;
910 ImeWindowHandle = NULL;
911
912 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
913 RenderDrawListsFn = NULL;
914 #endif
915
916 // Input (NB: we already have memset zero the entire structure)
917 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
918 MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
919 MouseDragThreshold = 6.0f;
920 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
921 for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
922 for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
923 }
924
925 // Pass in translated ASCII characters for text input.
926 // - with glfw you can get those from the callback set in glfwSetCharCallback()
927 // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
AddInputCharacter(ImWchar c)928 void ImGuiIO::AddInputCharacter(ImWchar c)
929 {
930 const int n = ImStrlenW(InputCharacters);
931 if (n + 1 < IM_ARRAYSIZE(InputCharacters))
932 {
933 InputCharacters[n] = c;
934 InputCharacters[n+1] = '\0';
935 }
936 }
937
AddInputCharactersUTF8(const char * utf8_chars)938 void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
939 {
940 // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more
941 const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar);
942 ImWchar wchars[wchars_buf_len];
943 ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL);
944 for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++)
945 AddInputCharacter(wchars[i]);
946 }
947
948 //-----------------------------------------------------------------------------
949 // HELPERS
950 //-----------------------------------------------------------------------------
951
952 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose
953 #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
954
ImLineClosestPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & p)955 ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
956 {
957 ImVec2 ap = p - a;
958 ImVec2 ab_dir = b - a;
959 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
960 if (dot < 0.0f)
961 return a;
962 float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
963 if (dot > ab_len_sqr)
964 return b;
965 return a + ab_dir * dot / ab_len_sqr;
966 }
967
ImTriangleContainsPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p)968 bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
969 {
970 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
971 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
972 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
973 return ((b1 == b2) && (b2 == b3));
974 }
975
ImTriangleBarycentricCoords(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p,float & out_u,float & out_v,float & out_w)976 void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)
977 {
978 ImVec2 v0 = b - a;
979 ImVec2 v1 = c - a;
980 ImVec2 v2 = p - a;
981 const float denom = v0.x * v1.y - v1.x * v0.y;
982 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
983 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
984 out_u = 1.0f - out_v - out_w;
985 }
986
ImTriangleClosestPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p)987 ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
988 {
989 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
990 ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
991 ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
992 float dist2_ab = ImLengthSqr(p - proj_ab);
993 float dist2_bc = ImLengthSqr(p - proj_bc);
994 float dist2_ca = ImLengthSqr(p - proj_ca);
995 float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
996 if (m == dist2_ab)
997 return proj_ab;
998 if (m == dist2_bc)
999 return proj_bc;
1000 return proj_ca;
1001 }
1002
ImStricmp(const char * str1,const char * str2)1003 int ImStricmp(const char* str1, const char* str2)
1004 {
1005 int d;
1006 while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; }
1007 return d;
1008 }
1009
ImStrnicmp(const char * str1,const char * str2,size_t count)1010 int ImStrnicmp(const char* str1, const char* str2, size_t count)
1011 {
1012 int d = 0;
1013 while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
1014 return d;
1015 }
1016
ImStrncpy(char * dst,const char * src,size_t count)1017 void ImStrncpy(char* dst, const char* src, size_t count)
1018 {
1019 if (count < 1) return;
1020 strncpy(dst, src, count-1);
1021 dst[count-1] = 0;
1022 }
1023
ImStrdup(const char * str)1024 char* ImStrdup(const char *str)
1025 {
1026 size_t len = strlen(str) + 1;
1027 void* buf = ImGui::MemAlloc(len);
1028 return (char*)memcpy(buf, (const void*)str, len);
1029 }
1030
ImStrchrRange(const char * str,const char * str_end,char c)1031 const char* ImStrchrRange(const char* str, const char* str_end, char c)
1032 {
1033 for ( ; str < str_end; str++)
1034 if (*str == c)
1035 return str;
1036 return NULL;
1037 }
1038
ImStrlenW(const ImWchar * str)1039 int ImStrlenW(const ImWchar* str)
1040 {
1041 int n = 0;
1042 while (*str++) n++;
1043 return n;
1044 }
1045
ImStrbolW(const ImWchar * buf_mid_line,const ImWchar * buf_begin)1046 const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
1047 {
1048 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
1049 buf_mid_line--;
1050 return buf_mid_line;
1051 }
1052
ImStristr(const char * haystack,const char * haystack_end,const char * needle,const char * needle_end)1053 const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
1054 {
1055 if (!needle_end)
1056 needle_end = needle + strlen(needle);
1057
1058 const char un0 = (char)toupper(*needle);
1059 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
1060 {
1061 if (toupper(*haystack) == un0)
1062 {
1063 const char* b = needle + 1;
1064 for (const char* a = haystack + 1; b < needle_end; a++, b++)
1065 if (toupper(*a) != toupper(*b))
1066 break;
1067 if (b == needle_end)
1068 return haystack;
1069 }
1070 haystack++;
1071 }
1072 return NULL;
1073 }
1074
ImAtoi(const char * src,int * output)1075 static const char* ImAtoi(const char* src, int* output)
1076 {
1077 int negative = 0;
1078 if (*src == '-') { negative = 1; src++; }
1079 if (*src == '+') { src++; }
1080 int v = 0;
1081 while (*src >= '0' && *src <= '9')
1082 v = (v * 10) + (*src++ - '0');
1083 *output = negative ? -v : v;
1084 return src;
1085 }
1086
1087 // A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
1088 // 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.
1089 // B) When buf==NULL vsnprintf() will return the output size.
1090 #ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
ImFormatString(char * buf,size_t buf_size,const char * fmt,...)1091 int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
1092 {
1093 va_list args;
1094 va_start(args, fmt);
1095 int w = vsnprintf(buf, buf_size, fmt, args);
1096 va_end(args);
1097 if (buf == NULL)
1098 return w;
1099 if (w == -1 || w >= (int)buf_size)
1100 w = (int)buf_size - 1;
1101 buf[w] = 0;
1102 return w;
1103 }
1104
ImFormatStringV(char * buf,size_t buf_size,const char * fmt,va_list args)1105 int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
1106 {
1107 int w = vsnprintf(buf, buf_size, fmt, args);
1108 if (buf == NULL)
1109 return w;
1110 if (w == -1 || w >= (int)buf_size)
1111 w = (int)buf_size - 1;
1112 buf[w] = 0;
1113 return w;
1114 }
1115 #endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
1116
1117 // Pass data_size==0 for zero-terminated strings
1118 // 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)1119 ImU32 ImHash(const void* data, int data_size, ImU32 seed)
1120 {
1121 static ImU32 crc32_lut[256] = { 0 };
1122 if (!crc32_lut[1])
1123 {
1124 const ImU32 polynomial = 0xEDB88320;
1125 for (ImU32 i = 0; i < 256; i++)
1126 {
1127 ImU32 crc = i;
1128 for (ImU32 j = 0; j < 8; j++)
1129 crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial);
1130 crc32_lut[i] = crc;
1131 }
1132 }
1133
1134 seed = ~seed;
1135 ImU32 crc = seed;
1136 const unsigned char* current = (const unsigned char*)data;
1137
1138 if (data_size > 0)
1139 {
1140 // Known size
1141 while (data_size--)
1142 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++];
1143 }
1144 else
1145 {
1146 // Zero-terminated string
1147 while (unsigned char c = *current++)
1148 {
1149 // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
1150 // Because this syntax is rarely used we are optimizing for the common case.
1151 // - If we reach ### in the string we discard the hash so far and reset to the seed.
1152 // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller.
1153 if (c == '#' && current[0] == '#' && current[1] == '#')
1154 crc = seed;
1155 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1156 }
1157 }
1158 return ~crc;
1159 }
1160
1161 //-----------------------------------------------------------------------------
1162 // ImText* helpers
1163 //-----------------------------------------------------------------------------
1164
1165 // Convert UTF-8 to 32-bits character, process single character input.
1166 // Based on stb_from_utf8() from github.com/nothings/stb/
1167 // We handle UTF-8 decoding error by skipping forward.
ImTextCharFromUtf8(unsigned int * out_char,const char * in_text,const char * in_text_end)1168 int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
1169 {
1170 unsigned int c = (unsigned int)-1;
1171 const unsigned char* str = (const unsigned char*)in_text;
1172 if (!(*str & 0x80))
1173 {
1174 c = (unsigned int)(*str++);
1175 *out_char = c;
1176 return 1;
1177 }
1178 if ((*str & 0xe0) == 0xc0)
1179 {
1180 *out_char = 0xFFFD; // will be invalid but not end of string
1181 if (in_text_end && in_text_end - (const char*)str < 2) return 1;
1182 if (*str < 0xc2) return 2;
1183 c = (unsigned int)((*str++ & 0x1f) << 6);
1184 if ((*str & 0xc0) != 0x80) return 2;
1185 c += (*str++ & 0x3f);
1186 *out_char = c;
1187 return 2;
1188 }
1189 if ((*str & 0xf0) == 0xe0)
1190 {
1191 *out_char = 0xFFFD; // will be invalid but not end of string
1192 if (in_text_end && in_text_end - (const char*)str < 3) return 1;
1193 if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3;
1194 if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below
1195 c = (unsigned int)((*str++ & 0x0f) << 12);
1196 if ((*str & 0xc0) != 0x80) return 3;
1197 c += (unsigned int)((*str++ & 0x3f) << 6);
1198 if ((*str & 0xc0) != 0x80) return 3;
1199 c += (*str++ & 0x3f);
1200 *out_char = c;
1201 return 3;
1202 }
1203 if ((*str & 0xf8) == 0xf0)
1204 {
1205 *out_char = 0xFFFD; // will be invalid but not end of string
1206 if (in_text_end && in_text_end - (const char*)str < 4) return 1;
1207 if (*str > 0xf4) return 4;
1208 if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4;
1209 if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below
1210 c = (unsigned int)((*str++ & 0x07) << 18);
1211 if ((*str & 0xc0) != 0x80) return 4;
1212 c += (unsigned int)((*str++ & 0x3f) << 12);
1213 if ((*str & 0xc0) != 0x80) return 4;
1214 c += (unsigned int)((*str++ & 0x3f) << 6);
1215 if ((*str & 0xc0) != 0x80) return 4;
1216 c += (*str++ & 0x3f);
1217 // utf-8 encodings of values used in surrogate pairs are invalid
1218 if ((c & 0xFFFFF800) == 0xD800) return 4;
1219 *out_char = c;
1220 return 4;
1221 }
1222 *out_char = 0;
1223 return 0;
1224 }
1225
ImTextStrFromUtf8(ImWchar * buf,int buf_size,const char * in_text,const char * in_text_end,const char ** in_text_remaining)1226 int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)
1227 {
1228 ImWchar* buf_out = buf;
1229 ImWchar* buf_end = buf + buf_size;
1230 while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
1231 {
1232 unsigned int c;
1233 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1234 if (c == 0)
1235 break;
1236 if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes
1237 *buf_out++ = (ImWchar)c;
1238 }
1239 *buf_out = 0;
1240 if (in_text_remaining)
1241 *in_text_remaining = in_text;
1242 return (int)(buf_out - buf);
1243 }
1244
ImTextCountCharsFromUtf8(const char * in_text,const char * in_text_end)1245 int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
1246 {
1247 int char_count = 0;
1248 while ((!in_text_end || in_text < in_text_end) && *in_text)
1249 {
1250 unsigned int c;
1251 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1252 if (c == 0)
1253 break;
1254 if (c < 0x10000)
1255 char_count++;
1256 }
1257 return char_count;
1258 }
1259
1260 // Based on stb_to_utf8() from github.com/nothings/stb/
ImTextCharToUtf8(char * buf,int buf_size,unsigned int c)1261 static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
1262 {
1263 if (c < 0x80)
1264 {
1265 buf[0] = (char)c;
1266 return 1;
1267 }
1268 if (c < 0x800)
1269 {
1270 if (buf_size < 2) return 0;
1271 buf[0] = (char)(0xc0 + (c >> 6));
1272 buf[1] = (char)(0x80 + (c & 0x3f));
1273 return 2;
1274 }
1275 if (c >= 0xdc00 && c < 0xe000)
1276 {
1277 return 0;
1278 }
1279 if (c >= 0xd800 && c < 0xdc00)
1280 {
1281 if (buf_size < 4) return 0;
1282 buf[0] = (char)(0xf0 + (c >> 18));
1283 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
1284 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
1285 buf[3] = (char)(0x80 + ((c ) & 0x3f));
1286 return 4;
1287 }
1288 //else if (c < 0x10000)
1289 {
1290 if (buf_size < 3) return 0;
1291 buf[0] = (char)(0xe0 + (c >> 12));
1292 buf[1] = (char)(0x80 + ((c>> 6) & 0x3f));
1293 buf[2] = (char)(0x80 + ((c ) & 0x3f));
1294 return 3;
1295 }
1296 }
1297
ImTextCountUtf8BytesFromChar(unsigned int c)1298 static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
1299 {
1300 if (c < 0x80) return 1;
1301 if (c < 0x800) return 2;
1302 if (c >= 0xdc00 && c < 0xe000) return 0;
1303 if (c >= 0xd800 && c < 0xdc00) return 4;
1304 return 3;
1305 }
1306
ImTextStrToUtf8(char * buf,int buf_size,const ImWchar * in_text,const ImWchar * in_text_end)1307 int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
1308 {
1309 char* buf_out = buf;
1310 const char* buf_end = buf + buf_size;
1311 while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
1312 {
1313 unsigned int c = (unsigned int)(*in_text++);
1314 if (c < 0x80)
1315 *buf_out++ = (char)c;
1316 else
1317 buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c);
1318 }
1319 *buf_out = 0;
1320 return (int)(buf_out - buf);
1321 }
1322
ImTextCountUtf8BytesFromStr(const ImWchar * in_text,const ImWchar * in_text_end)1323 int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
1324 {
1325 int bytes_count = 0;
1326 while ((!in_text_end || in_text < in_text_end) && *in_text)
1327 {
1328 unsigned int c = (unsigned int)(*in_text++);
1329 if (c < 0x80)
1330 bytes_count++;
1331 else
1332 bytes_count += ImTextCountUtf8BytesFromChar(c);
1333 }
1334 return bytes_count;
1335 }
1336
ColorConvertU32ToFloat4(ImU32 in)1337 ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
1338 {
1339 float s = 1.0f/255.0f;
1340 return ImVec4(
1341 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
1342 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
1343 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
1344 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
1345 }
1346
ColorConvertFloat4ToU32(const ImVec4 & in)1347 ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
1348 {
1349 ImU32 out;
1350 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
1351 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
1352 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
1353 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
1354 return out;
1355 }
1356
GetColorU32(ImGuiCol idx,float alpha_mul)1357 ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
1358 {
1359 ImGuiStyle& style = GImGui->Style;
1360 ImVec4 c = style.Colors[idx];
1361 c.w *= style.Alpha * alpha_mul;
1362 return ColorConvertFloat4ToU32(c);
1363 }
1364
GetColorU32(const ImVec4 & col)1365 ImU32 ImGui::GetColorU32(const ImVec4& col)
1366 {
1367 ImGuiStyle& style = GImGui->Style;
1368 ImVec4 c = col;
1369 c.w *= style.Alpha;
1370 return ColorConvertFloat4ToU32(c);
1371 }
1372
GetStyleColorVec4(ImGuiCol idx)1373 const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
1374 {
1375 ImGuiStyle& style = GImGui->Style;
1376 return style.Colors[idx];
1377 }
1378
GetColorU32(ImU32 col)1379 ImU32 ImGui::GetColorU32(ImU32 col)
1380 {
1381 float style_alpha = GImGui->Style.Alpha;
1382 if (style_alpha >= 1.0f)
1383 return col;
1384 ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
1385 a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
1386 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
1387 }
1388
1389 // Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
1390 // 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)1391 void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
1392 {
1393 float K = 0.f;
1394 if (g < b)
1395 {
1396 ImSwap(g, b);
1397 K = -1.f;
1398 }
1399 if (r < g)
1400 {
1401 ImSwap(r, g);
1402 K = -2.f / 6.f - K;
1403 }
1404
1405 const float chroma = r - (g < b ? g : b);
1406 out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f));
1407 out_s = chroma / (r + 1e-20f);
1408 out_v = r;
1409 }
1410
1411 // Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
1412 // 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)1413 void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
1414 {
1415 if (s == 0.0f)
1416 {
1417 // gray
1418 out_r = out_g = out_b = v;
1419 return;
1420 }
1421
1422 h = fmodf(h, 1.0f) / (60.0f/360.0f);
1423 int i = (int)h;
1424 float f = h - (float)i;
1425 float p = v * (1.0f - s);
1426 float q = v * (1.0f - s * f);
1427 float t = v * (1.0f - s * (1.0f - f));
1428
1429 switch (i)
1430 {
1431 case 0: out_r = v; out_g = t; out_b = p; break;
1432 case 1: out_r = q; out_g = v; out_b = p; break;
1433 case 2: out_r = p; out_g = v; out_b = t; break;
1434 case 3: out_r = p; out_g = q; out_b = v; break;
1435 case 4: out_r = t; out_g = p; out_b = v; break;
1436 case 5: default: out_r = v; out_g = p; out_b = q; break;
1437 }
1438 }
1439
ImFileOpen(const char * filename,const char * mode)1440 FILE* ImFileOpen(const char* filename, const char* mode)
1441 {
1442 #if defined(_WIN32) && !defined(__CYGWIN__)
1443 // 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)
1444 const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
1445 const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
1446 ImVector<ImWchar> buf;
1447 buf.resize(filename_wsize + mode_wsize);
1448 ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
1449 ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
1450 return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]);
1451 #else
1452 return fopen(filename, mode);
1453 #endif
1454 }
1455
1456 // Load file content into memory
1457 // 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)1458 void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes)
1459 {
1460 IM_ASSERT(filename && file_open_mode);
1461 if (out_file_size)
1462 *out_file_size = 0;
1463
1464 FILE* f;
1465 if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
1466 return NULL;
1467
1468 long file_size_signed;
1469 if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET))
1470 {
1471 fclose(f);
1472 return NULL;
1473 }
1474
1475 int file_size = (int)file_size_signed;
1476 void* file_data = ImGui::MemAlloc((size_t)(file_size + padding_bytes));
1477 if (file_data == NULL)
1478 {
1479 fclose(f);
1480 return NULL;
1481 }
1482 if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size)
1483 {
1484 fclose(f);
1485 ImGui::MemFree(file_data);
1486 return NULL;
1487 }
1488 if (padding_bytes > 0)
1489 memset((void *)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
1490
1491 fclose(f);
1492 if (out_file_size)
1493 *out_file_size = file_size;
1494
1495 return file_data;
1496 }
1497
1498 //-----------------------------------------------------------------------------
1499 // ImGuiStorage
1500 // Helper: Key->value storage
1501 //-----------------------------------------------------------------------------
1502
1503 // std::lower_bound but without the bullshit
LowerBound(ImVector<ImGuiStorage::Pair> & data,ImGuiID key)1504 static ImVector<ImGuiStorage::Pair>::iterator LowerBound(ImVector<ImGuiStorage::Pair>& data, ImGuiID key)
1505 {
1506 ImVector<ImGuiStorage::Pair>::iterator first = data.begin();
1507 ImVector<ImGuiStorage::Pair>::iterator last = data.end();
1508 size_t count = (size_t)(last - first);
1509 while (count > 0)
1510 {
1511 size_t count2 = count >> 1;
1512 ImVector<ImGuiStorage::Pair>::iterator mid = first + count2;
1513 if (mid->key < key)
1514 {
1515 first = ++mid;
1516 count -= count2 + 1;
1517 }
1518 else
1519 {
1520 count = count2;
1521 }
1522 }
1523 return first;
1524 }
1525
1526 // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
BuildSortByKey()1527 void ImGuiStorage::BuildSortByKey()
1528 {
1529 struct StaticFunc
1530 {
1531 static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
1532 {
1533 // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
1534 if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1;
1535 if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1;
1536 return 0;
1537 }
1538 };
1539 if (Data.Size > 1)
1540 qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID);
1541 }
1542
GetInt(ImGuiID key,int default_val) const1543 int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
1544 {
1545 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1546 if (it == Data.end() || it->key != key)
1547 return default_val;
1548 return it->val_i;
1549 }
1550
GetBool(ImGuiID key,bool default_val) const1551 bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
1552 {
1553 return GetInt(key, default_val ? 1 : 0) != 0;
1554 }
1555
GetFloat(ImGuiID key,float default_val) const1556 float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
1557 {
1558 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1559 if (it == Data.end() || it->key != key)
1560 return default_val;
1561 return it->val_f;
1562 }
1563
GetVoidPtr(ImGuiID key) const1564 void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
1565 {
1566 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1567 if (it == Data.end() || it->key != key)
1568 return NULL;
1569 return it->val_p;
1570 }
1571
1572 // 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)1573 int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
1574 {
1575 ImVector<Pair>::iterator it = LowerBound(Data, key);
1576 if (it == Data.end() || it->key != key)
1577 it = Data.insert(it, Pair(key, default_val));
1578 return &it->val_i;
1579 }
1580
GetBoolRef(ImGuiID key,bool default_val)1581 bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
1582 {
1583 return (bool*)GetIntRef(key, default_val ? 1 : 0);
1584 }
1585
GetFloatRef(ImGuiID key,float default_val)1586 float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
1587 {
1588 ImVector<Pair>::iterator it = LowerBound(Data, key);
1589 if (it == Data.end() || it->key != key)
1590 it = Data.insert(it, Pair(key, default_val));
1591 return &it->val_f;
1592 }
1593
GetVoidPtrRef(ImGuiID key,void * default_val)1594 void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
1595 {
1596 ImVector<Pair>::iterator it = LowerBound(Data, key);
1597 if (it == Data.end() || it->key != key)
1598 it = Data.insert(it, Pair(key, default_val));
1599 return &it->val_p;
1600 }
1601
1602 // 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)1603 void ImGuiStorage::SetInt(ImGuiID key, int val)
1604 {
1605 ImVector<Pair>::iterator it = LowerBound(Data, key);
1606 if (it == Data.end() || it->key != key)
1607 {
1608 Data.insert(it, Pair(key, val));
1609 return;
1610 }
1611 it->val_i = val;
1612 }
1613
SetBool(ImGuiID key,bool val)1614 void ImGuiStorage::SetBool(ImGuiID key, bool val)
1615 {
1616 SetInt(key, val ? 1 : 0);
1617 }
1618
SetFloat(ImGuiID key,float val)1619 void ImGuiStorage::SetFloat(ImGuiID key, float val)
1620 {
1621 ImVector<Pair>::iterator it = LowerBound(Data, key);
1622 if (it == Data.end() || it->key != key)
1623 {
1624 Data.insert(it, Pair(key, val));
1625 return;
1626 }
1627 it->val_f = val;
1628 }
1629
SetVoidPtr(ImGuiID key,void * val)1630 void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
1631 {
1632 ImVector<Pair>::iterator it = LowerBound(Data, key);
1633 if (it == Data.end() || it->key != key)
1634 {
1635 Data.insert(it, Pair(key, val));
1636 return;
1637 }
1638 it->val_p = val;
1639 }
1640
SetAllInt(int v)1641 void ImGuiStorage::SetAllInt(int v)
1642 {
1643 for (int i = 0; i < Data.Size; i++)
1644 Data[i].val_i = v;
1645 }
1646
1647 //-----------------------------------------------------------------------------
1648 // ImGuiTextFilter
1649 //-----------------------------------------------------------------------------
1650
1651 // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
ImGuiTextFilter(const char * default_filter)1652 ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
1653 {
1654 if (default_filter)
1655 {
1656 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
1657 Build();
1658 }
1659 else
1660 {
1661 InputBuf[0] = 0;
1662 CountGrep = 0;
1663 }
1664 }
1665
Draw(const char * label,float width)1666 bool ImGuiTextFilter::Draw(const char* label, float width)
1667 {
1668 if (width != 0.0f)
1669 ImGui::PushItemWidth(width);
1670 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
1671 if (width != 0.0f)
1672 ImGui::PopItemWidth();
1673 if (value_changed)
1674 Build();
1675 return value_changed;
1676 }
1677
split(char separator,ImVector<TextRange> & out)1678 void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>& out)
1679 {
1680 out.resize(0);
1681 const char* wb = b;
1682 const char* we = wb;
1683 while (we < e)
1684 {
1685 if (*we == separator)
1686 {
1687 out.push_back(TextRange(wb, we));
1688 wb = we + 1;
1689 }
1690 we++;
1691 }
1692 if (wb != we)
1693 out.push_back(TextRange(wb, we));
1694 }
1695
Build()1696 void ImGuiTextFilter::Build()
1697 {
1698 Filters.resize(0);
1699 TextRange input_range(InputBuf, InputBuf+strlen(InputBuf));
1700 input_range.split(',', Filters);
1701
1702 CountGrep = 0;
1703 for (int i = 0; i != Filters.Size; i++)
1704 {
1705 Filters[i].trim_blanks();
1706 if (Filters[i].empty())
1707 continue;
1708 if (Filters[i].front() != '-')
1709 CountGrep += 1;
1710 }
1711 }
1712
PassFilter(const char * text,const char * text_end) const1713 bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
1714 {
1715 if (Filters.empty())
1716 return true;
1717
1718 if (text == NULL)
1719 text = "";
1720
1721 for (int i = 0; i != Filters.Size; i++)
1722 {
1723 const TextRange& f = Filters[i];
1724 if (f.empty())
1725 continue;
1726 if (f.front() == '-')
1727 {
1728 // Subtract
1729 if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL)
1730 return false;
1731 }
1732 else
1733 {
1734 // Grep
1735 if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
1736 return true;
1737 }
1738 }
1739
1740 // Implicit * grep
1741 if (CountGrep == 0)
1742 return true;
1743
1744 return false;
1745 }
1746
1747 //-----------------------------------------------------------------------------
1748 // ImGuiTextBuffer
1749 //-----------------------------------------------------------------------------
1750
1751 // On some platform vsnprintf() takes va_list by reference and modifies it.
1752 // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
1753 #ifndef va_copy
1754 #define va_copy(dest, src) (dest = src)
1755 #endif
1756
1757 // Helper: Text buffer for logging/accumulating text
appendfv(const char * fmt,va_list args)1758 void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
1759 {
1760 va_list args_copy;
1761 va_copy(args_copy, args);
1762
1763 int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
1764 if (len <= 0)
1765 return;
1766
1767 const int write_off = Buf.Size;
1768 const int needed_sz = write_off + len;
1769 if (write_off + len >= Buf.Capacity)
1770 {
1771 int double_capacity = Buf.Capacity * 2;
1772 Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity);
1773 }
1774
1775 Buf.resize(needed_sz);
1776 ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);
1777 }
1778
appendf(const char * fmt,...)1779 void ImGuiTextBuffer::appendf(const char* fmt, ...)
1780 {
1781 va_list args;
1782 va_start(args, fmt);
1783 appendfv(fmt, args);
1784 va_end(args);
1785 }
1786
1787 //-----------------------------------------------------------------------------
1788 // ImGuiSimpleColumns (internal use only)
1789 //-----------------------------------------------------------------------------
1790
ImGuiMenuColumns()1791 ImGuiMenuColumns::ImGuiMenuColumns()
1792 {
1793 Count = 0;
1794 Spacing = Width = NextWidth = 0.0f;
1795 memset(Pos, 0, sizeof(Pos));
1796 memset(NextWidths, 0, sizeof(NextWidths));
1797 }
1798
Update(int count,float spacing,bool clear)1799 void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
1800 {
1801 IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
1802 Count = count;
1803 Width = NextWidth = 0.0f;
1804 Spacing = spacing;
1805 if (clear) memset(NextWidths, 0, sizeof(NextWidths));
1806 for (int i = 0; i < Count; i++)
1807 {
1808 if (i > 0 && NextWidths[i] > 0.0f)
1809 Width += Spacing;
1810 Pos[i] = (float)(int)Width;
1811 Width += NextWidths[i];
1812 NextWidths[i] = 0.0f;
1813 }
1814 }
1815
DeclColumns(float w0,float w1,float w2)1816 float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
1817 {
1818 NextWidth = 0.0f;
1819 NextWidths[0] = ImMax(NextWidths[0], w0);
1820 NextWidths[1] = ImMax(NextWidths[1], w1);
1821 NextWidths[2] = ImMax(NextWidths[2], w2);
1822 for (int i = 0; i < 3; i++)
1823 NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
1824 return ImMax(Width, NextWidth);
1825 }
1826
CalcExtraSpace(float avail_w)1827 float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
1828 {
1829 return ImMax(0.0f, avail_w - Width);
1830 }
1831
1832 //-----------------------------------------------------------------------------
1833 // ImGuiListClipper
1834 //-----------------------------------------------------------------------------
1835
SetCursorPosYAndSetupDummyPrevLine(float pos_y,float line_height)1836 static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height)
1837 {
1838 // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor.
1839 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.
1840 // The clipper should probably have a 4th step to display the last item in a regular manner.
1841 ImGui::SetCursorPosY(pos_y);
1842 ImGuiWindow* window = ImGui::GetCurrentWindow();
1843 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.
1844 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.
1845 if (window->DC.ColumnsSet)
1846 window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
1847 }
1848
1849 // Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
1850 // Use case B: Begin() called from constructor with items_height>0
1851 // 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)1852 void ImGuiListClipper::Begin(int count, float items_height)
1853 {
1854 StartPosY = ImGui::GetCursorPosY();
1855 ItemsHeight = items_height;
1856 ItemsCount = count;
1857 StepNo = 0;
1858 DisplayEnd = DisplayStart = -1;
1859 if (ItemsHeight > 0.0f)
1860 {
1861 ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
1862 if (DisplayStart > 0)
1863 SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
1864 StepNo = 2;
1865 }
1866 }
1867
End()1868 void ImGuiListClipper::End()
1869 {
1870 if (ItemsCount < 0)
1871 return;
1872 // 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.
1873 if (ItemsCount < INT_MAX)
1874 SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
1875 ItemsCount = -1;
1876 StepNo = 3;
1877 }
1878
Step()1879 bool ImGuiListClipper::Step()
1880 {
1881 if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems)
1882 {
1883 ItemsCount = -1;
1884 return false;
1885 }
1886 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.
1887 {
1888 DisplayStart = 0;
1889 DisplayEnd = 1;
1890 StartPosY = ImGui::GetCursorPosY();
1891 StepNo = 1;
1892 return true;
1893 }
1894 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.
1895 {
1896 if (ItemsCount == 1) { ItemsCount = -1; return false; }
1897 float items_height = ImGui::GetCursorPosY() - StartPosY;
1898 IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically
1899 Begin(ItemsCount-1, items_height);
1900 DisplayStart++;
1901 DisplayEnd++;
1902 StepNo = 3;
1903 return true;
1904 }
1905 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.
1906 {
1907 IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
1908 StepNo = 3;
1909 return true;
1910 }
1911 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.
1912 End();
1913 return false;
1914 }
1915
1916 //-----------------------------------------------------------------------------
1917 // ImGuiWindow
1918 //-----------------------------------------------------------------------------
1919
ImGuiWindow(ImGuiContext * context,const char * name)1920 ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name)
1921 : DrawListInst(&context->DrawListSharedData)
1922 {
1923 Name = ImStrdup(name);
1924 ID = ImHash(name, 0);
1925 IDStack.push_back(ID);
1926 Flags = 0;
1927 PosFloat = Pos = ImVec2(0.0f, 0.0f);
1928 Size = SizeFull = ImVec2(0.0f, 0.0f);
1929 SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f);
1930 WindowPadding = ImVec2(0.0f, 0.0f);
1931 WindowRounding = 0.0f;
1932 WindowBorderSize = 0.0f;
1933 MoveId = GetID("#MOVE");
1934 ChildId = 0;
1935 Scroll = ImVec2(0.0f, 0.0f);
1936 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
1937 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
1938 ScrollbarX = ScrollbarY = false;
1939 ScrollbarSizes = ImVec2(0.0f, 0.0f);
1940 Active = WasActive = false;
1941 WriteAccessed = false;
1942 Collapsed = false;
1943 CollapseToggleWanted = false;
1944 SkipItems = false;
1945 Appearing = false;
1946 CloseButton = false;
1947 BeginOrderWithinParent = -1;
1948 BeginOrderWithinContext = -1;
1949 BeginCount = 0;
1950 PopupId = 0;
1951 AutoFitFramesX = AutoFitFramesY = -1;
1952 AutoFitOnlyGrows = false;
1953 AutoFitChildAxises = 0x00;
1954 AutoPosLastDirection = ImGuiDir_None;
1955 HiddenFrames = 0;
1956 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
1957 SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
1958
1959 LastFrameActive = -1;
1960 ItemWidthDefault = 0.0f;
1961 FontWindowScale = 1.0f;
1962
1963 DrawList = &DrawListInst;
1964 DrawList->_OwnerName = Name;
1965 ParentWindow = NULL;
1966 RootWindow = NULL;
1967 RootWindowForTitleBarHighlight = NULL;
1968 RootWindowForTabbing = NULL;
1969 RootWindowForNav = NULL;
1970
1971 NavLastIds[0] = NavLastIds[1] = 0;
1972 NavRectRel[0] = NavRectRel[1] = ImRect();
1973 NavLastChildNavWindow = NULL;
1974
1975 FocusIdxAllCounter = FocusIdxTabCounter = -1;
1976 FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
1977 FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX;
1978 }
1979
~ImGuiWindow()1980 ImGuiWindow::~ImGuiWindow()
1981 {
1982 IM_ASSERT(DrawList == &DrawListInst);
1983 IM_DELETE(Name);
1984 for (int i = 0; i != ColumnsStorage.Size; i++)
1985 ColumnsStorage[i].~ImGuiColumnsSet();
1986 }
1987
GetID(const char * str,const char * str_end)1988 ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
1989 {
1990 ImGuiID seed = IDStack.back();
1991 ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
1992 ImGui::KeepAliveID(id);
1993 return id;
1994 }
1995
GetID(const void * ptr)1996 ImGuiID ImGuiWindow::GetID(const void* ptr)
1997 {
1998 ImGuiID seed = IDStack.back();
1999 ImGuiID id = ImHash(&ptr, sizeof(void*), seed);
2000 ImGui::KeepAliveID(id);
2001 return id;
2002 }
2003
GetIDNoKeepAlive(const char * str,const char * str_end)2004 ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end)
2005 {
2006 ImGuiID seed = IDStack.back();
2007 return ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
2008 }
2009
2010 // This is only used in rare/specific situations to manufacture an ID out of nowhere.
GetIDFromRectangle(const ImRect & r_abs)2011 ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
2012 {
2013 ImGuiID seed = IDStack.back();
2014 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) };
2015 ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed);
2016 ImGui::KeepAliveID(id);
2017 return id;
2018 }
2019
2020 //-----------------------------------------------------------------------------
2021 // Internal API exposed in imgui_internal.h
2022 //-----------------------------------------------------------------------------
2023
SetCurrentWindow(ImGuiWindow * window)2024 static void SetCurrentWindow(ImGuiWindow* window)
2025 {
2026 ImGuiContext& g = *GImGui;
2027 g.CurrentWindow = window;
2028 if (window)
2029 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
2030 }
2031
SetNavID(ImGuiID id,int nav_layer)2032 static void SetNavID(ImGuiID id, int nav_layer)
2033 {
2034 ImGuiContext& g = *GImGui;
2035 IM_ASSERT(g.NavWindow);
2036 IM_ASSERT(nav_layer == 0 || nav_layer == 1);
2037 g.NavId = id;
2038 g.NavWindow->NavLastIds[nav_layer] = id;
2039 }
2040
SetNavIDWithRectRel(ImGuiID id,int nav_layer,const ImRect & rect_rel)2041 static void SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel)
2042 {
2043 ImGuiContext& g = *GImGui;
2044 SetNavID(id, nav_layer);
2045 g.NavWindow->NavRectRel[nav_layer] = rect_rel;
2046 g.NavMousePosDirty = true;
2047 g.NavDisableHighlight = false;
2048 g.NavDisableMouseHover = true;
2049 }
2050
SetActiveID(ImGuiID id,ImGuiWindow * window)2051 void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
2052 {
2053 ImGuiContext& g = *GImGui;
2054 g.ActiveIdIsJustActivated = (g.ActiveId != id);
2055 if (g.ActiveIdIsJustActivated)
2056 g.ActiveIdTimer = 0.0f;
2057 g.ActiveId = id;
2058 g.ActiveIdAllowNavDirFlags = 0;
2059 g.ActiveIdAllowOverlap = false;
2060 g.ActiveIdWindow = window;
2061 if (id)
2062 {
2063 g.ActiveIdIsAlive = true;
2064 g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
2065 }
2066 }
2067
GetActiveID()2068 ImGuiID ImGui::GetActiveID()
2069 {
2070 ImGuiContext& g = *GImGui;
2071 return g.ActiveId;
2072 }
2073
SetFocusID(ImGuiID id,ImGuiWindow * window)2074 void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
2075 {
2076 ImGuiContext& g = *GImGui;
2077 IM_ASSERT(id != 0);
2078
2079 // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it.
2080 const int nav_layer = window->DC.NavLayerCurrent;
2081 if (g.NavWindow != window)
2082 g.NavInitRequest = false;
2083 g.NavId = id;
2084 g.NavWindow = window;
2085 g.NavLayer = nav_layer;
2086 window->NavLastIds[nav_layer] = id;
2087 if (window->DC.LastItemId == id)
2088 window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos);
2089
2090 if (g.ActiveIdSource == ImGuiInputSource_Nav)
2091 g.NavDisableMouseHover = true;
2092 else
2093 g.NavDisableHighlight = true;
2094 }
2095
ClearActiveID()2096 void ImGui::ClearActiveID()
2097 {
2098 SetActiveID(0, NULL);
2099 }
2100
SetHoveredID(ImGuiID id)2101 void ImGui::SetHoveredID(ImGuiID id)
2102 {
2103 ImGuiContext& g = *GImGui;
2104 g.HoveredId = id;
2105 g.HoveredIdAllowOverlap = false;
2106 g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f;
2107 }
2108
GetHoveredID()2109 ImGuiID ImGui::GetHoveredID()
2110 {
2111 ImGuiContext& g = *GImGui;
2112 return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
2113 }
2114
KeepAliveID(ImGuiID id)2115 void ImGui::KeepAliveID(ImGuiID id)
2116 {
2117 ImGuiContext& g = *GImGui;
2118 if (g.ActiveId == id)
2119 g.ActiveIdIsAlive = true;
2120 }
2121
IsWindowContentHoverable(ImGuiWindow * window,ImGuiHoveredFlags flags)2122 static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
2123 {
2124 // An active popup disable hovering on other windows (apart from its own children)
2125 // FIXME-OPT: This could be cached/stored within the window.
2126 ImGuiContext& g = *GImGui;
2127 if (g.NavWindow)
2128 if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)
2129 if (focused_root_window->WasActive && focused_root_window != window->RootWindow)
2130 {
2131 // For the purpose of those flags we differentiate "standard popup" from "modal popup"
2132 // NB: The order of those two tests is important because Modal windows are also Popups.
2133 if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
2134 return false;
2135 if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
2136 return false;
2137 }
2138
2139 return true;
2140 }
2141
2142 // Advance cursor given item size for layout.
ItemSize(const ImVec2 & size,float text_offset_y)2143 void ImGui::ItemSize(const ImVec2& size, float text_offset_y)
2144 {
2145 ImGuiContext& g = *GImGui;
2146 ImGuiWindow* window = g.CurrentWindow;
2147 if (window->SkipItems)
2148 return;
2149
2150 // Always align ourselves on pixel boundaries
2151 const float line_height = ImMax(window->DC.CurrentLineHeight, size.y);
2152 const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y);
2153 //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]
2154 window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y);
2155 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));
2156 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
2157 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
2158 //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
2159
2160 window->DC.PrevLineHeight = line_height;
2161 window->DC.PrevLineTextBaseOffset = text_base_offset;
2162 window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f;
2163
2164 // Horizontal layout mode
2165 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
2166 SameLine();
2167 }
2168
ItemSize(const ImRect & bb,float text_offset_y)2169 void ImGui::ItemSize(const ImRect& bb, float text_offset_y)
2170 {
2171 ItemSize(bb.GetSize(), text_offset_y);
2172 }
2173
NavScoreItemGetQuadrant(float dx,float dy)2174 static ImGuiDir NavScoreItemGetQuadrant(float dx, float dy)
2175 {
2176 if (fabsf(dx) > fabsf(dy))
2177 return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
2178 return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
2179 }
2180
NavScoreItemDistInterval(float a0,float a1,float b0,float b1)2181 static float NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
2182 {
2183 if (a1 < b0)
2184 return a1 - b0;
2185 if (b1 < a0)
2186 return a0 - b1;
2187 return 0.0f;
2188 }
2189
2190 // Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
NavScoreItem(ImGuiNavMoveResult * result,ImRect cand)2191 static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
2192 {
2193 ImGuiContext& g = *GImGui;
2194 ImGuiWindow* window = g.CurrentWindow;
2195 if (g.NavLayer != window->DC.NavLayerCurrent)
2196 return false;
2197
2198 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)
2199 g.NavScoringCount++;
2200
2201 // 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)
2202 if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
2203 {
2204 cand.Min.y = ImClamp(cand.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y);
2205 cand.Max.y = ImClamp(cand.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y);
2206 }
2207 else
2208 {
2209 cand.Min.x = ImClamp(cand.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
2210 cand.Max.x = ImClamp(cand.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
2211 }
2212
2213 // Compute distance between boxes
2214 // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
2215 float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
2216 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
2217 if (dby != 0.0f && dbx != 0.0f)
2218 dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
2219 float dist_box = fabsf(dbx) + fabsf(dby);
2220
2221 // 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)
2222 float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
2223 float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
2224 float dist_center = fabsf(dcx) + fabsf(dcy); // L1 metric (need this for our connectedness guarantee)
2225
2226 // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
2227 ImGuiDir quadrant;
2228 float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
2229 if (dbx != 0.0f || dby != 0.0f)
2230 {
2231 // For non-overlapping boxes, use distance between boxes
2232 dax = dbx;
2233 day = dby;
2234 dist_axial = dist_box;
2235 quadrant = NavScoreItemGetQuadrant(dbx, dby);
2236 }
2237 else if (dcx != 0.0f || dcy != 0.0f)
2238 {
2239 // For overlapping boxes with different centers, use distance between centers
2240 dax = dcx;
2241 day = dcy;
2242 dist_axial = dist_center;
2243 quadrant = NavScoreItemGetQuadrant(dcx, dcy);
2244 }
2245 else
2246 {
2247 // 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)
2248 quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
2249 }
2250
2251 #if IMGUI_DEBUG_NAV_SCORING
2252 char buf[128];
2253 if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
2254 {
2255 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]);
2256 g.OverlayDrawList.AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100));
2257 g.OverlayDrawList.AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
2258 g.OverlayDrawList.AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150));
2259 g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
2260 }
2261 else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
2262 {
2263 if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; }
2264 if (quadrant == g.NavMoveDir)
2265 {
2266 ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
2267 g.OverlayDrawList.AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
2268 g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
2269 }
2270 }
2271 #endif
2272
2273 // Is it in the quadrant we're interesting in moving to?
2274 bool new_best = false;
2275 if (quadrant == g.NavMoveDir)
2276 {
2277 // Does it beat the current best candidate?
2278 if (dist_box < result->DistBox)
2279 {
2280 result->DistBox = dist_box;
2281 result->DistCenter = dist_center;
2282 return true;
2283 }
2284 if (dist_box == result->DistBox)
2285 {
2286 // Try using distance between center points to break ties
2287 if (dist_center < result->DistCenter)
2288 {
2289 result->DistCenter = dist_center;
2290 new_best = true;
2291 }
2292 else if (dist_center == result->DistCenter)
2293 {
2294 // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
2295 // (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),
2296 // 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.
2297 if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
2298 new_best = true;
2299 }
2300 }
2301 }
2302
2303 // 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
2304 // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
2305 // 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.
2306 // 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.
2307 // Disabling it may however lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
2308 if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
2309 if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
2310 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))
2311 {
2312 result->DistAxial = dist_axial;
2313 new_best = true;
2314 }
2315
2316 return new_best;
2317 }
2318
NavSaveLastChildNavWindow(ImGuiWindow * child_window)2319 static void NavSaveLastChildNavWindow(ImGuiWindow* child_window)
2320 {
2321 ImGuiWindow* parent_window = child_window;
2322 while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
2323 parent_window = parent_window->ParentWindow;
2324 if (parent_window && parent_window != child_window)
2325 parent_window->NavLastChildNavWindow = child_window;
2326 }
2327
2328 // Call when we are expected to land on Layer 0 after FocusWindow()
NavRestoreLastChildNavWindow(ImGuiWindow * window)2329 static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window)
2330 {
2331 return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window;
2332 }
2333
NavRestoreLayer(int layer)2334 static void NavRestoreLayer(int layer)
2335 {
2336 ImGuiContext& g = *GImGui;
2337 g.NavLayer = layer;
2338 if (layer == 0)
2339 g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow);
2340 if (layer == 0 && g.NavWindow->NavLastIds[0] != 0)
2341 SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]);
2342 else
2343 ImGui::NavInitWindow(g.NavWindow, true);
2344 }
2345
NavUpdateAnyRequestFlag()2346 static inline void NavUpdateAnyRequestFlag()
2347 {
2348 ImGuiContext& g = *GImGui;
2349 g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
2350 if (g.NavAnyRequest)
2351 IM_ASSERT(g.NavWindow != NULL);
2352 }
2353
NavMoveRequestButNoResultYet()2354 static bool NavMoveRequestButNoResultYet()
2355 {
2356 ImGuiContext& g = *GImGui;
2357 return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
2358 }
2359
NavMoveRequestCancel()2360 void ImGui::NavMoveRequestCancel()
2361 {
2362 ImGuiContext& g = *GImGui;
2363 g.NavMoveRequest = false;
2364 NavUpdateAnyRequestFlag();
2365 }
2366
2367 // 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)2368 static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id)
2369 {
2370 ImGuiContext& g = *GImGui;
2371 //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.
2372 // return;
2373
2374 const ImGuiItemFlags item_flags = window->DC.ItemFlags;
2375 const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
2376 if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
2377 {
2378 // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
2379 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
2380 {
2381 g.NavInitResultId = id;
2382 g.NavInitResultRectRel = nav_bb_rel;
2383 }
2384 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
2385 {
2386 g.NavInitRequest = false; // Found a match, clear request
2387 NavUpdateAnyRequestFlag();
2388 }
2389 }
2390
2391 // Scoring for navigation
2392 if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav))
2393 {
2394 ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
2395 #if IMGUI_DEBUG_NAV_SCORING
2396 // [DEBUG] Score all items in NavWindow at all times
2397 if (!g.NavMoveRequest)
2398 g.NavMoveDir = g.NavMoveDirLast;
2399 bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
2400 #else
2401 bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
2402 #endif
2403 if (new_best)
2404 {
2405 result->ID = id;
2406 result->ParentID = window->IDStack.back();
2407 result->Window = window;
2408 result->RectRel = nav_bb_rel;
2409 }
2410 }
2411
2412 // Update window-relative bounding box of navigated item
2413 if (g.NavId == id)
2414 {
2415 g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
2416 g.NavLayer = window->DC.NavLayerCurrent;
2417 g.NavIdIsAlive = true;
2418 g.NavIdTabCounter = window->FocusIdxTabCounter;
2419 window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
2420 }
2421 }
2422
2423 // Declare item bounding box for clipping and interaction.
2424 // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
2425 // 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)2426 bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg)
2427 {
2428 ImGuiContext& g = *GImGui;
2429 ImGuiWindow* window = g.CurrentWindow;
2430
2431 if (id != 0)
2432 {
2433 // Navigation processing runs prior to clipping early-out
2434 // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
2435 // (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.
2436 // 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.
2437 // 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)
2438 window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask;
2439 if (g.NavId == id || g.NavAnyRequest)
2440 if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
2441 if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
2442 NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
2443 }
2444
2445 window->DC.LastItemId = id;
2446 window->DC.LastItemRect = bb;
2447 window->DC.LastItemStatusFlags = 0;
2448
2449 // Clipping test
2450 const bool is_clipped = IsClippedEx(bb, id, false);
2451 if (is_clipped)
2452 return false;
2453 //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
2454
2455 // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
2456 if (IsMouseHoveringRect(bb.Min, bb.Max))
2457 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect;
2458 return true;
2459 }
2460
2461 // This is roughly matching the behavior of internal-facing ItemHoverable()
2462 // - 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()
2463 // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
IsItemHovered(ImGuiHoveredFlags flags)2464 bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
2465 {
2466 ImGuiContext& g = *GImGui;
2467 ImGuiWindow* window = g.CurrentWindow;
2468 if (g.NavDisableMouseHover && !g.NavDisableHighlight)
2469 return IsItemFocused();
2470
2471 // Test for bounding box overlap, as updated as ItemAdd()
2472 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
2473 return false;
2474 IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function
2475
2476 // Test if we are hovering the right window (our window could be behind another window)
2477 // [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.
2478 // 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.
2479 //if (g.HoveredWindow != window)
2480 // return false;
2481 if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped))
2482 return false;
2483
2484 // Test if another item is active (e.g. being dragged)
2485 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
2486 if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId)
2487 return false;
2488
2489 // Test if interactions on this window are blocked by an active popup or modal
2490 if (!IsWindowContentHoverable(window, flags))
2491 return false;
2492
2493 // Test if the item is disabled
2494 if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
2495 return false;
2496
2497 // 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.
2498 if (window->DC.LastItemId == window->MoveId && window->WriteAccessed)
2499 return false;
2500 return true;
2501 }
2502
2503 // Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
ItemHoverable(const ImRect & bb,ImGuiID id)2504 bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
2505 {
2506 ImGuiContext& g = *GImGui;
2507 if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
2508 return false;
2509
2510 ImGuiWindow* window = g.CurrentWindow;
2511 if (g.HoveredWindow != window)
2512 return false;
2513 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
2514 return false;
2515 if (!IsMouseHoveringRect(bb.Min, bb.Max))
2516 return false;
2517 if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_Default))
2518 return false;
2519 if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
2520 return false;
2521
2522 SetHoveredID(id);
2523 return true;
2524 }
2525
IsClippedEx(const ImRect & bb,ImGuiID id,bool clip_even_when_logged)2526 bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged)
2527 {
2528 ImGuiContext& g = *GImGui;
2529 ImGuiWindow* window = g.CurrentWindow;
2530 if (!bb.Overlaps(window->ClipRect))
2531 if (id == 0 || id != g.ActiveId)
2532 if (clip_even_when_logged || !g.LogEnabled)
2533 return true;
2534 return false;
2535 }
2536
FocusableItemRegister(ImGuiWindow * window,ImGuiID id,bool tab_stop)2537 bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop)
2538 {
2539 ImGuiContext& g = *GImGui;
2540
2541 const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus;
2542 window->FocusIdxAllCounter++;
2543 if (allow_keyboard_focus)
2544 window->FocusIdxTabCounter++;
2545
2546 // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item.
2547 // Note that we can always TAB out of a widget that doesn't allow tabbing in.
2548 if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab))
2549 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.
2550
2551 if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent)
2552 return true;
2553 if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
2554 {
2555 g.NavJustTabbedId = id;
2556 return true;
2557 }
2558
2559 return false;
2560 }
2561
FocusableItemUnregister(ImGuiWindow * window)2562 void ImGui::FocusableItemUnregister(ImGuiWindow* window)
2563 {
2564 window->FocusIdxAllCounter--;
2565 window->FocusIdxTabCounter--;
2566 }
2567
CalcItemSize(ImVec2 size,float default_x,float default_y)2568 ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y)
2569 {
2570 ImGuiContext& g = *GImGui;
2571 ImVec2 content_max;
2572 if (size.x < 0.0f || size.y < 0.0f)
2573 content_max = g.CurrentWindow->Pos + GetContentRegionMax();
2574 if (size.x <= 0.0f)
2575 size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x;
2576 if (size.y <= 0.0f)
2577 size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y;
2578 return size;
2579 }
2580
CalcWrapWidthForPos(const ImVec2 & pos,float wrap_pos_x)2581 float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
2582 {
2583 if (wrap_pos_x < 0.0f)
2584 return 0.0f;
2585
2586 ImGuiWindow* window = GetCurrentWindowRead();
2587 if (wrap_pos_x == 0.0f)
2588 wrap_pos_x = GetContentRegionMax().x + window->Pos.x;
2589 else if (wrap_pos_x > 0.0f)
2590 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
2591
2592 return ImMax(wrap_pos_x - pos.x, 1.0f);
2593 }
2594
2595 //-----------------------------------------------------------------------------
2596
MemAlloc(size_t sz)2597 void* ImGui::MemAlloc(size_t sz)
2598 {
2599 GImAllocatorActiveAllocationsCount++;
2600 return GImAllocatorAllocFunc(sz, GImAllocatorUserData);
2601 }
2602
MemFree(void * ptr)2603 void ImGui::MemFree(void* ptr)
2604 {
2605 if (ptr) GImAllocatorActiveAllocationsCount--;
2606 return GImAllocatorFreeFunc(ptr, GImAllocatorUserData);
2607 }
2608
GetClipboardText()2609 const char* ImGui::GetClipboardText()
2610 {
2611 return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : "";
2612 }
2613
SetClipboardText(const char * text)2614 void ImGui::SetClipboardText(const char* text)
2615 {
2616 if (GImGui->IO.SetClipboardTextFn)
2617 GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text);
2618 }
2619
GetVersion()2620 const char* ImGui::GetVersion()
2621 {
2622 return IMGUI_VERSION;
2623 }
2624
2625 // Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself
2626 // 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()2627 ImGuiContext* ImGui::GetCurrentContext()
2628 {
2629 return GImGui;
2630 }
2631
SetCurrentContext(ImGuiContext * ctx)2632 void ImGui::SetCurrentContext(ImGuiContext* ctx)
2633 {
2634 #ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
2635 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
2636 #else
2637 GImGui = ctx;
2638 #endif
2639 }
2640
SetAllocatorFunctions(void * (* alloc_func)(size_t sz,void * user_data),void (* free_func)(void * ptr,void * user_data),void * user_data)2641 void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data)
2642 {
2643 GImAllocatorAllocFunc = alloc_func;
2644 GImAllocatorFreeFunc = free_func;
2645 GImAllocatorUserData = user_data;
2646 }
2647
CreateContext(ImFontAtlas * shared_font_atlas)2648 ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
2649 {
2650 ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
2651 if (GImGui == NULL)
2652 SetCurrentContext(ctx);
2653 Initialize(ctx);
2654 return ctx;
2655 }
2656
DestroyContext(ImGuiContext * ctx)2657 void ImGui::DestroyContext(ImGuiContext* ctx)
2658 {
2659 if (ctx == NULL)
2660 ctx = GImGui;
2661 Shutdown(ctx);
2662 if (GImGui == ctx)
2663 SetCurrentContext(NULL);
2664 IM_DELETE(ctx);
2665 }
2666
GetIO()2667 ImGuiIO& ImGui::GetIO()
2668 {
2669 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
2670 return GImGui->IO;
2671 }
2672
GetStyle()2673 ImGuiStyle& ImGui::GetStyle()
2674 {
2675 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
2676 return GImGui->Style;
2677 }
2678
2679 // Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame()
GetDrawData()2680 ImDrawData* ImGui::GetDrawData()
2681 {
2682 ImGuiContext& g = *GImGui;
2683 return g.DrawData.Valid ? &g.DrawData : NULL;
2684 }
2685
GetTime()2686 float ImGui::GetTime()
2687 {
2688 return GImGui->Time;
2689 }
2690
GetFrameCount()2691 int ImGui::GetFrameCount()
2692 {
2693 return GImGui->FrameCount;
2694 }
2695
GetOverlayDrawList()2696 ImDrawList* ImGui::GetOverlayDrawList()
2697 {
2698 return &GImGui->OverlayDrawList;
2699 }
2700
GetDrawListSharedData()2701 ImDrawListSharedData* ImGui::GetDrawListSharedData()
2702 {
2703 return &GImGui->DrawListSharedData;
2704 }
2705
2706 // This needs to be called before we submit any widget (aka in or before Begin)
NavInitWindow(ImGuiWindow * window,bool force_reinit)2707 void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
2708 {
2709 ImGuiContext& g = *GImGui;
2710 IM_ASSERT(window == g.NavWindow);
2711 bool init_for_nav = false;
2712 if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
2713 if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
2714 init_for_nav = true;
2715 if (init_for_nav)
2716 {
2717 SetNavID(0, g.NavLayer);
2718 g.NavInitRequest = true;
2719 g.NavInitRequestFromMove = false;
2720 g.NavInitResultId = 0;
2721 g.NavInitResultRectRel = ImRect();
2722 NavUpdateAnyRequestFlag();
2723 }
2724 else
2725 {
2726 g.NavId = window->NavLastIds[0];
2727 }
2728 }
2729
NavCalcPreferredMousePos()2730 static ImVec2 NavCalcPreferredMousePos()
2731 {
2732 ImGuiContext& g = *GImGui;
2733 ImGuiWindow* window = g.NavWindow;
2734 if (!window)
2735 return g.IO.MousePos;
2736 const ImRect& rect_rel = window->NavRectRel[g.NavLayer];
2737 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()));
2738 ImRect visible_rect = GetViewportRect();
2739 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.
2740 }
2741
FindWindowIndex(ImGuiWindow * window)2742 static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
2743 {
2744 ImGuiContext& g = *GImGui;
2745 for (int i = g.Windows.Size-1; i >= 0; i--)
2746 if (g.Windows[i] == window)
2747 return i;
2748 return -1;
2749 }
2750
FindWindowNavigable(int i_start,int i_stop,int dir)2751 static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
2752 {
2753 ImGuiContext& g = *GImGui;
2754 for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
2755 if (ImGui::IsWindowNavFocusable(g.Windows[i]))
2756 return g.Windows[i];
2757 return NULL;
2758 }
2759
GetNavInputAmount(ImGuiNavInput n,ImGuiInputReadMode mode)2760 float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
2761 {
2762 ImGuiContext& g = *GImGui;
2763 if (mode == ImGuiInputReadMode_Down)
2764 return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
2765
2766 const float t = g.IO.NavInputsDownDuration[n];
2767 if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input.
2768 return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
2769 if (t < 0.0f)
2770 return 0.0f;
2771 if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input.
2772 return (t == 0.0f) ? 1.0f : 0.0f;
2773 if (mode == ImGuiInputReadMode_Repeat)
2774 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
2775 if (mode == ImGuiInputReadMode_RepeatSlow)
2776 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
2777 if (mode == ImGuiInputReadMode_RepeatFast)
2778 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
2779 return 0.0f;
2780 }
2781
2782 // Equivalent of IsKeyDown() for NavInputs[]
IsNavInputDown(ImGuiNavInput n)2783 static bool IsNavInputDown(ImGuiNavInput n)
2784 {
2785 return GImGui->IO.NavInputs[n] > 0.0f;
2786 }
2787
2788 // Equivalent of IsKeyPressed() for NavInputs[]
IsNavInputPressed(ImGuiNavInput n,ImGuiInputReadMode mode)2789 static bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode)
2790 {
2791 return ImGui::GetNavInputAmount(n, mode) > 0.0f;
2792 }
2793
IsNavInputPressedAnyOfTwo(ImGuiNavInput n1,ImGuiNavInput n2,ImGuiInputReadMode mode)2794 static bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode)
2795 {
2796 return (ImGui::GetNavInputAmount(n1, mode) + ImGui::GetNavInputAmount(n2, mode)) > 0.0f;
2797 }
2798
GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources,ImGuiInputReadMode mode,float slow_factor,float fast_factor)2799 ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
2800 {
2801 ImVec2 delta(0.0f, 0.0f);
2802 if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
2803 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode));
2804 if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
2805 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode));
2806 if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
2807 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
2808 if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
2809 delta *= slow_factor;
2810 if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
2811 delta *= fast_factor;
2812 return delta;
2813 }
2814
NavUpdateWindowingHighlightWindow(int focus_change_dir)2815 static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
2816 {
2817 ImGuiContext& g = *GImGui;
2818 IM_ASSERT(g.NavWindowingTarget);
2819 if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
2820 return;
2821
2822 const int i_current = FindWindowIndex(g.NavWindowingTarget);
2823 ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
2824 if (!window_target)
2825 window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir);
2826 g.NavWindowingTarget = window_target;
2827 g.NavWindowingToggleLayer = false;
2828 }
2829
2830 // Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer)
NavUpdateWindowing()2831 static void ImGui::NavUpdateWindowing()
2832 {
2833 ImGuiContext& g = *GImGui;
2834 ImGuiWindow* apply_focus_window = NULL;
2835 bool apply_toggle_layer = false;
2836
2837 bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
2838 bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
2839 if (start_windowing_with_gamepad || start_windowing_with_keyboard)
2840 if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1))
2841 {
2842 g.NavWindowingTarget = window->RootWindowForTabbing;
2843 g.NavWindowingHighlightTimer = g.NavWindowingHighlightAlpha = 0.0f;
2844 g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
2845 g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
2846 }
2847
2848 // Gamepad update
2849 g.NavWindowingHighlightTimer += g.IO.DeltaTime;
2850 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad)
2851 {
2852 // 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
2853 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.20f) / 0.05f));
2854
2855 // Select window to focus
2856 const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
2857 if (focus_change_dir != 0)
2858 {
2859 NavUpdateWindowingHighlightWindow(focus_change_dir);
2860 g.NavWindowingHighlightAlpha = 1.0f;
2861 }
2862
2863 // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most)
2864 if (!IsNavInputDown(ImGuiNavInput_Menu))
2865 {
2866 g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
2867 if (g.NavWindowingToggleLayer && g.NavWindow)
2868 apply_toggle_layer = true;
2869 else if (!g.NavWindowingToggleLayer)
2870 apply_focus_window = g.NavWindowingTarget;
2871 g.NavWindowingTarget = NULL;
2872 }
2873 }
2874
2875 // Keyboard: Focus
2876 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard)
2877 {
2878 // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
2879 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.15f) / 0.04f)); // 1.0f
2880 if (IsKeyPressedMap(ImGuiKey_Tab, true))
2881 NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
2882 if (!g.IO.KeyCtrl)
2883 apply_focus_window = g.NavWindowingTarget;
2884 }
2885
2886 // Keyboard: Press and Release ALT to toggle menu layer
2887 // 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
2888 if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
2889 if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
2890 apply_toggle_layer = true;
2891
2892 // Move window
2893 if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
2894 {
2895 ImVec2 move_delta;
2896 if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
2897 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
2898 if (g.NavInputSource == ImGuiInputSource_NavGamepad)
2899 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
2900 if (move_delta.x != 0.0f || move_delta.y != 0.0f)
2901 {
2902 const float NAV_MOVE_SPEED = 800.0f;
2903 const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
2904 g.NavWindowingTarget->PosFloat += move_delta * move_speed;
2905 g.NavDisableMouseHover = true;
2906 MarkIniSettingsDirty(g.NavWindowingTarget);
2907 }
2908 }
2909
2910 // Apply final focus
2911 if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowForTabbing))
2912 {
2913 g.NavDisableHighlight = false;
2914 g.NavDisableMouseHover = true;
2915 apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
2916 ClosePopupsOverWindow(apply_focus_window);
2917 FocusWindow(apply_focus_window);
2918 if (apply_focus_window->NavLastIds[0] == 0)
2919 NavInitWindow(apply_focus_window, false);
2920
2921 // If the window only has a menu layer, select it directly
2922 if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1))
2923 g.NavLayer = 1;
2924 }
2925 if (apply_focus_window)
2926 g.NavWindowingTarget = NULL;
2927
2928 // Apply menu/layer toggle
2929 if (apply_toggle_layer && g.NavWindow)
2930 {
2931 ImGuiWindow* new_nav_window = g.NavWindow;
2932 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)
2933 new_nav_window = new_nav_window->ParentWindow;
2934 if (new_nav_window != g.NavWindow)
2935 {
2936 ImGuiWindow* old_nav_window = g.NavWindow;
2937 FocusWindow(new_nav_window);
2938 new_nav_window->NavLastChildNavWindow = old_nav_window;
2939 }
2940 g.NavDisableHighlight = false;
2941 g.NavDisableMouseHover = true;
2942 NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0);
2943 }
2944 }
2945
2946 // NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
NavScrollToBringItemIntoView(ImGuiWindow * window,ImRect & item_rect_rel)2947 static void NavScrollToBringItemIntoView(ImGuiWindow* window, ImRect& item_rect_rel)
2948 {
2949 // Scroll to keep newly navigated item fully into view
2950 ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1));
2951 //g.OverlayDrawList.AddRect(window->Pos + window_rect_rel.Min, window->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG]
2952 if (window_rect_rel.Contains(item_rect_rel))
2953 return;
2954
2955 ImGuiContext& g = *GImGui;
2956 if (window->ScrollbarX && item_rect_rel.Min.x < window_rect_rel.Min.x)
2957 {
2958 window->ScrollTarget.x = item_rect_rel.Min.x + window->Scroll.x - g.Style.ItemSpacing.x;
2959 window->ScrollTargetCenterRatio.x = 0.0f;
2960 }
2961 else if (window->ScrollbarX && item_rect_rel.Max.x >= window_rect_rel.Max.x)
2962 {
2963 window->ScrollTarget.x = item_rect_rel.Max.x + window->Scroll.x + g.Style.ItemSpacing.x;
2964 window->ScrollTargetCenterRatio.x = 1.0f;
2965 }
2966 if (item_rect_rel.Min.y < window_rect_rel.Min.y)
2967 {
2968 window->ScrollTarget.y = item_rect_rel.Min.y + window->Scroll.y - g.Style.ItemSpacing.y;
2969 window->ScrollTargetCenterRatio.y = 0.0f;
2970 }
2971 else if (item_rect_rel.Max.y >= window_rect_rel.Max.y)
2972 {
2973 window->ScrollTarget.y = item_rect_rel.Max.y + window->Scroll.y + g.Style.ItemSpacing.y;
2974 window->ScrollTargetCenterRatio.y = 1.0f;
2975 }
2976
2977 // Estimate upcoming scroll so we can offset our relative mouse position so mouse position can be applied immediately (under this block)
2978 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
2979 item_rect_rel.Translate(window->Scroll - next_scroll);
2980 }
2981
NavUpdate()2982 static void ImGui::NavUpdate()
2983 {
2984 ImGuiContext& g = *GImGui;
2985 g.IO.WantSetMousePos = false;
2986
2987 #if 0
2988 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);
2989 #endif
2990
2991 if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad))
2992 if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
2993 g.NavInputSource = ImGuiInputSource_NavGamepad;
2994
2995 // Update Keyboard->Nav inputs mapping
2996 if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
2997 {
2998 #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
2999 NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
3000 NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input );
3001 NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel );
3002 NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ );
3003 NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_);
3004 NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ );
3005 NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ );
3006 if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
3007 if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
3008 if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f;
3009 #undef NAV_MAP_KEY
3010 }
3011
3012 memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
3013 for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
3014 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;
3015
3016 // Process navigation init request (select first/default focus)
3017 if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove))
3018 {
3019 // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
3020 IM_ASSERT(g.NavWindow);
3021 if (g.NavInitRequestFromMove)
3022 SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel);
3023 else
3024 SetNavID(g.NavInitResultId, g.NavLayer);
3025 g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
3026 }
3027 g.NavInitRequest = false;
3028 g.NavInitRequestFromMove = false;
3029 g.NavInitResultId = 0;
3030 g.NavJustMovedToId = 0;
3031
3032 // Process navigation move request
3033 if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0))
3034 {
3035 // Select which result to use
3036 ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
3037 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
3038 if ((g.NavMoveResultOther.DistBox < g.NavMoveResultLocal.DistBox) || (g.NavMoveResultOther.DistBox == g.NavMoveResultLocal.DistBox && g.NavMoveResultOther.DistCenter < g.NavMoveResultLocal.DistCenter))
3039 result = &g.NavMoveResultOther;
3040
3041 IM_ASSERT(g.NavWindow && result->Window);
3042
3043 // Scroll to keep newly navigated item fully into view
3044 if (g.NavLayer == 0)
3045 NavScrollToBringItemIntoView(result->Window, result->RectRel);
3046
3047 // Apply result from previous frame navigation directional move request
3048 ClearActiveID();
3049 g.NavWindow = result->Window;
3050 SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel);
3051 g.NavJustMovedToId = result->ID;
3052 g.NavMoveFromClampedRefRect = false;
3053 }
3054
3055 // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
3056 if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
3057 {
3058 IM_ASSERT(g.NavMoveRequest);
3059 if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
3060 g.NavDisableHighlight = false;
3061 g.NavMoveRequestForward = ImGuiNavForward_None;
3062 }
3063
3064 // Apply application mouse position movement, after we had a chance to process move request result.
3065 if (g.NavMousePosDirty && g.NavIdIsAlive)
3066 {
3067 // Set mouse position given our knowledge of the nav widget position from last frame
3068 if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
3069 {
3070 g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredMousePos();
3071 g.IO.WantSetMousePos = true;
3072 }
3073 g.NavMousePosDirty = false;
3074 }
3075 g.NavIdIsAlive = false;
3076 g.NavJustTabbedId = 0;
3077 IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
3078
3079 // 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
3080 if (g.NavWindow)
3081 NavSaveLastChildNavWindow(g.NavWindow);
3082 if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0)
3083 g.NavWindow->NavLastChildNavWindow = NULL;
3084
3085 NavUpdateWindowing();
3086
3087 // Set output flags for user application
3088 bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
3089 bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
3090 g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
3091 g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
3092
3093 // Process NavCancel input (to close a popup, get back to parent, clear focus)
3094 if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
3095 {
3096 if (g.ActiveId != 0)
3097 {
3098 ClearActiveID();
3099 }
3100 else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
3101 {
3102 // Exit child window
3103 ImGuiWindow* child_window = g.NavWindow;
3104 ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
3105 IM_ASSERT(child_window->ChildId != 0);
3106 FocusWindow(parent_window);
3107 SetNavID(child_window->ChildId, 0);
3108 g.NavIdIsAlive = false;
3109 if (g.NavDisableMouseHover)
3110 g.NavMousePosDirty = true;
3111 }
3112 else if (g.OpenPopupStack.Size > 0)
3113 {
3114 // Close open popup/menu
3115 if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
3116 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
3117 }
3118 else if (g.NavLayer != 0)
3119 {
3120 // Leave the "menu" layer
3121 NavRestoreLayer(0);
3122 }
3123 else
3124 {
3125 // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
3126 if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
3127 g.NavWindow->NavLastIds[0] = 0;
3128 g.NavId = 0;
3129 }
3130 }
3131
3132 // Process manual activation request
3133 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
3134 if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3135 {
3136 bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
3137 bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
3138 if (g.ActiveId == 0 && activate_pressed)
3139 g.NavActivateId = g.NavId;
3140 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
3141 g.NavActivateDownId = g.NavId;
3142 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
3143 g.NavActivatePressedId = g.NavId;
3144 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
3145 g.NavInputId = g.NavId;
3146 }
3147 if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3148 g.NavDisableHighlight = true;
3149 if (g.NavActivateId != 0)
3150 IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
3151 g.NavMoveRequest = false;
3152
3153 // Process programmatic activation request
3154 if (g.NavNextActivateId != 0)
3155 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
3156 g.NavNextActivateId = 0;
3157
3158 // Initiate directional inputs request
3159 const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
3160 if (g.NavMoveRequestForward == ImGuiNavForward_None)
3161 {
3162 g.NavMoveDir = ImGuiDir_None;
3163 if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3164 {
3165 if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
3166 if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
3167 if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
3168 if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
3169 }
3170 }
3171 else
3172 {
3173 // 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)
3174 IM_ASSERT(g.NavMoveDir != ImGuiDir_None);
3175 IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
3176 g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
3177 }
3178
3179 if (g.NavMoveDir != ImGuiDir_None)
3180 {
3181 g.NavMoveRequest = true;
3182 g.NavMoveDirLast = g.NavMoveDir;
3183 }
3184
3185 // 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
3186 if (g.NavMoveRequest && g.NavId == 0)
3187 {
3188 g.NavInitRequest = g.NavInitRequestFromMove = true;
3189 g.NavInitResultId = 0;
3190 g.NavDisableHighlight = false;
3191 }
3192
3193 NavUpdateAnyRequestFlag();
3194
3195 // Scrolling
3196 if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
3197 {
3198 // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item
3199 ImGuiWindow* window = g.NavWindow;
3200 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.
3201 if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
3202 {
3203 if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
3204 SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
3205 if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
3206 SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
3207 }
3208
3209 // *Normal* Manual scroll with NavScrollXXX keys
3210 // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
3211 ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f);
3212 if (scroll_dir.x != 0.0f && window->ScrollbarX)
3213 {
3214 SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
3215 g.NavMoveFromClampedRefRect = true;
3216 }
3217 if (scroll_dir.y != 0.0f)
3218 {
3219 SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
3220 g.NavMoveFromClampedRefRect = true;
3221 }
3222 }
3223
3224 // Reset search results
3225 g.NavMoveResultLocal.Clear();
3226 g.NavMoveResultOther.Clear();
3227
3228 // 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
3229 if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
3230 {
3231 ImGuiWindow* window = g.NavWindow;
3232 ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1,1), window->InnerRect.Max - window->Pos + ImVec2(1,1));
3233 if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
3234 {
3235 float pad = 0.0f;
3236 if (window) pad = window->CalcFontSize() * 0.5f;
3237 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
3238 window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
3239 g.NavId = 0;
3240 }
3241 g.NavMoveFromClampedRefRect = false;
3242 }
3243
3244 // 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)
3245 ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
3246 g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect();
3247 g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
3248 g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
3249 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().
3250 //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
3251 g.NavScoringCount = 0;
3252 #if IMGUI_DEBUG_NAV_RECTS
3253 if (g.NavWindow) { 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)); } // [DEBUG]
3254 if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredMousePos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }
3255 #endif
3256 }
3257
NewFrameUpdateMovingWindow()3258 static void ImGui::NewFrameUpdateMovingWindow()
3259 {
3260 ImGuiContext& g = *GImGui;
3261 if (g.MovingWindow && g.MovingWindow->MoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse)
3262 {
3263 // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
3264 // We track it to preserve Focus and so that ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
3265 KeepAliveID(g.ActiveId);
3266 IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
3267 ImGuiWindow* moving_window = g.MovingWindow->RootWindow;
3268 if (g.IO.MouseDown[0])
3269 {
3270 ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
3271 if (moving_window->PosFloat.x != pos.x || moving_window->PosFloat.y != pos.y)
3272 {
3273 MarkIniSettingsDirty(moving_window);
3274 moving_window->PosFloat = pos;
3275 }
3276 FocusWindow(g.MovingWindow);
3277 }
3278 else
3279 {
3280 ClearActiveID();
3281 g.MovingWindow = NULL;
3282 }
3283 }
3284 else
3285 {
3286 // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.
3287 if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
3288 {
3289 KeepAliveID(g.ActiveId);
3290 if (!g.IO.MouseDown[0])
3291 ClearActiveID();
3292 }
3293 g.MovingWindow = NULL;
3294 }
3295 }
3296
NewFrameUpdateMouseInputs()3297 static void ImGui::NewFrameUpdateMouseInputs()
3298 {
3299 ImGuiContext& g = *GImGui;
3300
3301 // 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
3302 if (ImGui::IsMousePosValid(&g.IO.MousePos) && ImGui::IsMousePosValid(&g.IO.MousePosPrev))
3303 g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
3304 else
3305 g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
3306 if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
3307 g.NavDisableMouseHover = false;
3308
3309 g.IO.MousePosPrev = g.IO.MousePos;
3310 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3311 {
3312 g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
3313 g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
3314 g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
3315 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;
3316 g.IO.MouseDoubleClicked[i] = false;
3317 if (g.IO.MouseClicked[i])
3318 {
3319 if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime)
3320 {
3321 if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
3322 g.IO.MouseDoubleClicked[i] = true;
3323 g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click
3324 }
3325 else
3326 {
3327 g.IO.MouseClickedTime[i] = g.Time;
3328 }
3329 g.IO.MouseClickedPos[i] = g.IO.MousePos;
3330 g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
3331 g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
3332 }
3333 else if (g.IO.MouseDown[i])
3334 {
3335 ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i];
3336 g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x);
3337 g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y);
3338 g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta));
3339 }
3340 if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
3341 g.NavDisableMouseHover = false;
3342 }
3343 }
3344
3345 // The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app)
NewFrameUpdateHoveredWindowAndCaptureFlags()3346 void ImGui::NewFrameUpdateHoveredWindowAndCaptureFlags()
3347 {
3348 ImGuiContext& g = *GImGui;
3349
3350 // Find the window hovered by mouse:
3351 // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
3352 // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame.
3353 // - 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.
3354 g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow();
3355 g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
3356
3357 // Modal windows prevents cursor from hovering behind them.
3358 ImGuiWindow* modal_window = GetFrontMostPopupModal();
3359 if (modal_window)
3360 if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window))
3361 g.HoveredRootWindow = g.HoveredWindow = NULL;
3362
3363 // Disabled mouse?
3364 if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse)
3365 g.HoveredWindow = g.HoveredRootWindow = NULL;
3366
3367 // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward.
3368 int mouse_earliest_button_down = -1;
3369 bool mouse_any_down = false;
3370 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3371 {
3372 if (g.IO.MouseClicked[i])
3373 g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty());
3374 mouse_any_down |= g.IO.MouseDown[i];
3375 if (g.IO.MouseDown[i])
3376 if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down])
3377 mouse_earliest_button_down = i;
3378 }
3379 const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
3380
3381 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
3382 // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)
3383 const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
3384 if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload)
3385 g.HoveredWindow = g.HoveredRootWindow = NULL;
3386
3387 // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to imgui + app)
3388 if (g.WantCaptureMouseNextFrame != -1)
3389 g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0);
3390 else
3391 g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty());
3392
3393 // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to imgui + app)
3394 if (g.WantCaptureKeyboardNextFrame != -1)
3395 g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
3396 else
3397 g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
3398 if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard))
3399 g.IO.WantCaptureKeyboard = true;
3400
3401 // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible
3402 g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0;
3403 }
3404
NewFrame()3405 void ImGui::NewFrame()
3406 {
3407 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
3408 ImGuiContext& g = *GImGui;
3409
3410 // Check user data
3411 // (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)
3412 IM_ASSERT(g.Initialized);
3413 IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)");
3414 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value");
3415 IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
3416 IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
3417 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting");
3418 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)");
3419 IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?");
3420 for (int n = 0; n < ImGuiKey_COUNT; n++)
3421 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)");
3422
3423 // 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)
3424 if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
3425 IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
3426
3427 // Load settings on first frame
3428 if (!g.SettingsLoaded)
3429 {
3430 IM_ASSERT(g.SettingsWindows.empty());
3431 LoadIniSettingsFromDisk(g.IO.IniFilename);
3432 g.SettingsLoaded = true;
3433 }
3434
3435 // Save settings (with a delay so we don't spam disk too much)
3436 if (g.SettingsDirtyTimer > 0.0f)
3437 {
3438 g.SettingsDirtyTimer -= g.IO.DeltaTime;
3439 if (g.SettingsDirtyTimer <= 0.0f)
3440 SaveIniSettingsToDisk(g.IO.IniFilename);
3441 }
3442
3443 g.Time += g.IO.DeltaTime;
3444 g.FrameCount += 1;
3445 g.TooltipOverrideCount = 0;
3446 g.WindowsActiveCount = 0;
3447
3448 SetCurrentFont(GetDefaultFont());
3449 IM_ASSERT(g.Font->IsLoaded());
3450 g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
3451 g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
3452
3453 g.OverlayDrawList.Clear();
3454 g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID);
3455 g.OverlayDrawList.PushClipRectFullScreen();
3456 g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
3457
3458 // Mark rendering data as invalid to prevent user who may have a handle on it to use it
3459 g.DrawData.Clear();
3460
3461 // Clear reference to active widget if the widget isn't alive anymore
3462 if (!g.HoveredIdPreviousFrame)
3463 g.HoveredIdTimer = 0.0f;
3464 g.HoveredIdPreviousFrame = g.HoveredId;
3465 g.HoveredId = 0;
3466 g.HoveredIdAllowOverlap = false;
3467 if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
3468 ClearActiveID();
3469 if (g.ActiveId)
3470 g.ActiveIdTimer += g.IO.DeltaTime;
3471 g.ActiveIdPreviousFrame = g.ActiveId;
3472 g.ActiveIdIsAlive = false;
3473 g.ActiveIdIsJustActivated = false;
3474 if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId)
3475 g.ScalarAsInputTextId = 0;
3476
3477 // Elapse drag & drop payload
3478 if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount)
3479 {
3480 ClearDragDrop();
3481 g.DragDropPayloadBufHeap.clear();
3482 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
3483 }
3484 g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
3485 g.DragDropAcceptIdCurr = 0;
3486 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
3487
3488 // Update keyboard input state
3489 memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
3490 for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
3491 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;
3492
3493 // Update gamepad/keyboard directional navigation
3494 NavUpdate();
3495
3496 // Update mouse input state
3497 NewFrameUpdateMouseInputs();
3498
3499 // Calculate frame-rate for the user, as a purely luxurious feature
3500 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
3501 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
3502 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
3503 g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX;
3504
3505 // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
3506 NewFrameUpdateMovingWindow();
3507 NewFrameUpdateHoveredWindowAndCaptureFlags();
3508
3509 if (GetFrontMostPopupModal() != NULL)
3510 g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f);
3511 else
3512 g.ModalWindowDarkeningRatio = 0.0f;
3513
3514 g.MouseCursor = ImGuiMouseCursor_Arrow;
3515 g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
3516 g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default
3517
3518 // Mouse wheel scrolling, scale
3519 if (g.HoveredWindow && !g.HoveredWindow->Collapsed && (g.IO.MouseWheel != 0.0f || g.IO.MouseWheelH != 0.0f))
3520 {
3521 // 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).
3522 ImGuiWindow* window = g.HoveredWindow;
3523 ImGuiWindow* scroll_window = window;
3524 while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow)
3525 scroll_window = scroll_window->ParentWindow;
3526 const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs);
3527
3528 if (g.IO.MouseWheel != 0.0f)
3529 {
3530 if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
3531 {
3532 // Zoom / Scale window
3533 const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
3534 const float scale = new_font_scale / window->FontWindowScale;
3535 window->FontWindowScale = new_font_scale;
3536
3537 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
3538 window->Pos += offset;
3539 window->PosFloat += offset;
3540 window->Size *= scale;
3541 window->SizeFull *= scale;
3542 }
3543 else if (!g.IO.KeyCtrl && scroll_allowed)
3544 {
3545 // Mouse wheel vertical scrolling
3546 float scroll_amount = 5 * scroll_window->CalcFontSize();
3547 scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f);
3548 SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount);
3549 }
3550 }
3551 if (g.IO.MouseWheelH != 0.0f && scroll_allowed)
3552 {
3553 // Mouse wheel horizontal scrolling (for hardware that supports it)
3554 float scroll_amount = scroll_window->CalcFontSize();
3555 if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse))
3556 SetWindowScrollX(window, window->Scroll.x - g.IO.MouseWheelH * scroll_amount);
3557 }
3558 }
3559
3560 // Pressing TAB activate widget focus
3561 if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false))
3562 {
3563 if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
3564 g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1);
3565 else
3566 g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0;
3567 }
3568 g.NavIdTabCounter = INT_MAX;
3569
3570 // Mark all windows as not visible
3571 for (int i = 0; i != g.Windows.Size; i++)
3572 {
3573 ImGuiWindow* window = g.Windows[i];
3574 window->WasActive = window->Active;
3575 window->Active = false;
3576 window->WriteAccessed = false;
3577 }
3578
3579 // Closing the focused window restore focus to the first active root window in descending z-order
3580 if (g.NavWindow && !g.NavWindow->WasActive)
3581 FocusFrontMostActiveWindow(NULL);
3582
3583 // No window should be open at the beginning of the frame.
3584 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
3585 g.CurrentWindowStack.resize(0);
3586 g.CurrentPopupStack.resize(0);
3587 ClosePopupsOverWindow(g.NavWindow);
3588
3589 // Create implicit window - we will only render it if the user has added something to it.
3590 // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
3591 SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver);
3592 Begin("Debug##Default");
3593 }
3594
SettingsHandlerWindow_ReadOpen(ImGuiContext *,ImGuiSettingsHandler *,const char * name)3595 static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
3596 {
3597 ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0));
3598 if (!settings)
3599 settings = AddWindowSettings(name);
3600 return (void*)settings;
3601 }
3602
SettingsHandlerWindow_ReadLine(ImGuiContext *,ImGuiSettingsHandler *,void * entry,const char * line)3603 static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
3604 {
3605 ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
3606 float x, y;
3607 int i;
3608 if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y);
3609 else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize);
3610 else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0);
3611 }
3612
SettingsHandlerWindow_WriteAll(ImGuiContext * imgui_ctx,ImGuiSettingsHandler * handler,ImGuiTextBuffer * buf)3613 static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
3614 {
3615 // Gather data from windows that were active during this session
3616 ImGuiContext& g = *imgui_ctx;
3617 for (int i = 0; i != g.Windows.Size; i++)
3618 {
3619 ImGuiWindow* window = g.Windows[i];
3620 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
3621 continue;
3622 ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID);
3623 if (!settings)
3624 settings = AddWindowSettings(window->Name);
3625 settings->Pos = window->Pos;
3626 settings->Size = window->SizeFull;
3627 settings->Collapsed = window->Collapsed;
3628 }
3629
3630 // Write a buffer
3631 // If a window wasn't opened in this session we preserve its settings
3632 buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve
3633 for (int i = 0; i != g.SettingsWindows.Size; i++)
3634 {
3635 const ImGuiWindowSettings* settings = &g.SettingsWindows[i];
3636 if (settings->Pos.x == FLT_MAX)
3637 continue;
3638 const char* name = settings->Name;
3639 if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
3640 name = p;
3641 buf->appendf("[%s][%s]\n", handler->TypeName, name);
3642 buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
3643 buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
3644 buf->appendf("Collapsed=%d\n", settings->Collapsed);
3645 buf->appendf("\n");
3646 }
3647 }
3648
Initialize(ImGuiContext * context)3649 void ImGui::Initialize(ImGuiContext* context)
3650 {
3651 ImGuiContext& g = *context;
3652 IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
3653 g.LogClipboard = IM_NEW(ImGuiTextBuffer)();
3654
3655 // Add .ini handle for ImGuiWindow type
3656 ImGuiSettingsHandler ini_handler;
3657 ini_handler.TypeName = "Window";
3658 ini_handler.TypeHash = ImHash("Window", 0, 0);
3659 ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen;
3660 ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine;
3661 ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll;
3662 g.SettingsHandlers.push_front(ini_handler);
3663
3664 g.Initialized = true;
3665 }
3666
3667 // This function is merely here to free heap allocations.
Shutdown(ImGuiContext * context)3668 void ImGui::Shutdown(ImGuiContext* context)
3669 {
3670 ImGuiContext& g = *context;
3671
3672 // 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)
3673 if (g.IO.Fonts && g.FontAtlasOwnedByContext)
3674 IM_DELETE(g.IO.Fonts);
3675 g.IO.Fonts = NULL;
3676
3677 // Cleanup of other data are conditional on actually having initialize ImGui.
3678 if (!g.Initialized)
3679 return;
3680
3681 // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file)
3682 if (g.SettingsLoaded)
3683 SaveIniSettingsToDisk(g.IO.IniFilename);
3684
3685 // Clear everything else
3686 for (int i = 0; i < g.Windows.Size; i++)
3687 IM_DELETE(g.Windows[i]);
3688 g.Windows.clear();
3689 g.WindowsSortBuffer.clear();
3690 g.CurrentWindow = NULL;
3691 g.CurrentWindowStack.clear();
3692 g.WindowsById.Clear();
3693 g.NavWindow = NULL;
3694 g.HoveredWindow = NULL;
3695 g.HoveredRootWindow = NULL;
3696 g.ActiveIdWindow = NULL;
3697 g.MovingWindow = NULL;
3698 g.ColorModifiers.clear();
3699 g.StyleModifiers.clear();
3700 g.FontStack.clear();
3701 g.OpenPopupStack.clear();
3702 g.CurrentPopupStack.clear();
3703 g.DrawDataBuilder.ClearFreeMemory();
3704 g.OverlayDrawList.ClearFreeMemory();
3705 g.PrivateClipboard.clear();
3706 g.InputTextState.Text.clear();
3707 g.InputTextState.InitialText.clear();
3708 g.InputTextState.TempTextBuffer.clear();
3709
3710 for (int i = 0; i < g.SettingsWindows.Size; i++)
3711 IM_DELETE(g.SettingsWindows[i].Name);
3712 g.SettingsWindows.clear();
3713 g.SettingsHandlers.clear();
3714
3715 if (g.LogFile && g.LogFile != stdout)
3716 {
3717 fclose(g.LogFile);
3718 g.LogFile = NULL;
3719 }
3720 if (g.LogClipboard)
3721 IM_DELETE(g.LogClipboard);
3722 g.LogClipboard = NULL;
3723
3724 g.Initialized = false;
3725 }
3726
FindWindowSettings(ImGuiID id)3727 ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
3728 {
3729 ImGuiContext& g = *GImGui;
3730 for (int i = 0; i != g.SettingsWindows.Size; i++)
3731 if (g.SettingsWindows[i].Id == id)
3732 return &g.SettingsWindows[i];
3733 return NULL;
3734 }
3735
AddWindowSettings(const char * name)3736 static ImGuiWindowSettings* AddWindowSettings(const char* name)
3737 {
3738 ImGuiContext& g = *GImGui;
3739 g.SettingsWindows.push_back(ImGuiWindowSettings());
3740 ImGuiWindowSettings* settings = &g.SettingsWindows.back();
3741 settings->Name = ImStrdup(name);
3742 settings->Id = ImHash(name, 0);
3743 return settings;
3744 }
3745
LoadIniSettingsFromDisk(const char * ini_filename)3746 static void LoadIniSettingsFromDisk(const char* ini_filename)
3747 {
3748 if (!ini_filename)
3749 return;
3750 char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1);
3751 if (!file_data)
3752 return;
3753 LoadIniSettingsFromMemory(file_data);
3754 ImGui::MemFree(file_data);
3755 }
3756
FindSettingsHandler(const char * type_name)3757 ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
3758 {
3759 ImGuiContext& g = *GImGui;
3760 const ImGuiID type_hash = ImHash(type_name, 0, 0);
3761 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
3762 if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
3763 return &g.SettingsHandlers[handler_n];
3764 return NULL;
3765 }
3766
3767 // Zero-tolerance, no error reporting, cheap .ini parsing
LoadIniSettingsFromMemory(const char * buf_readonly)3768 static void LoadIniSettingsFromMemory(const char* buf_readonly)
3769 {
3770 // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy.
3771 char* buf = ImStrdup(buf_readonly);
3772 char* buf_end = buf + strlen(buf);
3773
3774 ImGuiContext& g = *GImGui;
3775 void* entry_data = NULL;
3776 ImGuiSettingsHandler* entry_handler = NULL;
3777
3778 char* line_end = NULL;
3779 for (char* line = buf; line < buf_end; line = line_end + 1)
3780 {
3781 // Skip new lines markers, then find end of the line
3782 while (*line == '\n' || *line == '\r')
3783 line++;
3784 line_end = line;
3785 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
3786 line_end++;
3787 line_end[0] = 0;
3788
3789 if (line[0] == '[' && line_end > line && line_end[-1] == ']')
3790 {
3791 // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
3792 line_end[-1] = 0;
3793 const char* name_end = line_end - 1;
3794 const char* type_start = line + 1;
3795 char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']');
3796 const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
3797 if (!type_end || !name_start)
3798 {
3799 name_start = type_start; // Import legacy entries that have no type
3800 type_start = "Window";
3801 }
3802 else
3803 {
3804 *type_end = 0; // Overwrite first ']'
3805 name_start++; // Skip second '['
3806 }
3807 entry_handler = ImGui::FindSettingsHandler(type_start);
3808 entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
3809 }
3810 else if (entry_handler != NULL && entry_data != NULL)
3811 {
3812 // Let type handler parse the line
3813 entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
3814 }
3815 }
3816 ImGui::MemFree(buf);
3817 g.SettingsLoaded = true;
3818 }
3819
SaveIniSettingsToDisk(const char * ini_filename)3820 static void SaveIniSettingsToDisk(const char* ini_filename)
3821 {
3822 ImGuiContext& g = *GImGui;
3823 g.SettingsDirtyTimer = 0.0f;
3824 if (!ini_filename)
3825 return;
3826
3827 ImVector<char> buf;
3828 SaveIniSettingsToMemory(buf);
3829
3830 FILE* f = ImFileOpen(ini_filename, "wt");
3831 if (!f)
3832 return;
3833 fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f);
3834 fclose(f);
3835 }
3836
SaveIniSettingsToMemory(ImVector<char> & out_buf)3837 static void SaveIniSettingsToMemory(ImVector<char>& out_buf)
3838 {
3839 ImGuiContext& g = *GImGui;
3840 g.SettingsDirtyTimer = 0.0f;
3841
3842 ImGuiTextBuffer buf;
3843 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
3844 {
3845 ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
3846 handler->WriteAllFn(&g, handler, &buf);
3847 }
3848
3849 buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer
3850 out_buf.swap(buf.Buf);
3851 }
3852
MarkIniSettingsDirty()3853 void ImGui::MarkIniSettingsDirty()
3854 {
3855 ImGuiContext& g = *GImGui;
3856 if (g.SettingsDirtyTimer <= 0.0f)
3857 g.SettingsDirtyTimer = g.IO.IniSavingRate;
3858 }
3859
MarkIniSettingsDirty(ImGuiWindow * window)3860 static void MarkIniSettingsDirty(ImGuiWindow* window)
3861 {
3862 ImGuiContext& g = *GImGui;
3863 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
3864 if (g.SettingsDirtyTimer <= 0.0f)
3865 g.SettingsDirtyTimer = g.IO.IniSavingRate;
3866 }
3867
3868 // FIXME: Add a more explicit sort order in the window structure.
ChildWindowComparer(const void * lhs,const void * rhs)3869 static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
3870 {
3871 const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs;
3872 const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs;
3873 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
3874 return d;
3875 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
3876 return d;
3877 return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
3878 }
3879
AddWindowToSortedBuffer(ImVector<ImGuiWindow * > * out_sorted_windows,ImGuiWindow * window)3880 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
3881 {
3882 out_sorted_windows->push_back(window);
3883 if (window->Active)
3884 {
3885 int count = window->DC.ChildWindows.Size;
3886 if (count > 1)
3887 qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
3888 for (int i = 0; i < count; i++)
3889 {
3890 ImGuiWindow* child = window->DC.ChildWindows[i];
3891 if (child->Active)
3892 AddWindowToSortedBuffer(out_sorted_windows, child);
3893 }
3894 }
3895 }
3896
AddDrawListToDrawData(ImVector<ImDrawList * > * out_render_list,ImDrawList * draw_list)3897 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_render_list, ImDrawList* draw_list)
3898 {
3899 if (draw_list->CmdBuffer.empty())
3900 return;
3901
3902 // Remove trailing command if unused
3903 ImDrawCmd& last_cmd = draw_list->CmdBuffer.back();
3904 if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL)
3905 {
3906 draw_list->CmdBuffer.pop_back();
3907 if (draw_list->CmdBuffer.empty())
3908 return;
3909 }
3910
3911 // 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.
3912 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
3913 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
3914 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
3915
3916 // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)
3917 // If this assert triggers because you are drawing lots of stuff manually:
3918 // 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.
3919 // 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.
3920 // 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:
3921 // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
3922 // 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.
3923 // 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.
3924 if (sizeof(ImDrawIdx) == 2)
3925 IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above");
3926
3927 out_render_list->push_back(draw_list);
3928 }
3929
AddWindowToDrawData(ImVector<ImDrawList * > * out_render_list,ImGuiWindow * window)3930 static void AddWindowToDrawData(ImVector<ImDrawList*>* out_render_list, ImGuiWindow* window)
3931 {
3932 AddDrawListToDrawData(out_render_list, window->DrawList);
3933 for (int i = 0; i < window->DC.ChildWindows.Size; i++)
3934 {
3935 ImGuiWindow* child = window->DC.ChildWindows[i];
3936 if (child->Active && child->HiddenFrames == 0) // clipped children may have been marked not active
3937 AddWindowToDrawData(out_render_list, child);
3938 }
3939 }
3940
AddWindowToDrawDataSelectLayer(ImGuiWindow * window)3941 static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window)
3942 {
3943 ImGuiContext& g = *GImGui;
3944 g.IO.MetricsActiveWindows++;
3945 if (window->Flags & ImGuiWindowFlags_Tooltip)
3946 AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window);
3947 else
3948 AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window);
3949 }
3950
FlattenIntoSingleLayer()3951 void ImDrawDataBuilder::FlattenIntoSingleLayer()
3952 {
3953 int n = Layers[0].Size;
3954 int size = n;
3955 for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
3956 size += Layers[i].Size;
3957 Layers[0].resize(size);
3958 for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
3959 {
3960 ImVector<ImDrawList*>& layer = Layers[layer_n];
3961 if (layer.empty())
3962 continue;
3963 memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
3964 n += layer.Size;
3965 layer.resize(0);
3966 }
3967 }
3968
SetupDrawData(ImVector<ImDrawList * > * draw_lists,ImDrawData * out_draw_data)3969 static void SetupDrawData(ImVector<ImDrawList*>* draw_lists, ImDrawData* out_draw_data)
3970 {
3971 out_draw_data->Valid = true;
3972 out_draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL;
3973 out_draw_data->CmdListsCount = draw_lists->Size;
3974 out_draw_data->TotalVtxCount = out_draw_data->TotalIdxCount = 0;
3975 for (int n = 0; n < draw_lists->Size; n++)
3976 {
3977 out_draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size;
3978 out_draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size;
3979 }
3980 }
3981
3982 // 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)3983 void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)
3984 {
3985 ImGuiWindow* window = GetCurrentWindow();
3986 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
3987 window->ClipRect = window->DrawList->_ClipRectStack.back();
3988 }
3989
PopClipRect()3990 void ImGui::PopClipRect()
3991 {
3992 ImGuiWindow* window = GetCurrentWindow();
3993 window->DrawList->PopClipRect();
3994 window->ClipRect = window->DrawList->_ClipRectStack.back();
3995 }
3996
3997 // 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()3998 void ImGui::EndFrame()
3999 {
4000 ImGuiContext& g = *GImGui;
4001 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
4002 if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times.
4003 return;
4004
4005 // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
4006 if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f)
4007 {
4008 g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y);
4009 g.OsImePosSet = g.OsImePosRequest;
4010 }
4011
4012 // Hide implicit "Debug" window if it hasn't been used
4013 IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls
4014 if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
4015 g.CurrentWindow->Active = false;
4016 End();
4017
4018 if (g.ActiveId == 0 && g.HoveredId == 0)
4019 {
4020 if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear
4021 {
4022 // Click to focus window and start moving (after we're done with all our widgets)
4023 if (g.IO.MouseClicked[0])
4024 {
4025 if (g.HoveredRootWindow != NULL)
4026 {
4027 // Set ActiveId even if the _NoMove flag is set, without it dragging away from a window with _NoMove would activate hover on other windows.
4028 FocusWindow(g.HoveredWindow);
4029 SetActiveID(g.HoveredWindow->MoveId, g.HoveredWindow);
4030 g.NavDisableHighlight = true;
4031 g.ActiveIdClickOffset = g.IO.MousePos - g.HoveredRootWindow->Pos;
4032 if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove))
4033 g.MovingWindow = g.HoveredWindow;
4034 }
4035 else if (g.NavWindow != NULL && GetFrontMostPopupModal() == NULL)
4036 {
4037 // Clicking on void disable focus
4038 FocusWindow(NULL);
4039 }
4040 }
4041
4042 // With right mouse button we close popups without changing focus
4043 // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger)
4044 if (g.IO.MouseClicked[1])
4045 {
4046 // Find the top-most window between HoveredWindow and the front most Modal Window.
4047 // This is where we can trim the popup stack.
4048 ImGuiWindow* modal = GetFrontMostPopupModal();
4049 bool hovered_window_above_modal = false;
4050 if (modal == NULL)
4051 hovered_window_above_modal = true;
4052 for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--)
4053 {
4054 ImGuiWindow* window = g.Windows[i];
4055 if (window == modal)
4056 break;
4057 if (window == g.HoveredWindow)
4058 hovered_window_above_modal = true;
4059 }
4060 ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal);
4061 }
4062 }
4063 }
4064
4065 // Sort the window list so that all child windows are after their parent
4066 // We cannot do that on FocusWindow() because childs may not exist yet
4067 g.WindowsSortBuffer.resize(0);
4068 g.WindowsSortBuffer.reserve(g.Windows.Size);
4069 for (int i = 0; i != g.Windows.Size; i++)
4070 {
4071 ImGuiWindow* window = g.Windows[i];
4072 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
4073 continue;
4074 AddWindowToSortedBuffer(&g.WindowsSortBuffer, window);
4075 }
4076
4077 IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong
4078 g.Windows.swap(g.WindowsSortBuffer);
4079
4080 // Clear Input data for next frame
4081 g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
4082 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
4083 memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs));
4084
4085 g.FrameCountEnded = g.FrameCount;
4086 }
4087
Render()4088 void ImGui::Render()
4089 {
4090 ImGuiContext& g = *GImGui;
4091 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
4092
4093 if (g.FrameCountEnded != g.FrameCount)
4094 ImGui::EndFrame();
4095 g.FrameCountRendered = g.FrameCount;
4096
4097 // Gather windows to render
4098 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0;
4099 g.DrawDataBuilder.Clear();
4100 ImGuiWindow* window_to_render_front_most = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget : NULL;
4101 for (int n = 0; n != g.Windows.Size; n++)
4102 {
4103 ImGuiWindow* window = g.Windows[n];
4104 if (window->Active && window->HiddenFrames == 0 && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != window_to_render_front_most)
4105 AddWindowToDrawDataSelectLayer(window);
4106 }
4107 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
4108 AddWindowToDrawDataSelectLayer(window_to_render_front_most);
4109 g.DrawDataBuilder.FlattenIntoSingleLayer();
4110
4111 // Draw software mouse cursor if requested
4112 ImVec2 offset, size, uv[4];
4113 if (g.IO.MouseDrawCursor && g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &offset, &size, &uv[0], &uv[2]))
4114 {
4115 const ImVec2 pos = g.IO.MousePos - offset;
4116 const ImTextureID tex_id = g.IO.Fonts->TexID;
4117 const float sc = g.Style.MouseCursorScale;
4118 g.OverlayDrawList.PushTextureID(tex_id);
4119 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
4120 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
4121 g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[2], uv[3], IM_COL32(0,0,0,255)); // Black border
4122 g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[0], uv[1], IM_COL32(255,255,255,255)); // White fill
4123 g.OverlayDrawList.PopTextureID();
4124 }
4125 if (!g.OverlayDrawList.VtxBuffer.empty())
4126 AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList);
4127
4128 // Setup ImDrawData structure for end-user
4129 SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData);
4130 g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount;
4131 g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount;
4132
4133 // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData()
4134 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
4135 if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL)
4136 g.IO.RenderDrawListsFn(&g.DrawData);
4137 #endif
4138 }
4139
FindRenderedTextEnd(const char * text,const char * text_end)4140 const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
4141 {
4142 const char* text_display_end = text;
4143 if (!text_end)
4144 text_end = (const char*)-1;
4145
4146 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
4147 text_display_end++;
4148 return text_display_end;
4149 }
4150
4151 // Pass text data straight to log (without being displayed)
LogText(const char * fmt,...)4152 void ImGui::LogText(const char* fmt, ...)
4153 {
4154 ImGuiContext& g = *GImGui;
4155 if (!g.LogEnabled)
4156 return;
4157
4158 va_list args;
4159 va_start(args, fmt);
4160 if (g.LogFile)
4161 {
4162 vfprintf(g.LogFile, fmt, args);
4163 }
4164 else
4165 {
4166 g.LogClipboard->appendfv(fmt, args);
4167 }
4168 va_end(args);
4169 }
4170
4171 // Internal version that takes a position to decide on newline placement and pad items according to their depth.
4172 // 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)4173 static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL)
4174 {
4175 ImGuiContext& g = *GImGui;
4176 ImGuiWindow* window = g.CurrentWindow;
4177
4178 if (!text_end)
4179 text_end = ImGui::FindRenderedTextEnd(text, text_end);
4180
4181 const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1);
4182 if (ref_pos)
4183 window->DC.LogLinePosY = ref_pos->y;
4184
4185 const char* text_remaining = text;
4186 if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
4187 g.LogStartDepth = window->DC.TreeDepth;
4188 const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
4189 for (;;)
4190 {
4191 // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
4192 const char* line_end = text_remaining;
4193 while (line_end < text_end)
4194 if (*line_end == '\n')
4195 break;
4196 else
4197 line_end++;
4198 if (line_end >= text_end)
4199 line_end = NULL;
4200
4201 const bool is_first_line = (text == text_remaining);
4202 bool is_last_line = false;
4203 if (line_end == NULL)
4204 {
4205 is_last_line = true;
4206 line_end = text_end;
4207 }
4208 if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0))
4209 {
4210 const int char_count = (int)(line_end - text_remaining);
4211 if (log_new_line || !is_first_line)
4212 ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining);
4213 else
4214 ImGui::LogText(" %.*s", char_count, text_remaining);
4215 }
4216
4217 if (is_last_line)
4218 break;
4219 text_remaining = line_end + 1;
4220 }
4221 }
4222
4223 // Internal ImGui functions to render text
4224 // RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
RenderText(ImVec2 pos,const char * text,const char * text_end,bool hide_text_after_hash)4225 void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
4226 {
4227 ImGuiContext& g = *GImGui;
4228 ImGuiWindow* window = g.CurrentWindow;
4229
4230 // Hide anything after a '##' string
4231 const char* text_display_end;
4232 if (hide_text_after_hash)
4233 {
4234 text_display_end = FindRenderedTextEnd(text, text_end);
4235 }
4236 else
4237 {
4238 if (!text_end)
4239 text_end = text + strlen(text); // FIXME-OPT
4240 text_display_end = text_end;
4241 }
4242
4243 const int text_len = (int)(text_display_end - text);
4244 if (text_len > 0)
4245 {
4246 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
4247 if (g.LogEnabled)
4248 LogRenderedText(&pos, text, text_display_end);
4249 }
4250 }
4251
RenderTextWrapped(ImVec2 pos,const char * text,const char * text_end,float wrap_width)4252 void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
4253 {
4254 ImGuiContext& g = *GImGui;
4255 ImGuiWindow* window = g.CurrentWindow;
4256
4257 if (!text_end)
4258 text_end = text + strlen(text); // FIXME-OPT
4259
4260 const int text_len = (int)(text_end - text);
4261 if (text_len > 0)
4262 {
4263 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
4264 if (g.LogEnabled)
4265 LogRenderedText(&pos, text, text_end);
4266 }
4267 }
4268
4269 // Default clip_rect uses (pos_min,pos_max)
4270 // 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)4271 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)
4272 {
4273 // Hide anything after a '##' string
4274 const char* text_display_end = FindRenderedTextEnd(text, text_end);
4275 const int text_len = (int)(text_display_end - text);
4276 if (text_len == 0)
4277 return;
4278
4279 ImGuiContext& g = *GImGui;
4280 ImGuiWindow* window = g.CurrentWindow;
4281
4282 // Perform CPU side clipping for single clipped element to avoid using scissor state
4283 ImVec2 pos = pos_min;
4284 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
4285
4286 const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
4287 const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
4288 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
4289 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
4290 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
4291
4292 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
4293 if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
4294 if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
4295
4296 // Render
4297 if (need_clipping)
4298 {
4299 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
4300 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
4301 }
4302 else
4303 {
4304 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
4305 }
4306 if (g.LogEnabled)
4307 LogRenderedText(&pos, text, text_display_end);
4308 }
4309
4310 // Render a rectangle shaped with optional rounding and borders
RenderFrame(ImVec2 p_min,ImVec2 p_max,ImU32 fill_col,bool border,float rounding)4311 void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
4312 {
4313 ImGuiContext& g = *GImGui;
4314 ImGuiWindow* window = g.CurrentWindow;
4315 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
4316 const float border_size = g.Style.FrameBorderSize;
4317 if (border && border_size > 0.0f)
4318 {
4319 window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
4320 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
4321 }
4322 }
4323
RenderFrameBorder(ImVec2 p_min,ImVec2 p_max,float rounding)4324 void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
4325 {
4326 ImGuiContext& g = *GImGui;
4327 ImGuiWindow* window = g.CurrentWindow;
4328 const float border_size = g.Style.FrameBorderSize;
4329 if (border_size > 0.0f)
4330 {
4331 window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
4332 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
4333 }
4334 }
4335
4336 // Render a triangle to denote expanded/collapsed state
RenderArrow(ImVec2 p_min,ImGuiDir dir,float scale)4337 void ImGui::RenderArrow(ImVec2 p_min, ImGuiDir dir, float scale)
4338 {
4339 ImGuiContext& g = *GImGui;
4340 ImGuiWindow* window = g.CurrentWindow;
4341
4342 const float h = g.FontSize * 1.00f;
4343 float r = h * 0.40f * scale;
4344 ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale);
4345
4346 ImVec2 a, b, c;
4347 switch (dir)
4348 {
4349 case ImGuiDir_Up:
4350 case ImGuiDir_Down:
4351 if (dir == ImGuiDir_Up) r = -r;
4352 center.y -= r * 0.25f;
4353 a = ImVec2(0,1) * r;
4354 b = ImVec2(-0.866f,-0.5f) * r;
4355 c = ImVec2(+0.866f,-0.5f) * r;
4356 break;
4357 case ImGuiDir_Left:
4358 case ImGuiDir_Right:
4359 if (dir == ImGuiDir_Left) r = -r;
4360 center.x -= r * 0.25f;
4361 a = ImVec2(1,0) * r;
4362 b = ImVec2(-0.500f,+0.866f) * r;
4363 c = ImVec2(-0.500f,-0.866f) * r;
4364 break;
4365 case ImGuiDir_None:
4366 case ImGuiDir_COUNT:
4367 IM_ASSERT(0);
4368 break;
4369 }
4370
4371 window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text));
4372 }
4373
RenderBullet(ImVec2 pos)4374 void ImGui::RenderBullet(ImVec2 pos)
4375 {
4376 ImGuiContext& g = *GImGui;
4377 ImGuiWindow* window = g.CurrentWindow;
4378 window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8);
4379 }
4380
RenderCheckMark(ImVec2 pos,ImU32 col,float sz)4381 void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz)
4382 {
4383 ImGuiContext& g = *GImGui;
4384 ImGuiWindow* window = g.CurrentWindow;
4385
4386 float thickness = ImMax(sz / 5.0f, 1.0f);
4387 sz -= thickness*0.5f;
4388 pos += ImVec2(thickness*0.25f, thickness*0.25f);
4389
4390 float third = sz / 3.0f;
4391 float bx = pos.x + third;
4392 float by = pos.y + sz - third*0.5f;
4393 window->DrawList->PathLineTo(ImVec2(bx - third, by - third));
4394 window->DrawList->PathLineTo(ImVec2(bx, by));
4395 window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2));
4396 window->DrawList->PathStroke(col, false, thickness);
4397 }
4398
RenderNavHighlight(const ImRect & bb,ImGuiID id,ImGuiNavHighlightFlags flags)4399 void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
4400 {
4401 ImGuiContext& g = *GImGui;
4402 if (id != g.NavId)
4403 return;
4404 if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
4405 return;
4406 ImGuiWindow* window = ImGui::GetCurrentWindow();
4407 if (window->DC.NavHideHighlightOneFrame)
4408 return;
4409
4410 float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
4411 ImRect display_rect = bb;
4412 display_rect.ClipWith(window->ClipRect);
4413 if (flags & ImGuiNavHighlightFlags_TypeDefault)
4414 {
4415 const float THICKNESS = 2.0f;
4416 const float DISTANCE = 3.0f + THICKNESS * 0.5f;
4417 display_rect.Expand(ImVec2(DISTANCE,DISTANCE));
4418 bool fully_visible = window->ClipRect.Contains(display_rect);
4419 if (!fully_visible)
4420 window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
4421 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);
4422 if (!fully_visible)
4423 window->DrawList->PopClipRect();
4424 }
4425 if (flags & ImGuiNavHighlightFlags_TypeThin)
4426 {
4427 window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f);
4428 }
4429 }
4430
4431 // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
4432 // 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)4433 ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
4434 {
4435 ImGuiContext& g = *GImGui;
4436
4437 const char* text_display_end;
4438 if (hide_text_after_double_hash)
4439 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
4440 else
4441 text_display_end = text_end;
4442
4443 ImFont* font = g.Font;
4444 const float font_size = g.FontSize;
4445 if (text == text_display_end)
4446 return ImVec2(0.0f, font_size);
4447 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
4448
4449 // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field)
4450 const float font_scale = font_size / font->FontSize;
4451 const float character_spacing_x = 1.0f * font_scale;
4452 if (text_size.x > 0.0f)
4453 text_size.x -= character_spacing_x;
4454 text_size.x = (float)(int)(text_size.x + 0.95f);
4455
4456 return text_size;
4457 }
4458
4459 // Helper to calculate coarse clipping of large list of evenly sized items.
4460 // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
4461 // 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)4462 void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
4463 {
4464 ImGuiContext& g = *GImGui;
4465 ImGuiWindow* window = g.CurrentWindow;
4466 if (g.LogEnabled)
4467 {
4468 // If logging is active, do not perform any clipping
4469 *out_items_display_start = 0;
4470 *out_items_display_end = items_count;
4471 return;
4472 }
4473 if (window->SkipItems)
4474 {
4475 *out_items_display_start = *out_items_display_end = 0;
4476 return;
4477 }
4478
4479 const ImVec2 pos = window->DC.CursorPos;
4480 int start = (int)((window->ClipRect.Min.y - pos.y) / items_height);
4481 int end = (int)((window->ClipRect.Max.y - pos.y) / items_height);
4482 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
4483 start--;
4484 if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down)
4485 end++;
4486
4487 start = ImClamp(start, 0, items_count);
4488 end = ImClamp(end + 1, start, items_count);
4489 *out_items_display_start = start;
4490 *out_items_display_end = end;
4491 }
4492
4493 // Find window given position, search front-to-back
4494 // 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()4495 static ImGuiWindow* FindHoveredWindow()
4496 {
4497 ImGuiContext& g = *GImGui;
4498 for (int i = g.Windows.Size - 1; i >= 0; i--)
4499 {
4500 ImGuiWindow* window = g.Windows[i];
4501 if (!window->Active)
4502 continue;
4503 if (window->Flags & ImGuiWindowFlags_NoInputs)
4504 continue;
4505
4506 // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
4507 ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding);
4508 if (bb.Contains(g.IO.MousePos))
4509 return window;
4510 }
4511 return NULL;
4512 }
4513
4514 // Test if mouse cursor is hovering given rectangle
4515 // NB- Rectangle is clipped by our current clip setting
4516 // 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)4517 bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
4518 {
4519 ImGuiContext& g = *GImGui;
4520 ImGuiWindow* window = g.CurrentWindow;
4521
4522 // Clip
4523 ImRect rect_clipped(r_min, r_max);
4524 if (clip)
4525 rect_clipped.ClipWith(window->ClipRect);
4526
4527 // Expand for touch input
4528 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
4529 return rect_for_touch.Contains(g.IO.MousePos);
4530 }
4531
IsKeyPressedMap(ImGuiKey key,bool repeat)4532 static bool IsKeyPressedMap(ImGuiKey key, bool repeat)
4533 {
4534 const int key_index = GImGui->IO.KeyMap[key];
4535 return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false;
4536 }
4537
GetKeyIndex(ImGuiKey imgui_key)4538 int ImGui::GetKeyIndex(ImGuiKey imgui_key)
4539 {
4540 IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
4541 return GImGui->IO.KeyMap[imgui_key];
4542 }
4543
4544 // 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)4545 bool ImGui::IsKeyDown(int user_key_index)
4546 {
4547 if (user_key_index < 0) return false;
4548 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown));
4549 return GImGui->IO.KeysDown[user_key_index];
4550 }
4551
CalcTypematicPressedRepeatAmount(float t,float t_prev,float repeat_delay,float repeat_rate)4552 int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate)
4553 {
4554 if (t == 0.0f)
4555 return 1;
4556 if (t <= repeat_delay || repeat_rate <= 0.0f)
4557 return 0;
4558 const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate);
4559 return (count > 0) ? count : 0;
4560 }
4561
GetKeyPressedAmount(int key_index,float repeat_delay,float repeat_rate)4562 int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate)
4563 {
4564 ImGuiContext& g = *GImGui;
4565 if (key_index < 0) return false;
4566 IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4567 const float t = g.IO.KeysDownDuration[key_index];
4568 return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate);
4569 }
4570
IsKeyPressed(int user_key_index,bool repeat)4571 bool ImGui::IsKeyPressed(int user_key_index, bool repeat)
4572 {
4573 ImGuiContext& g = *GImGui;
4574 if (user_key_index < 0) return false;
4575 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4576 const float t = g.IO.KeysDownDuration[user_key_index];
4577 if (t == 0.0f)
4578 return true;
4579 if (repeat && t > g.IO.KeyRepeatDelay)
4580 return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
4581 return false;
4582 }
4583
IsKeyReleased(int user_key_index)4584 bool ImGui::IsKeyReleased(int user_key_index)
4585 {
4586 ImGuiContext& g = *GImGui;
4587 if (user_key_index < 0) return false;
4588 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4589 return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index];
4590 }
4591
IsMouseDown(int button)4592 bool ImGui::IsMouseDown(int button)
4593 {
4594 ImGuiContext& g = *GImGui;
4595 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4596 return g.IO.MouseDown[button];
4597 }
4598
IsAnyMouseDown()4599 bool ImGui::IsAnyMouseDown()
4600 {
4601 ImGuiContext& g = *GImGui;
4602 for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
4603 if (g.IO.MouseDown[n])
4604 return true;
4605 return false;
4606 }
4607
IsMouseClicked(int button,bool repeat)4608 bool ImGui::IsMouseClicked(int button, bool repeat)
4609 {
4610 ImGuiContext& g = *GImGui;
4611 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4612 const float t = g.IO.MouseDownDuration[button];
4613 if (t == 0.0f)
4614 return true;
4615
4616 if (repeat && t > g.IO.KeyRepeatDelay)
4617 {
4618 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate;
4619 if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f))
4620 return true;
4621 }
4622
4623 return false;
4624 }
4625
IsMouseReleased(int button)4626 bool ImGui::IsMouseReleased(int button)
4627 {
4628 ImGuiContext& g = *GImGui;
4629 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4630 return g.IO.MouseReleased[button];
4631 }
4632
IsMouseDoubleClicked(int button)4633 bool ImGui::IsMouseDoubleClicked(int button)
4634 {
4635 ImGuiContext& g = *GImGui;
4636 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4637 return g.IO.MouseDoubleClicked[button];
4638 }
4639
IsMouseDragging(int button,float lock_threshold)4640 bool ImGui::IsMouseDragging(int button, float lock_threshold)
4641 {
4642 ImGuiContext& g = *GImGui;
4643 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4644 if (!g.IO.MouseDown[button])
4645 return false;
4646 if (lock_threshold < 0.0f)
4647 lock_threshold = g.IO.MouseDragThreshold;
4648 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
4649 }
4650
GetMousePos()4651 ImVec2 ImGui::GetMousePos()
4652 {
4653 return GImGui->IO.MousePos;
4654 }
4655
4656 // NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
GetMousePosOnOpeningCurrentPopup()4657 ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
4658 {
4659 ImGuiContext& g = *GImGui;
4660 if (g.CurrentPopupStack.Size > 0)
4661 return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos;
4662 return g.IO.MousePos;
4663 }
4664
4665 // We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position
IsMousePosValid(const ImVec2 * mouse_pos)4666 bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
4667 {
4668 if (mouse_pos == NULL)
4669 mouse_pos = &GImGui->IO.MousePos;
4670 const float MOUSE_INVALID = -256000.0f;
4671 return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID;
4672 }
4673
4674 // 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)4675 ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold)
4676 {
4677 ImGuiContext& g = *GImGui;
4678 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4679 if (lock_threshold < 0.0f)
4680 lock_threshold = g.IO.MouseDragThreshold;
4681 if (g.IO.MouseDown[button])
4682 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
4683 return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment).
4684 return ImVec2(0.0f, 0.0f);
4685 }
4686
ResetMouseDragDelta(int button)4687 void ImGui::ResetMouseDragDelta(int button)
4688 {
4689 ImGuiContext& g = *GImGui;
4690 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4691 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
4692 g.IO.MouseClickedPos[button] = g.IO.MousePos;
4693 }
4694
GetMouseCursor()4695 ImGuiMouseCursor ImGui::GetMouseCursor()
4696 {
4697 return GImGui->MouseCursor;
4698 }
4699
SetMouseCursor(ImGuiMouseCursor cursor_type)4700 void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
4701 {
4702 GImGui->MouseCursor = cursor_type;
4703 }
4704
CaptureKeyboardFromApp(bool capture)4705 void ImGui::CaptureKeyboardFromApp(bool capture)
4706 {
4707 GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0;
4708 }
4709
CaptureMouseFromApp(bool capture)4710 void ImGui::CaptureMouseFromApp(bool capture)
4711 {
4712 GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0;
4713 }
4714
IsItemActive()4715 bool ImGui::IsItemActive()
4716 {
4717 ImGuiContext& g = *GImGui;
4718 if (g.ActiveId)
4719 {
4720 ImGuiWindow* window = g.CurrentWindow;
4721 return g.ActiveId == window->DC.LastItemId;
4722 }
4723 return false;
4724 }
4725
IsItemFocused()4726 bool ImGui::IsItemFocused()
4727 {
4728 ImGuiContext& g = *GImGui;
4729 return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId;
4730 }
4731
IsItemClicked(int mouse_button)4732 bool ImGui::IsItemClicked(int mouse_button)
4733 {
4734 return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default);
4735 }
4736
IsAnyItemHovered()4737 bool ImGui::IsAnyItemHovered()
4738 {
4739 ImGuiContext& g = *GImGui;
4740 return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
4741 }
4742
IsAnyItemActive()4743 bool ImGui::IsAnyItemActive()
4744 {
4745 ImGuiContext& g = *GImGui;
4746 return g.ActiveId != 0;
4747 }
4748
IsAnyItemFocused()4749 bool ImGui::IsAnyItemFocused()
4750 {
4751 ImGuiContext& g = *GImGui;
4752 return g.NavId != 0 && !g.NavDisableHighlight;
4753 }
4754
IsItemVisible()4755 bool ImGui::IsItemVisible()
4756 {
4757 ImGuiWindow* window = GetCurrentWindowRead();
4758 return window->ClipRect.Overlaps(window->DC.LastItemRect);
4759 }
4760
4761 // 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()4762 void ImGui::SetItemAllowOverlap()
4763 {
4764 ImGuiContext& g = *GImGui;
4765 if (g.HoveredId == g.CurrentWindow->DC.LastItemId)
4766 g.HoveredIdAllowOverlap = true;
4767 if (g.ActiveId == g.CurrentWindow->DC.LastItemId)
4768 g.ActiveIdAllowOverlap = true;
4769 }
4770
GetItemRectMin()4771 ImVec2 ImGui::GetItemRectMin()
4772 {
4773 ImGuiWindow* window = GetCurrentWindowRead();
4774 return window->DC.LastItemRect.Min;
4775 }
4776
GetItemRectMax()4777 ImVec2 ImGui::GetItemRectMax()
4778 {
4779 ImGuiWindow* window = GetCurrentWindowRead();
4780 return window->DC.LastItemRect.Max;
4781 }
4782
GetItemRectSize()4783 ImVec2 ImGui::GetItemRectSize()
4784 {
4785 ImGuiWindow* window = GetCurrentWindowRead();
4786 return window->DC.LastItemRect.GetSize();
4787 }
4788
GetViewportRect()4789 static ImRect GetViewportRect()
4790 {
4791 ImGuiContext& g = *GImGui;
4792 if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y)
4793 return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax);
4794 return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
4795 }
4796
4797 // 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)4798 void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip)
4799 {
4800 ImGuiContext& g = *GImGui;
4801 char window_name[16];
4802 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
4803 if (override_previous_tooltip)
4804 if (ImGuiWindow* window = FindWindowByName(window_name))
4805 if (window->Active)
4806 {
4807 // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
4808 window->HiddenFrames = 1;
4809 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
4810 }
4811 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav;
4812 Begin(window_name, NULL, flags | extra_flags);
4813 }
4814
SetTooltipV(const char * fmt,va_list args)4815 void ImGui::SetTooltipV(const char* fmt, va_list args)
4816 {
4817 BeginTooltipEx(0, true);
4818 TextV(fmt, args);
4819 EndTooltip();
4820 }
4821
SetTooltip(const char * fmt,...)4822 void ImGui::SetTooltip(const char* fmt, ...)
4823 {
4824 va_list args;
4825 va_start(args, fmt);
4826 SetTooltipV(fmt, args);
4827 va_end(args);
4828 }
4829
BeginTooltip()4830 void ImGui::BeginTooltip()
4831 {
4832 BeginTooltipEx(0, false);
4833 }
4834
EndTooltip()4835 void ImGui::EndTooltip()
4836 {
4837 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
4838 End();
4839 }
4840
4841 // Mark popup as open (toggle toward open state).
4842 // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
4843 // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
4844 // One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
OpenPopupEx(ImGuiID id)4845 void ImGui::OpenPopupEx(ImGuiID id)
4846 {
4847 ImGuiContext& g = *GImGui;
4848 ImGuiWindow* parent_window = g.CurrentWindow;
4849 int current_stack_size = g.CurrentPopupStack.Size;
4850 ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
4851 popup_ref.PopupId = id;
4852 popup_ref.Window = NULL;
4853 popup_ref.ParentWindow = parent_window;
4854 popup_ref.OpenFrameCount = g.FrameCount;
4855 popup_ref.OpenParentId = parent_window->IDStack.back();
4856 popup_ref.OpenMousePos = g.IO.MousePos;
4857 popup_ref.OpenPopupPos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos;
4858
4859 //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id);
4860 if (g.OpenPopupStack.Size < current_stack_size + 1)
4861 {
4862 g.OpenPopupStack.push_back(popup_ref);
4863 }
4864 else
4865 {
4866 // Close child popups if any
4867 g.OpenPopupStack.resize(current_stack_size + 1);
4868
4869 // 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
4870 // 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
4871 // 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.
4872 if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
4873 g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
4874 else
4875 g.OpenPopupStack[current_stack_size] = popup_ref;
4876
4877 // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
4878 // This is equivalent to what ClosePopupToLevel() does.
4879 //if (g.OpenPopupStack[current_stack_size].PopupId == id)
4880 // FocusWindow(parent_window);
4881 }
4882 }
4883
OpenPopup(const char * str_id)4884 void ImGui::OpenPopup(const char* str_id)
4885 {
4886 ImGuiContext& g = *GImGui;
4887 OpenPopupEx(g.CurrentWindow->GetID(str_id));
4888 }
4889
ClosePopupsOverWindow(ImGuiWindow * ref_window)4890 void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window)
4891 {
4892 ImGuiContext& g = *GImGui;
4893 if (g.OpenPopupStack.empty())
4894 return;
4895
4896 // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
4897 // Don't close our own child popup windows.
4898 int n = 0;
4899 if (ref_window)
4900 {
4901 for (n = 0; n < g.OpenPopupStack.Size; n++)
4902 {
4903 ImGuiPopupRef& popup = g.OpenPopupStack[n];
4904 if (!popup.Window)
4905 continue;
4906 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
4907 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
4908 continue;
4909
4910 // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow)
4911 bool has_focus = false;
4912 for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
4913 has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow);
4914 if (!has_focus)
4915 break;
4916 }
4917 }
4918 if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below
4919 ClosePopupToLevel(n);
4920 }
4921
GetFrontMostPopupModal()4922 ImGuiWindow* ImGui::GetFrontMostPopupModal()
4923 {
4924 ImGuiContext& g = *GImGui;
4925 for (int n = g.OpenPopupStack.Size-1; n >= 0; n--)
4926 if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
4927 if (popup->Flags & ImGuiWindowFlags_Modal)
4928 return popup;
4929 return NULL;
4930 }
4931
ClosePopupToLevel(int remaining)4932 static void ClosePopupToLevel(int remaining)
4933 {
4934 IM_ASSERT(remaining >= 0);
4935 ImGuiContext& g = *GImGui;
4936 ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow;
4937 if (g.NavLayer == 0)
4938 focus_window = NavRestoreLastChildNavWindow(focus_window);
4939 ImGui::FocusWindow(focus_window);
4940 focus_window->DC.NavHideHighlightOneFrame = true;
4941 g.OpenPopupStack.resize(remaining);
4942 }
4943
ClosePopup(ImGuiID id)4944 void ImGui::ClosePopup(ImGuiID id)
4945 {
4946 if (!IsPopupOpen(id))
4947 return;
4948 ImGuiContext& g = *GImGui;
4949 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
4950 }
4951
4952 // Close the popup we have begin-ed into.
CloseCurrentPopup()4953 void ImGui::CloseCurrentPopup()
4954 {
4955 ImGuiContext& g = *GImGui;
4956 int popup_idx = g.CurrentPopupStack.Size - 1;
4957 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
4958 return;
4959 while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
4960 popup_idx--;
4961 ClosePopupToLevel(popup_idx);
4962 }
4963
BeginPopupEx(ImGuiID id,ImGuiWindowFlags extra_flags)4964 bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags)
4965 {
4966 ImGuiContext& g = *GImGui;
4967 if (!IsPopupOpen(id))
4968 {
4969 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
4970 return false;
4971 }
4972
4973 char name[20];
4974 if (extra_flags & ImGuiWindowFlags_ChildMenu)
4975 ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
4976 else
4977 ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
4978
4979 bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup);
4980 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
4981 EndPopup();
4982
4983 return is_open;
4984 }
4985
BeginPopup(const char * str_id,ImGuiWindowFlags flags)4986 bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
4987 {
4988 ImGuiContext& g = *GImGui;
4989 if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
4990 {
4991 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
4992 return false;
4993 }
4994 return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
4995 }
4996
IsPopupOpen(ImGuiID id)4997 bool ImGui::IsPopupOpen(ImGuiID id)
4998 {
4999 ImGuiContext& g = *GImGui;
5000 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
5001 }
5002
IsPopupOpen(const char * str_id)5003 bool ImGui::IsPopupOpen(const char* str_id)
5004 {
5005 ImGuiContext& g = *GImGui;
5006 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
5007 }
5008
BeginPopupModal(const char * name,bool * p_open,ImGuiWindowFlags flags)5009 bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
5010 {
5011 ImGuiContext& g = *GImGui;
5012 ImGuiWindow* window = g.CurrentWindow;
5013 const ImGuiID id = window->GetID(name);
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 // Center modal windows by default
5021 // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
5022 if (g.NextWindowData.PosCond == 0)
5023 SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
5024
5025 bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
5026 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5027 {
5028 EndPopup();
5029 if (is_open)
5030 ClosePopup(id);
5031 return false;
5032 }
5033
5034 return is_open;
5035 }
5036
NavProcessMoveRequestWrapAround(ImGuiWindow * window)5037 static void NavProcessMoveRequestWrapAround(ImGuiWindow* window)
5038 {
5039 ImGuiContext& g = *GImGui;
5040 if (g.NavWindow == window && NavMoveRequestButNoResultYet())
5041 if ((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == 0)
5042 {
5043 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
5044 ImGui::NavMoveRequestCancel();
5045 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;
5046 }
5047 }
5048
EndPopup()5049 void ImGui::EndPopup()
5050 {
5051 ImGuiContext& g = *GImGui; (void)g;
5052 IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
5053 IM_ASSERT(g.CurrentPopupStack.Size > 0);
5054
5055 // Make all menus and popups wrap around for now, may need to expose that policy.
5056 NavProcessMoveRequestWrapAround(g.CurrentWindow);
5057
5058 End();
5059 }
5060
OpenPopupOnItemClick(const char * str_id,int mouse_button)5061 bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button)
5062 {
5063 ImGuiWindow* window = GImGui->CurrentWindow;
5064 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5065 {
5066 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!
5067 IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
5068 OpenPopupEx(id);
5069 return true;
5070 }
5071 return false;
5072 }
5073
5074 // This is a helper to handle the simplest case of associating one named popup to one given widget.
5075 // You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
5076 // You can pass a NULL str_id to use the identifier of the last item.
BeginPopupContextItem(const char * str_id,int mouse_button)5077 bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button)
5078 {
5079 ImGuiWindow* window = GImGui->CurrentWindow;
5080 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!
5081 IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
5082 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5083 OpenPopupEx(id);
5084 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5085 }
5086
BeginPopupContextWindow(const char * str_id,int mouse_button,bool also_over_items)5087 bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items)
5088 {
5089 if (!str_id)
5090 str_id = "window_context";
5091 ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
5092 if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5093 if (also_over_items || !IsAnyItemHovered())
5094 OpenPopupEx(id);
5095 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5096 }
5097
BeginPopupContextVoid(const char * str_id,int mouse_button)5098 bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button)
5099 {
5100 if (!str_id)
5101 str_id = "void_context";
5102 ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
5103 if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
5104 OpenPopupEx(id);
5105 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5106 }
5107
BeginChildEx(const char * name,ImGuiID id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5108 static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5109 {
5110 ImGuiContext& g = *GImGui;
5111 ImGuiWindow* parent_window = ImGui::GetCurrentWindow();
5112 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow;
5113 flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
5114
5115 const ImVec2 content_avail = ImGui::GetContentRegionAvail();
5116 ImVec2 size = ImFloor(size_arg);
5117 const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
5118 if (size.x <= 0.0f)
5119 size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
5120 if (size.y <= 0.0f)
5121 size.y = ImMax(content_avail.y + size.y, 4.0f);
5122
5123 const float backup_border_size = g.Style.ChildBorderSize;
5124 if (!border)
5125 g.Style.ChildBorderSize = 0.0f;
5126 flags |= extra_flags;
5127
5128 char title[256];
5129 if (name)
5130 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s", parent_window->Name, name);
5131 else
5132 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id);
5133
5134 ImGui::SetNextWindowSize(size);
5135 bool ret = ImGui::Begin(title, NULL, flags);
5136 ImGuiWindow* child_window = ImGui::GetCurrentWindow();
5137 child_window->ChildId = id;
5138 child_window->AutoFitChildAxises = auto_fit_axises;
5139 g.Style.ChildBorderSize = backup_border_size;
5140
5141 // Process navigation-in immediately so NavInit can run on first frame
5142 if (!(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && g.NavActivateId == id)
5143 {
5144 ImGui::FocusWindow(child_window);
5145 ImGui::NavInitWindow(child_window, false);
5146 ImGui::SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
5147 g.ActiveIdSource = ImGuiInputSource_Nav;
5148 }
5149
5150 return ret;
5151 }
5152
BeginChild(const char * str_id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5153 bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5154 {
5155 ImGuiWindow* window = GetCurrentWindow();
5156 return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
5157 }
5158
BeginChild(ImGuiID id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5159 bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5160 {
5161 IM_ASSERT(id != 0);
5162 return BeginChildEx(NULL, id, size_arg, border, extra_flags);
5163 }
5164
EndChild()5165 void ImGui::EndChild()
5166 {
5167 ImGuiContext& g = *GImGui;
5168 ImGuiWindow* window = g.CurrentWindow;
5169
5170 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss
5171 if (window->BeginCount > 1)
5172 {
5173 End();
5174 }
5175 else
5176 {
5177 // 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.
5178 ImVec2 sz = GetWindowSize();
5179 if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
5180 sz.x = ImMax(4.0f, sz.x);
5181 if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
5182 sz.y = ImMax(4.0f, sz.y);
5183 End();
5184
5185 ImGuiWindow* parent_window = g.CurrentWindow;
5186 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
5187 ItemSize(sz);
5188 if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened))
5189 {
5190 ItemAdd(bb, window->ChildId);
5191 RenderNavHighlight(bb, window->ChildId);
5192
5193 // When browsing a window that has no activable items (scroll only) we keep a highlight on the child
5194 if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow)
5195 RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin);
5196 }
5197 else
5198 {
5199 // Not navigable into
5200 ItemAdd(bb, 0);
5201 }
5202 }
5203 }
5204
5205 // Helper to create a child window / scrolling region that looks like a normal widget frame.
BeginChildFrame(ImGuiID id,const ImVec2 & size,ImGuiWindowFlags extra_flags)5206 bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
5207 {
5208 ImGuiContext& g = *GImGui;
5209 const ImGuiStyle& style = g.Style;
5210 PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
5211 PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
5212 PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
5213 PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
5214 return BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
5215 }
5216
EndChildFrame()5217 void ImGui::EndChildFrame()
5218 {
5219 EndChild();
5220 PopStyleVar(3);
5221 PopStyleColor();
5222 }
5223
5224 // Save and compare stack sizes on Begin()/End() to detect usage errors
CheckStacksSize(ImGuiWindow * window,bool write)5225 static void CheckStacksSize(ImGuiWindow* window, bool write)
5226 {
5227 // 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)
5228 ImGuiContext& g = *GImGui;
5229 int* p_backup = &window->DC.StackSizesBackup[0];
5230 { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop()
5231 { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup()
5232 { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup()
5233 { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor()
5234 { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar()
5235 { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont()
5236 IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
5237 }
5238
5239 enum ImGuiPopupPositionPolicy
5240 {
5241 ImGuiPopupPositionPolicy_Default,
5242 ImGuiPopupPositionPolicy_ComboBox
5243 };
5244
FindScreenRectForWindow(ImGuiWindow * window)5245 static ImRect FindScreenRectForWindow(ImGuiWindow* window)
5246 {
5247 ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding;
5248 ImRect r_screen = GetViewportRect();
5249 r_screen.Expand(ImVec2((window->Size.x - r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (window->Size.y - r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
5250 return r_screen;
5251 }
5252
5253 // 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.)
5254 // 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.
FindBestWindowPosForPopupEx(const ImVec2 & ref_pos,const ImVec2 & size,ImGuiDir * last_dir,const ImRect & r_outer,const ImRect & r_avoid,ImGuiPopupPositionPolicy policy=ImGuiPopupPositionPolicy_Default)5255 static ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default)
5256 {
5257 ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
5258 //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
5259 //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
5260
5261 // Combo Box policy (we want a connecting edge)
5262 if (policy == ImGuiPopupPositionPolicy_ComboBox)
5263 {
5264 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
5265 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
5266 {
5267 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
5268 if (n != -1 && dir == *last_dir) // Already tried this direction?
5269 continue;
5270 ImVec2 pos;
5271 if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
5272 if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
5273 if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
5274 if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
5275 if (!r_outer.Contains(ImRect(pos, pos + size)))
5276 continue;
5277 *last_dir = dir;
5278 return pos;
5279 }
5280 }
5281
5282 // Default popup policy
5283 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
5284 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
5285 {
5286 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
5287 if (n != -1 && dir == *last_dir) // Already tried this direction?
5288 continue;
5289 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);
5290 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);
5291 if (avail_w < size.x || avail_h < size.y)
5292 continue;
5293 ImVec2 pos;
5294 pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
5295 pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
5296 *last_dir = dir;
5297 return pos;
5298 }
5299
5300 // Fallback, try to keep within display
5301 *last_dir = ImGuiDir_None;
5302 ImVec2 pos = ref_pos;
5303 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
5304 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
5305 return pos;
5306 }
5307
FindBestWindowPosForPopup(ImGuiWindow * window)5308 static ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window)
5309 {
5310 ImGuiContext& g = *GImGui;
5311
5312 ImRect r_screen = FindScreenRectForWindow(window);
5313 if (window->Flags & ImGuiWindowFlags_ChildMenu)
5314 {
5315 // 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.
5316 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
5317 IM_ASSERT(g.CurrentWindow == window);
5318 ImGuiWindow* parent_menu = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2];
5319 float horizontal_overlap = g.Style.ItemSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).
5320 ImRect r_avoid;
5321 if (parent_menu->DC.MenuBarAppending)
5322 r_avoid = ImRect(-FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight(), FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight() + parent_menu->MenuBarHeight());
5323 else
5324 r_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);
5325 return FindBestWindowPosForPopupEx(window->PosFloat, window->Size, &window->AutoPosLastDirection, r_screen, r_avoid);
5326 }
5327 if (window->Flags & ImGuiWindowFlags_Popup)
5328 {
5329 ImRect r_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1);
5330 return FindBestWindowPosForPopupEx(window->PosFloat, window->Size, &window->AutoPosLastDirection, r_screen, r_avoid);
5331 }
5332 if (window->Flags & ImGuiWindowFlags_Tooltip)
5333 {
5334 // Position tooltip (always follows mouse)
5335 float sc = g.Style.MouseCursorScale;
5336 ImVec2 ref_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos;
5337 ImRect r_avoid;
5338 if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
5339 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
5340 else
5341 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important.
5342 ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_screen, r_avoid);
5343 if (window->AutoPosLastDirection == ImGuiDir_None)
5344 pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
5345 return pos;
5346 }
5347 IM_ASSERT(0);
5348 return window->Pos;
5349 }
5350
SetWindowConditionAllowFlags(ImGuiWindow * window,ImGuiCond flags,bool enabled)5351 static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
5352 {
5353 window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
5354 window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
5355 window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
5356 }
5357
FindWindowByName(const char * name)5358 ImGuiWindow* ImGui::FindWindowByName(const char* name)
5359 {
5360 ImGuiContext& g = *GImGui;
5361 ImGuiID id = ImHash(name, 0);
5362 return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id);
5363 }
5364
CreateNewWindow(const char * name,ImVec2 size,ImGuiWindowFlags flags)5365 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags)
5366 {
5367 ImGuiContext& g = *GImGui;
5368
5369 // Create window the first time
5370 ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
5371 window->Flags = flags;
5372 g.WindowsById.SetVoidPtr(window->ID, window);
5373
5374 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
5375 if (!(flags & ImGuiWindowFlags_NoSavedSettings))
5376 {
5377 // Retrieve settings from .ini file
5378 // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
5379 window->Pos = window->PosFloat = ImVec2(60, 60);
5380
5381 if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID))
5382 {
5383 SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
5384 window->PosFloat = settings->Pos;
5385 window->Pos = ImFloor(window->PosFloat);
5386 window->Collapsed = settings->Collapsed;
5387 if (ImLengthSqr(settings->Size) > 0.00001f)
5388 size = settings->Size;
5389 }
5390 }
5391 window->Size = window->SizeFull = window->SizeFullAtLastBegin = size;
5392
5393 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
5394 {
5395 window->AutoFitFramesX = window->AutoFitFramesY = 2;
5396 window->AutoFitOnlyGrows = false;
5397 }
5398 else
5399 {
5400 if (window->Size.x <= 0.0f)
5401 window->AutoFitFramesX = 2;
5402 if (window->Size.y <= 0.0f)
5403 window->AutoFitFramesY = 2;
5404 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
5405 }
5406
5407 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
5408 g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once
5409 else
5410 g.Windows.push_back(window);
5411 return window;
5412 }
5413
CalcSizeAfterConstraint(ImGuiWindow * window,ImVec2 new_size)5414 static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size)
5415 {
5416 ImGuiContext& g = *GImGui;
5417 if (g.NextWindowData.SizeConstraintCond != 0)
5418 {
5419 // Using -1,-1 on either X/Y axis to preserve the current size.
5420 ImRect cr = g.NextWindowData.SizeConstraintRect;
5421 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
5422 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
5423 if (g.NextWindowData.SizeCallback)
5424 {
5425 ImGuiSizeCallbackData data;
5426 data.UserData = g.NextWindowData.SizeCallbackUserData;
5427 data.Pos = window->Pos;
5428 data.CurrentSize = window->SizeFull;
5429 data.DesiredSize = new_size;
5430 g.NextWindowData.SizeCallback(&data);
5431 new_size = data.DesiredSize;
5432 }
5433 }
5434
5435 // Minimum size
5436 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
5437 {
5438 new_size = ImMax(new_size, g.Style.WindowMinSize);
5439 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
5440 }
5441 return new_size;
5442 }
5443
CalcSizeContents(ImGuiWindow * window)5444 static ImVec2 CalcSizeContents(ImGuiWindow* window)
5445 {
5446 ImVec2 sz;
5447 sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x));
5448 sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y));
5449 return sz + window->WindowPadding;
5450 }
5451
CalcSizeAutoFit(ImGuiWindow * window,const ImVec2 & size_contents)5452 static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents)
5453 {
5454 ImGuiContext& g = *GImGui;
5455 ImGuiStyle& style = g.Style;
5456 ImGuiWindowFlags flags = window->Flags;
5457 ImVec2 size_auto_fit;
5458 if ((flags & ImGuiWindowFlags_Tooltip) != 0)
5459 {
5460 // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose.
5461 size_auto_fit = size_contents;
5462 }
5463 else
5464 {
5465 // 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.
5466 size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding));
5467 ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit);
5468 if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar))
5469 size_auto_fit.y += style.ScrollbarSize;
5470 if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar))
5471 size_auto_fit.x += style.ScrollbarSize;
5472 }
5473 return size_auto_fit;
5474 }
5475
GetScrollMaxX(ImGuiWindow * window)5476 static float GetScrollMaxX(ImGuiWindow* window)
5477 {
5478 return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x));
5479 }
5480
GetScrollMaxY(ImGuiWindow * window)5481 static float GetScrollMaxY(ImGuiWindow* window)
5482 {
5483 return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y));
5484 }
5485
CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow * window)5486 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)
5487 {
5488 ImVec2 scroll = window->Scroll;
5489 float cr_x = window->ScrollTargetCenterRatio.x;
5490 float cr_y = window->ScrollTargetCenterRatio.y;
5491 if (window->ScrollTarget.x < FLT_MAX)
5492 scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x);
5493 if (window->ScrollTarget.y < FLT_MAX)
5494 scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y);
5495 scroll = ImMax(scroll, ImVec2(0.0f, 0.0f));
5496 if (!window->Collapsed && !window->SkipItems)
5497 {
5498 scroll.x = ImMin(scroll.x, GetScrollMaxX(window));
5499 scroll.y = ImMin(scroll.y, GetScrollMaxY(window));
5500 }
5501 return scroll;
5502 }
5503
GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)5504 static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
5505 {
5506 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
5507 return ImGuiCol_PopupBg;
5508 if (flags & ImGuiWindowFlags_ChildWindow)
5509 return ImGuiCol_ChildBg;
5510 return ImGuiCol_WindowBg;
5511 }
5512
CalcResizePosSizeFromAnyCorner(ImGuiWindow * window,const ImVec2 & corner_target,const ImVec2 & corner_norm,ImVec2 * out_pos,ImVec2 * out_size)5513 static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)
5514 {
5515 ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left
5516 ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
5517 ImVec2 size_expected = pos_max - pos_min;
5518 ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected);
5519 *out_pos = pos_min;
5520 if (corner_norm.x == 0.0f)
5521 out_pos->x -= (size_constrained.x - size_expected.x);
5522 if (corner_norm.y == 0.0f)
5523 out_pos->y -= (size_constrained.y - size_expected.y);
5524 *out_size = size_constrained;
5525 }
5526
5527 struct ImGuiResizeGripDef
5528 {
5529 ImVec2 CornerPos;
5530 ImVec2 InnerDir;
5531 int AngleMin12, AngleMax12;
5532 };
5533
5534 const ImGuiResizeGripDef resize_grip_def[4] =
5535 {
5536 { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right
5537 { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left
5538 { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left
5539 { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right
5540 };
5541
GetBorderRect(ImGuiWindow * window,int border_n,float perp_padding,float thickness)5542 static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
5543 {
5544 ImRect rect = window->Rect();
5545 if (thickness == 0.0f) rect.Max -= ImVec2(1,1);
5546 if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness);
5547 if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding);
5548 if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y);
5549 if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding);
5550 IM_ASSERT(0);
5551 return ImRect();
5552 }
5553
5554 // 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])5555 static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4])
5556 {
5557 ImGuiContext& g = *GImGui;
5558 ImGuiWindowFlags flags = window->Flags;
5559 if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
5560 return;
5561
5562 const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0;
5563 const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
5564 const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f);
5565
5566 ImVec2 pos_target(FLT_MAX, FLT_MAX);
5567 ImVec2 size_target(FLT_MAX, FLT_MAX);
5568
5569 // Manual resize grips
5570 PushID("#RESIZE");
5571 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
5572 {
5573 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
5574 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
5575
5576 // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window
5577 ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size);
5578 resize_rect.FixInverted();
5579 bool hovered, held;
5580 ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
5581 if (hovered || held)
5582 g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
5583
5584 if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0)
5585 {
5586 // Manual auto-fit when double-clicking
5587 size_target = CalcSizeAfterConstraint(window, size_auto_fit);
5588 ClearActiveID();
5589 }
5590 else if (held)
5591 {
5592 // Resize from any of the four corners
5593 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
5594 ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip
5595 CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target);
5596 }
5597 if (resize_grip_n == 0 || held || hovered)
5598 resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
5599 }
5600 for (int border_n = 0; border_n < resize_border_count; border_n++)
5601 {
5602 const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check.
5603 const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise
5604 bool hovered, held;
5605 ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE);
5606 ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
5607 if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held)
5608 {
5609 g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
5610 if (held) *border_held = border_n;
5611 }
5612 if (held)
5613 {
5614 ImVec2 border_target = window->Pos;
5615 ImVec2 border_posn;
5616 if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); }
5617 if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); }
5618 if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); }
5619 if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); }
5620 CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target);
5621 }
5622 }
5623 PopID();
5624
5625 // Navigation resize (keyboard/gamepad)
5626 if (g.NavWindowingTarget == window)
5627 {
5628 ImVec2 nav_resize_delta;
5629 if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift)
5630 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
5631 if (g.NavInputSource == ImGuiInputSource_NavGamepad)
5632 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down);
5633 if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
5634 {
5635 const float NAV_RESIZE_SPEED = 600.0f;
5636 nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
5637 g.NavWindowingToggleLayer = false;
5638 g.NavDisableMouseHover = true;
5639 resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
5640 // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.
5641 size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
5642 }
5643 }
5644
5645 // Apply back modified position/size to window
5646 if (size_target.x != FLT_MAX)
5647 {
5648 window->SizeFull = size_target;
5649 MarkIniSettingsDirty(window);
5650 }
5651 if (pos_target.x != FLT_MAX)
5652 {
5653 window->Pos = window->PosFloat = ImFloor(pos_target);
5654 MarkIniSettingsDirty(window);
5655 }
5656
5657 window->Size = window->SizeFull;
5658 }
5659
5660 // Push a new ImGui window to add widgets to.
5661 // - 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.
5662 // - Begin/End can be called multiple times during the frame with the same window name to append content.
5663 // - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
5664 // 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.
5665 // - 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.
5666 // - 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)5667 bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
5668 {
5669 ImGuiContext& g = *GImGui;
5670 const ImGuiStyle& style = g.Style;
5671 IM_ASSERT(name != NULL); // Window name required
5672 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
5673 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
5674
5675 // Find or create
5676 ImGuiWindow* window = FindWindowByName(name);
5677 const bool window_just_created = (window == NULL);
5678 if (window_just_created)
5679 {
5680 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.
5681 window = CreateNewWindow(name, size_on_first_use, flags);
5682 }
5683
5684 // Automatically disable manual moving/resizing when NoInputs is set
5685 if (flags & ImGuiWindowFlags_NoInputs)
5686 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
5687
5688 if (flags & ImGuiWindowFlags_NavFlattened)
5689 IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
5690
5691 const int current_frame = g.FrameCount;
5692 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
5693 if (first_begin_of_the_frame)
5694 window->Flags = (ImGuiWindowFlags)flags;
5695 else
5696 flags = window->Flags;
5697
5698 // Update the Appearing flag
5699 bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
5700 const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames > 0);
5701 if (flags & ImGuiWindowFlags_Popup)
5702 {
5703 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
5704 window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
5705 window_just_activated_by_user |= (window != popup_ref.Window);
5706 }
5707 window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize);
5708 window->CloseButton = (p_open != NULL);
5709 if (window->Appearing)
5710 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
5711
5712 // 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
5713 ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back();
5714 ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
5715 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
5716
5717 // Add to stack
5718 g.CurrentWindowStack.push_back(window);
5719 SetCurrentWindow(window);
5720 CheckStacksSize(window, true);
5721 if (flags & ImGuiWindowFlags_Popup)
5722 {
5723 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
5724 popup_ref.Window = window;
5725 g.CurrentPopupStack.push_back(popup_ref);
5726 window->PopupId = popup_ref.PopupId;
5727 }
5728
5729 if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow))
5730 window->NavLastIds[0] = 0;
5731
5732 // Process SetNextWindow***() calls
5733 bool window_pos_set_by_api = false;
5734 bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
5735 if (g.NextWindowData.PosCond)
5736 {
5737 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
5738 if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
5739 {
5740 // May be processed on the next frame if this is our first frame and we are measuring size
5741 // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
5742 window->SetWindowPosVal = g.NextWindowData.PosVal;
5743 window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
5744 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
5745 }
5746 else
5747 {
5748 SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
5749 }
5750 g.NextWindowData.PosCond = 0;
5751 }
5752 if (g.NextWindowData.SizeCond)
5753 {
5754 window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
5755 window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
5756 SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
5757 g.NextWindowData.SizeCond = 0;
5758 }
5759 if (g.NextWindowData.ContentSizeCond)
5760 {
5761 // Adjust passed "client size" to become a "window size"
5762 window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal;
5763 if (window->SizeContentsExplicit.y != 0.0f)
5764 window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight();
5765 g.NextWindowData.ContentSizeCond = 0;
5766 }
5767 else if (first_begin_of_the_frame)
5768 {
5769 window->SizeContentsExplicit = ImVec2(0.0f, 0.0f);
5770 }
5771 if (g.NextWindowData.CollapsedCond)
5772 {
5773 SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
5774 g.NextWindowData.CollapsedCond = 0;
5775 }
5776 if (g.NextWindowData.FocusCond)
5777 {
5778 SetWindowFocus();
5779 g.NextWindowData.FocusCond = 0;
5780 }
5781 if (window->Appearing)
5782 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
5783
5784 // When reusing window again multiple times a frame, just append content (don't need to setup again)
5785 if (first_begin_of_the_frame)
5786 {
5787 const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
5788
5789 // Initialize
5790 window->ParentWindow = parent_window;
5791 window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = window->RootWindowForNav = window;
5792 if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !window_is_child_tooltip)
5793 window->RootWindow = parent_window->RootWindow;
5794 if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
5795 window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = parent_window->RootWindowForTitleBarHighlight; // Same value in master branch, will differ for docking
5796 while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
5797 window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
5798
5799 window->Active = true;
5800 window->BeginOrderWithinParent = 0;
5801 window->BeginOrderWithinContext = g.WindowsActiveCount++;
5802 window->BeginCount = 0;
5803 window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX);
5804 window->LastFrameActive = current_frame;
5805 window->IDStack.resize(1);
5806
5807 // Lock window rounding, border size and rounding so that altering the border sizes for children doesn't have side-effects.
5808 window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
5809 window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
5810 window->WindowPadding = style.WindowPadding;
5811 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f)
5812 window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
5813
5814 // Collapse window by double-clicking on title bar
5815 // 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
5816 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))
5817 {
5818 ImRect title_bar_rect = window->TitleBarRect();
5819 if (window->CollapseToggleWanted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]))
5820 {
5821 window->Collapsed = !window->Collapsed;
5822 MarkIniSettingsDirty(window);
5823 FocusWindow(window);
5824 }
5825 }
5826 else
5827 {
5828 window->Collapsed = false;
5829 }
5830 window->CollapseToggleWanted = false;
5831
5832 // SIZE
5833
5834 // Update contents size from last frame for auto-fitting (unless explicitly specified)
5835 window->SizeContents = CalcSizeContents(window);
5836
5837 // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
5838 if (window->HiddenFrames > 0)
5839 window->HiddenFrames--;
5840 if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)
5841 {
5842 window->HiddenFrames = 1;
5843 if (flags & ImGuiWindowFlags_AlwaysAutoResize)
5844 {
5845 if (!window_size_x_set_by_api)
5846 window->Size.x = window->SizeFull.x = 0.f;
5847 if (!window_size_y_set_by_api)
5848 window->Size.y = window->SizeFull.y = 0.f;
5849 window->SizeContents = ImVec2(0.f, 0.f);
5850 }
5851 }
5852
5853 // Hide new windows for one frame until they calculate their size
5854 if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))
5855 window->HiddenFrames = 1;
5856
5857 // Calculate auto-fit size, handle automatic resize
5858 const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents);
5859 ImVec2 size_full_modified(FLT_MAX, FLT_MAX);
5860 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
5861 {
5862 // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
5863 if (!window_size_x_set_by_api)
5864 window->SizeFull.x = size_full_modified.x = size_auto_fit.x;
5865 if (!window_size_y_set_by_api)
5866 window->SizeFull.y = size_full_modified.y = size_auto_fit.y;
5867 }
5868 else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
5869 {
5870 // Auto-fit may only grow window during the first few frames
5871 // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
5872 if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
5873 window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
5874 if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
5875 window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
5876 if (!window->Collapsed)
5877 MarkIniSettingsDirty(window);
5878 }
5879
5880 // Apply minimum/maximum window size constraints and final size
5881 window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull);
5882 window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;
5883
5884 // SCROLLBAR STATUS
5885
5886 // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size).
5887 if (!window->Collapsed)
5888 {
5889 // When reading the current size we need to read it after size constraints have been applied
5890 float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x;
5891 float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y;
5892 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
5893 window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
5894 if (window->ScrollbarX && !window->ScrollbarY)
5895 window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
5896 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
5897 }
5898
5899 // POSITION
5900
5901 // Popup latch its initial position, will position itself when it appears next frame
5902 if (window_just_activated_by_user)
5903 {
5904 window->AutoPosLastDirection = ImGuiDir_None;
5905 if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api)
5906 window->Pos = window->PosFloat = g.CurrentPopupStack.back().OpenPopupPos;
5907 }
5908
5909 // Position child window
5910 if (flags & ImGuiWindowFlags_ChildWindow)
5911 {
5912 window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size;
5913 parent_window->DC.ChildWindows.push_back(window);
5914 if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
5915 window->Pos = window->PosFloat = parent_window->DC.CursorPos;
5916 }
5917
5918 const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0);
5919 if (window_pos_with_pivot)
5920 SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); // Position given a pivot (e.g. for centering)
5921 else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
5922 window->PosFloat = FindBestWindowPosForPopup(window);
5923 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
5924 window->PosFloat = FindBestWindowPosForPopup(window);
5925 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
5926 window->PosFloat = FindBestWindowPosForPopup(window);
5927
5928 // Clamp position so it stays visible
5929 if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
5930 {
5931 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.
5932 {
5933 ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
5934 window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size;
5935 window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding);
5936 }
5937 }
5938 window->Pos = ImFloor(window->PosFloat);
5939
5940 // Default item width. Make it proportional to window size if window manually resizes
5941 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
5942 window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f);
5943 else
5944 window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f);
5945
5946 // Prepare for focus requests
5947 window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1);
5948 window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1);
5949 window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1;
5950 window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX;
5951
5952 // Apply scrolling
5953 window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
5954 window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
5955
5956 // Apply focus, new windows appears in front
5957 bool want_focus = false;
5958 if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
5959 if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
5960 want_focus = true;
5961
5962 // Handle manual resize: Resize Grips, Borders, Gamepad
5963 int border_held = -1;
5964 ImU32 resize_grip_col[4] = { 0 };
5965 const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4
5966 const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
5967 if (!window->Collapsed)
5968 UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]);
5969
5970 // DRAWING
5971
5972 // Setup draw list and outer clipping rectangle
5973 window->DrawList->Clear();
5974 window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
5975 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
5976 ImRect viewport_rect(GetViewportRect());
5977 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
5978 PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true);
5979 else
5980 PushClipRect(viewport_rect.Min, viewport_rect.Max, true);
5981
5982 // Draw modal window background (darkens what is behind them)
5983 if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostPopupModal())
5984 window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio));
5985
5986 // Draw navigation selection/windowing rectangle background
5987 if (g.NavWindowingTarget == window)
5988 {
5989 ImRect bb = window->Rect();
5990 bb.Expand(g.FontSize);
5991 if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway
5992 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding);
5993 }
5994
5995 // Draw window + handle manual resize
5996 const float window_rounding = window->WindowRounding;
5997 const float window_border_size = window->WindowBorderSize;
5998 const bool title_bar_is_highlight = want_focus || (g.NavWindow && window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight);
5999 const ImRect title_bar_rect = window->TitleBarRect();
6000 if (window->Collapsed)
6001 {
6002 // Title bar only
6003 float backup_border_size = style.FrameBorderSize;
6004 g.Style.FrameBorderSize = window->WindowBorderSize;
6005 ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
6006 RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
6007 g.Style.FrameBorderSize = backup_border_size;
6008 }
6009 else
6010 {
6011 // Window background
6012 ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
6013 if (g.NextWindowData.BgAlphaCond != 0)
6014 {
6015 bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT);
6016 g.NextWindowData.BgAlphaCond = 0;
6017 }
6018 window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot);
6019
6020 // Title bar
6021 ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
6022 if (!(flags & ImGuiWindowFlags_NoTitleBar))
6023 window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top);
6024
6025 // Menu bar
6026 if (flags & ImGuiWindowFlags_MenuBar)
6027 {
6028 ImRect menu_bar_rect = window->MenuBarRect();
6029 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.
6030 window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top);
6031 if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
6032 window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
6033 }
6034
6035 // Scrollbars
6036 if (window->ScrollbarX)
6037 Scrollbar(ImGuiLayoutType_Horizontal);
6038 if (window->ScrollbarY)
6039 Scrollbar(ImGuiLayoutType_Vertical);
6040
6041 // Render resize grips (after their input handling so we don't have a frame of latency)
6042 if (!(flags & ImGuiWindowFlags_NoResize))
6043 {
6044 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
6045 {
6046 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
6047 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
6048 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size)));
6049 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size)));
6050 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);
6051 window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]);
6052 }
6053 }
6054
6055 // Borders
6056 if (window_border_size > 0.0f)
6057 window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size);
6058 if (border_held != -1)
6059 {
6060 ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f);
6061 window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size));
6062 }
6063 if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar))
6064 window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
6065 }
6066
6067 // Draw navigation selection/windowing rectangle border
6068 if (g.NavWindowingTarget == window)
6069 {
6070 float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding);
6071 ImRect bb = window->Rect();
6072 bb.Expand(g.FontSize);
6073 if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward
6074 {
6075 bb.Expand(-g.FontSize - 1.0f);
6076 rounding = window->WindowRounding;
6077 }
6078 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f);
6079 }
6080
6081 // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars.
6082 window->SizeFullAtLastBegin = window->SizeFull;
6083
6084 // Update ContentsRegionMax. All the variable it depends on are set above in this function.
6085 window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x;
6086 window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight();
6087 window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x));
6088 window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y));
6089
6090 // Setup drawing context
6091 // (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.)
6092 window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x;
6093 window->DC.GroupOffsetX = 0.0f;
6094 window->DC.ColumnsOffsetX = 0.0f;
6095 window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y);
6096 window->DC.CursorPos = window->DC.CursorStartPos;
6097 window->DC.CursorPosPrevLine = window->DC.CursorPos;
6098 window->DC.CursorMaxPos = window->DC.CursorStartPos;
6099 window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f;
6100 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
6101 window->DC.NavHideHighlightOneFrame = false;
6102 window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f);
6103 window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext;
6104 window->DC.NavLayerActiveMaskNext = 0x00;
6105 window->DC.MenuBarAppending = false;
6106 window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x);
6107 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
6108 window->DC.ChildWindows.resize(0);
6109 window->DC.LayoutType = ImGuiLayoutType_Vertical;
6110 window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
6111 window->DC.ItemFlags = ImGuiItemFlags_Default_;
6112 window->DC.ItemWidth = window->ItemWidthDefault;
6113 window->DC.TextWrapPos = -1.0f; // disabled
6114 window->DC.ItemFlagsStack.resize(0);
6115 window->DC.ItemWidthStack.resize(0);
6116 window->DC.TextWrapPosStack.resize(0);
6117 window->DC.ColumnsSet = NULL;
6118 window->DC.TreeDepth = 0;
6119 window->DC.TreeDepthMayJumpToParentOnPop = 0x00;
6120 window->DC.StateStorage = &window->StateStorage;
6121 window->DC.GroupStack.resize(0);
6122 window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user);
6123
6124 if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags))
6125 {
6126 window->DC.ItemFlags = parent_window->DC.ItemFlags;
6127 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6128 }
6129
6130 if (window->AutoFitFramesX > 0)
6131 window->AutoFitFramesX--;
6132 if (window->AutoFitFramesY > 0)
6133 window->AutoFitFramesY--;
6134
6135 // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
6136 if (want_focus)
6137 {
6138 FocusWindow(window);
6139 NavInitWindow(window, false);
6140 }
6141
6142 // Title bar
6143 if (!(flags & ImGuiWindowFlags_NoTitleBar))
6144 {
6145 // Close & collapse button are on layer 1 (same as menus) and don't default focus
6146 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
6147 window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
6148 window->DC.NavLayerCurrent++;
6149 window->DC.NavLayerCurrentMask <<= 1;
6150
6151 // Collapse button
6152 if (!(flags & ImGuiWindowFlags_NoCollapse))
6153 {
6154 ImGuiID id = window->GetID("#COLLAPSE");
6155 ImRect bb(window->Pos + style.FramePadding + ImVec2(1,1), window->Pos + style.FramePadding + ImVec2(g.FontSize,g.FontSize) - ImVec2(1,1));
6156 ItemAdd(bb, id); // To allow navigation
6157 if (ButtonBehavior(bb, id, NULL, NULL))
6158 window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function
6159 RenderNavHighlight(bb, id);
6160 RenderArrow(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
6161 }
6162
6163 // Close button
6164 if (p_open != NULL)
6165 {
6166 const float pad = style.FramePadding.y;
6167 const float rad = g.FontSize * 0.5f;
6168 if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1))
6169 *p_open = false;
6170 }
6171
6172 window->DC.NavLayerCurrent--;
6173 window->DC.NavLayerCurrentMask >>= 1;
6174 window->DC.ItemFlags = item_flags_backup;
6175
6176 // Title text (FIXME: refactor text alignment facilities along with RenderText helpers)
6177 ImVec2 text_size = CalcTextSize(name, NULL, true);
6178 ImRect text_r = title_bar_rect;
6179 float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x;
6180 float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x;
6181 if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x);
6182 text_r.Min.x += pad_left;
6183 text_r.Max.x -= pad_right;
6184 ImRect clip_rect = text_r;
6185 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()
6186 RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect);
6187 }
6188
6189 // Save clipped aabb so we can access it in constant-time in FindHoveredWindow()
6190 window->WindowRectClipped = window->Rect();
6191 window->WindowRectClipped.ClipWith(window->ClipRect);
6192
6193 // Pressing CTRL+C while holding on a window copy its content to the clipboard
6194 // 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.
6195 // Maybe we can support CTRL+C on every element?
6196 /*
6197 if (g.ActiveId == move_id)
6198 if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
6199 ImGui::LogToClipboard();
6200 */
6201
6202 // Inner rectangle
6203 // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame
6204 // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior.
6205 window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize;
6206 window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
6207 window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize;
6208 window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize;
6209 //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE);
6210
6211 // Inner clipping rectangle
6212 // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
6213 window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize)));
6214 window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y);
6215 window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize)));
6216 window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y);
6217
6218 // 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.).
6219 window->DC.LastItemId = window->MoveId;
6220 window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0;
6221 window->DC.LastItemRect = title_bar_rect;
6222 }
6223
6224 PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
6225
6226 // 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)
6227 if (first_begin_of_the_frame)
6228 window->WriteAccessed = false;
6229
6230 window->BeginCount++;
6231 g.NextWindowData.SizeConstraintCond = 0;
6232
6233 // Child window can be out of sight and have "negative" clip windows.
6234 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar).
6235 if (flags & ImGuiWindowFlags_ChildWindow)
6236 {
6237 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);
6238 window->Collapsed = parent_window && parent_window->Collapsed;
6239
6240 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
6241 window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y);
6242
6243 // We also hide the window from rendering because we've already added its border to the command list.
6244 // (we could perform the check earlier in the function but it is simpler at this point)
6245 if (window->Collapsed)
6246 window->Active = false;
6247 }
6248 if (style.Alpha <= 0.0f)
6249 window->Active = false;
6250
6251 // Return false if we don't intend to display anything to allow user to perform an early out optimization
6252 window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0;
6253 return !window->SkipItems;
6254 }
6255
6256 // Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead.
6257 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
Begin(const char * name,bool * p_open,const ImVec2 & size_first_use,float bg_alpha_override,ImGuiWindowFlags flags)6258 bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags)
6259 {
6260 // 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.
6261 if (size_first_use.x != 0.0f || size_first_use.y != 0.0f)
6262 ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver);
6263
6264 // Old API feature: override the window background alpha with a parameter.
6265 if (bg_alpha_override >= 0.0f)
6266 ImGui::SetNextWindowBgAlpha(bg_alpha_override);
6267
6268 return ImGui::Begin(name, p_open, flags);
6269 }
6270 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
6271
End()6272 void ImGui::End()
6273 {
6274 ImGuiContext& g = *GImGui;
6275 ImGuiWindow* window = g.CurrentWindow;
6276
6277 if (window->DC.ColumnsSet != NULL)
6278 EndColumns();
6279 PopClipRect(); // Inner window clip rectangle
6280
6281 // Stop logging
6282 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
6283 LogFinish();
6284
6285 // Pop from window stack
6286 g.CurrentWindowStack.pop_back();
6287 if (window->Flags & ImGuiWindowFlags_Popup)
6288 g.CurrentPopupStack.pop_back();
6289 CheckStacksSize(window, false);
6290 SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
6291 }
6292
6293 // Vertical scrollbar
6294 // The entire piece of code below is rather confusing because:
6295 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
6296 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
6297 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
Scrollbar(ImGuiLayoutType direction)6298 void ImGui::Scrollbar(ImGuiLayoutType direction)
6299 {
6300 ImGuiContext& g = *GImGui;
6301 ImGuiWindow* window = g.CurrentWindow;
6302
6303 const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
6304 const ImGuiStyle& style = g.Style;
6305 const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
6306
6307 // Render background
6308 bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
6309 float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
6310 const ImRect window_rect = window->Rect();
6311 const float border_size = window->WindowBorderSize;
6312 ImRect bb = horizontal
6313 ? 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)
6314 : 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);
6315 if (!horizontal)
6316 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
6317 if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
6318 return;
6319
6320 int window_rounding_corners;
6321 if (horizontal)
6322 window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
6323 else
6324 window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
6325 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
6326 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)));
6327
6328 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
6329 float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
6330 float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
6331 float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
6332 float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
6333
6334 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
6335 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
6336 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.
6337 const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
6338 const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
6339 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
6340
6341 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
6342 bool held = false;
6343 bool hovered = false;
6344 const bool previously_held = (g.ActiveId == id);
6345 ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
6346
6347 float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
6348 float scroll_ratio = ImSaturate(scroll_v / scroll_max);
6349 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
6350 if (held && grab_h_norm < 1.0f)
6351 {
6352 float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
6353 float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
6354 float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
6355
6356 // Click position in scrollbar normalized space (0.0f->1.0f)
6357 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
6358 SetHoveredID(id);
6359
6360 bool seek_absolute = false;
6361 if (!previously_held)
6362 {
6363 // On initial click calculate the distance between mouse and the center of the grab
6364 if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
6365 {
6366 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
6367 }
6368 else
6369 {
6370 seek_absolute = true;
6371 *click_delta_to_grab_center_v = 0.0f;
6372 }
6373 }
6374
6375 // Apply scroll
6376 // 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
6377 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));
6378 scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
6379 if (horizontal)
6380 window->Scroll.x = scroll_v;
6381 else
6382 window->Scroll.y = scroll_v;
6383
6384 // Update values for rendering
6385 scroll_ratio = ImSaturate(scroll_v / scroll_max);
6386 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
6387
6388 // Update distance to grab now that we have seeked and saturated
6389 if (seek_absolute)
6390 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
6391 }
6392
6393 // Render
6394 const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
6395 ImRect grab_rect;
6396 if (horizontal)
6397 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);
6398 else
6399 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));
6400 window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
6401 }
6402
BringWindowToFront(ImGuiWindow * window)6403 void ImGui::BringWindowToFront(ImGuiWindow* window)
6404 {
6405 ImGuiContext& g = *GImGui;
6406 ImGuiWindow* current_front_window = g.Windows.back();
6407 if (current_front_window == window || current_front_window->RootWindow == window)
6408 return;
6409 for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window
6410 if (g.Windows[i] == window)
6411 {
6412 g.Windows.erase(g.Windows.Data + i);
6413 g.Windows.push_back(window);
6414 break;
6415 }
6416 }
6417
BringWindowToBack(ImGuiWindow * window)6418 void ImGui::BringWindowToBack(ImGuiWindow* window)
6419 {
6420 ImGuiContext& g = *GImGui;
6421 if (g.Windows[0] == window)
6422 return;
6423 for (int i = 0; i < g.Windows.Size; i++)
6424 if (g.Windows[i] == window)
6425 {
6426 memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));
6427 g.Windows[0] = window;
6428 break;
6429 }
6430 }
6431
6432 // Moving window to front of display and set focus (which happens to be back of our sorted list)
FocusWindow(ImGuiWindow * window)6433 void ImGui::FocusWindow(ImGuiWindow* window)
6434 {
6435 ImGuiContext& g = *GImGui;
6436
6437 if (g.NavWindow != window)
6438 {
6439 g.NavWindow = window;
6440 if (window && g.NavDisableMouseHover)
6441 g.NavMousePosDirty = true;
6442 g.NavInitRequest = false;
6443 g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
6444 g.NavIdIsAlive = false;
6445 g.NavLayer = 0;
6446 //printf("[%05d] FocusWindow(\"%s\")\n", g.FrameCount, window ? window->Name : NULL);
6447 }
6448
6449 // Passing NULL allow to disable keyboard focus
6450 if (!window)
6451 return;
6452
6453 // Move the root window to the top of the pile
6454 if (window->RootWindow)
6455 window = window->RootWindow;
6456
6457 // Steal focus on active widgets
6458 if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it..
6459 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window)
6460 ClearActiveID();
6461
6462 // Bring to front
6463 if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
6464 BringWindowToFront(window);
6465 }
6466
FocusFrontMostActiveWindow(ImGuiWindow * ignore_window)6467 void ImGui::FocusFrontMostActiveWindow(ImGuiWindow* ignore_window)
6468 {
6469 ImGuiContext& g = *GImGui;
6470 for (int i = g.Windows.Size - 1; i >= 0; i--)
6471 if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow))
6472 {
6473 ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]);
6474 FocusWindow(focus_window);
6475 return;
6476 }
6477 }
6478
PushItemWidth(float item_width)6479 void ImGui::PushItemWidth(float item_width)
6480 {
6481 ImGuiWindow* window = GetCurrentWindow();
6482 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
6483 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth);
6484 }
6485
PushMultiItemsWidths(int components,float w_full)6486 void ImGui::PushMultiItemsWidths(int components, float w_full)
6487 {
6488 ImGuiWindow* window = GetCurrentWindow();
6489 const ImGuiStyle& style = GImGui->Style;
6490 if (w_full <= 0.0f)
6491 w_full = CalcItemWidth();
6492 const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
6493 const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
6494 window->DC.ItemWidthStack.push_back(w_item_last);
6495 for (int i = 0; i < components-1; i++)
6496 window->DC.ItemWidthStack.push_back(w_item_one);
6497 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
6498 }
6499
PopItemWidth()6500 void ImGui::PopItemWidth()
6501 {
6502 ImGuiWindow* window = GetCurrentWindow();
6503 window->DC.ItemWidthStack.pop_back();
6504 window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back();
6505 }
6506
CalcItemWidth()6507 float ImGui::CalcItemWidth()
6508 {
6509 ImGuiWindow* window = GetCurrentWindowRead();
6510 float w = window->DC.ItemWidth;
6511 if (w < 0.0f)
6512 {
6513 // 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.
6514 float width_to_right_edge = GetContentRegionAvail().x;
6515 w = ImMax(1.0f, width_to_right_edge + w);
6516 }
6517 w = (float)(int)w;
6518 return w;
6519 }
6520
GetDefaultFont()6521 static ImFont* GetDefaultFont()
6522 {
6523 ImGuiContext& g = *GImGui;
6524 return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0];
6525 }
6526
SetCurrentFont(ImFont * font)6527 void ImGui::SetCurrentFont(ImFont* font)
6528 {
6529 ImGuiContext& g = *GImGui;
6530 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
6531 IM_ASSERT(font->Scale > 0.0f);
6532 g.Font = font;
6533 g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale;
6534 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
6535
6536 ImFontAtlas* atlas = g.Font->ContainerAtlas;
6537 g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
6538 g.DrawListSharedData.Font = g.Font;
6539 g.DrawListSharedData.FontSize = g.FontSize;
6540 }
6541
PushFont(ImFont * font)6542 void ImGui::PushFont(ImFont* font)
6543 {
6544 ImGuiContext& g = *GImGui;
6545 if (!font)
6546 font = GetDefaultFont();
6547 SetCurrentFont(font);
6548 g.FontStack.push_back(font);
6549 g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
6550 }
6551
PopFont()6552 void ImGui::PopFont()
6553 {
6554 ImGuiContext& g = *GImGui;
6555 g.CurrentWindow->DrawList->PopTextureID();
6556 g.FontStack.pop_back();
6557 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
6558 }
6559
PushItemFlag(ImGuiItemFlags option,bool enabled)6560 void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
6561 {
6562 ImGuiWindow* window = GetCurrentWindow();
6563 if (enabled)
6564 window->DC.ItemFlags |= option;
6565 else
6566 window->DC.ItemFlags &= ~option;
6567 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6568 }
6569
PopItemFlag()6570 void ImGui::PopItemFlag()
6571 {
6572 ImGuiWindow* window = GetCurrentWindow();
6573 window->DC.ItemFlagsStack.pop_back();
6574 window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back();
6575 }
6576
PushAllowKeyboardFocus(bool allow_keyboard_focus)6577 void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
6578 {
6579 PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus);
6580 }
6581
PopAllowKeyboardFocus()6582 void ImGui::PopAllowKeyboardFocus()
6583 {
6584 PopItemFlag();
6585 }
6586
PushButtonRepeat(bool repeat)6587 void ImGui::PushButtonRepeat(bool repeat)
6588 {
6589 PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat);
6590 }
6591
PopButtonRepeat()6592 void ImGui::PopButtonRepeat()
6593 {
6594 PopItemFlag();
6595 }
6596
PushTextWrapPos(float wrap_pos_x)6597 void ImGui::PushTextWrapPos(float wrap_pos_x)
6598 {
6599 ImGuiWindow* window = GetCurrentWindow();
6600 window->DC.TextWrapPos = wrap_pos_x;
6601 window->DC.TextWrapPosStack.push_back(wrap_pos_x);
6602 }
6603
PopTextWrapPos()6604 void ImGui::PopTextWrapPos()
6605 {
6606 ImGuiWindow* window = GetCurrentWindow();
6607 window->DC.TextWrapPosStack.pop_back();
6608 window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back();
6609 }
6610
6611 // 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)6612 void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
6613 {
6614 ImGuiContext& g = *GImGui;
6615 ImGuiColMod backup;
6616 backup.Col = idx;
6617 backup.BackupValue = g.Style.Colors[idx];
6618 g.ColorModifiers.push_back(backup);
6619 g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
6620 }
6621
PushStyleColor(ImGuiCol idx,const ImVec4 & col)6622 void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
6623 {
6624 ImGuiContext& g = *GImGui;
6625 ImGuiColMod backup;
6626 backup.Col = idx;
6627 backup.BackupValue = g.Style.Colors[idx];
6628 g.ColorModifiers.push_back(backup);
6629 g.Style.Colors[idx] = col;
6630 }
6631
PopStyleColor(int count)6632 void ImGui::PopStyleColor(int count)
6633 {
6634 ImGuiContext& g = *GImGui;
6635 while (count > 0)
6636 {
6637 ImGuiColMod& backup = g.ColorModifiers.back();
6638 g.Style.Colors[backup.Col] = backup.BackupValue;
6639 g.ColorModifiers.pop_back();
6640 count--;
6641 }
6642 }
6643
6644 struct ImGuiStyleVarInfo
6645 {
6646 ImGuiDataType Type;
6647 ImU32 Count;
6648 ImU32 Offset;
GetVarPtrImGuiStyleVarInfo6649 void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
6650 };
6651
6652 static const ImGuiStyleVarInfo GStyleVarInfo[] =
6653 {
6654 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha
6655 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding
6656 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding
6657 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize
6658 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize
6659 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign
6660 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding
6661 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize
6662 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding
6663 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize
6664 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding
6665 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding
6666 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize
6667 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing
6668 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing
6669 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing
6670 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize
6671 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding
6672 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize
6673 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding
6674 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
6675 };
6676
GetStyleVarInfo(ImGuiStyleVar idx)6677 static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
6678 {
6679 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
6680 IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
6681 return &GStyleVarInfo[idx];
6682 }
6683
PushStyleVar(ImGuiStyleVar idx,float val)6684 void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
6685 {
6686 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
6687 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1)
6688 {
6689 ImGuiContext& g = *GImGui;
6690 float* pvar = (float*)var_info->GetVarPtr(&g.Style);
6691 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
6692 *pvar = val;
6693 return;
6694 }
6695 IM_ASSERT(0); // Called function with wrong-type? Variable is not a float.
6696 }
6697
PushStyleVar(ImGuiStyleVar idx,const ImVec2 & val)6698 void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
6699 {
6700 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
6701 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
6702 {
6703 ImGuiContext& g = *GImGui;
6704 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
6705 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
6706 *pvar = val;
6707 return;
6708 }
6709 IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2.
6710 }
6711
PopStyleVar(int count)6712 void ImGui::PopStyleVar(int count)
6713 {
6714 ImGuiContext& g = *GImGui;
6715 while (count > 0)
6716 {
6717 // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
6718 ImGuiStyleMod& backup = g.StyleModifiers.back();
6719 const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
6720 void* data = info->GetVarPtr(&g.Style);
6721 if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; }
6722 else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
6723 g.StyleModifiers.pop_back();
6724 count--;
6725 }
6726 }
6727
GetStyleColorName(ImGuiCol idx)6728 const char* ImGui::GetStyleColorName(ImGuiCol idx)
6729 {
6730 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
6731 switch (idx)
6732 {
6733 case ImGuiCol_Text: return "Text";
6734 case ImGuiCol_TextDisabled: return "TextDisabled";
6735 case ImGuiCol_WindowBg: return "WindowBg";
6736 case ImGuiCol_ChildBg: return "ChildBg";
6737 case ImGuiCol_PopupBg: return "PopupBg";
6738 case ImGuiCol_Border: return "Border";
6739 case ImGuiCol_BorderShadow: return "BorderShadow";
6740 case ImGuiCol_FrameBg: return "FrameBg";
6741 case ImGuiCol_FrameBgHovered: return "FrameBgHovered";
6742 case ImGuiCol_FrameBgActive: return "FrameBgActive";
6743 case ImGuiCol_TitleBg: return "TitleBg";
6744 case ImGuiCol_TitleBgActive: return "TitleBgActive";
6745 case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed";
6746 case ImGuiCol_MenuBarBg: return "MenuBarBg";
6747 case ImGuiCol_ScrollbarBg: return "ScrollbarBg";
6748 case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab";
6749 case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
6750 case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
6751 case ImGuiCol_CheckMark: return "CheckMark";
6752 case ImGuiCol_SliderGrab: return "SliderGrab";
6753 case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
6754 case ImGuiCol_Button: return "Button";
6755 case ImGuiCol_ButtonHovered: return "ButtonHovered";
6756 case ImGuiCol_ButtonActive: return "ButtonActive";
6757 case ImGuiCol_Header: return "Header";
6758 case ImGuiCol_HeaderHovered: return "HeaderHovered";
6759 case ImGuiCol_HeaderActive: return "HeaderActive";
6760 case ImGuiCol_Separator: return "Separator";
6761 case ImGuiCol_SeparatorHovered: return "SeparatorHovered";
6762 case ImGuiCol_SeparatorActive: return "SeparatorActive";
6763 case ImGuiCol_ResizeGrip: return "ResizeGrip";
6764 case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered";
6765 case ImGuiCol_ResizeGripActive: return "ResizeGripActive";
6766 case ImGuiCol_PlotLines: return "PlotLines";
6767 case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
6768 case ImGuiCol_PlotHistogram: return "PlotHistogram";
6769 case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
6770 case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
6771 case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening";
6772 case ImGuiCol_DragDropTarget: return "DragDropTarget";
6773 case ImGuiCol_NavHighlight: return "NavHighlight";
6774 case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
6775 }
6776 IM_ASSERT(0);
6777 return "Unknown";
6778 }
6779
IsWindowChildOf(ImGuiWindow * window,ImGuiWindow * potential_parent)6780 bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
6781 {
6782 if (window->RootWindow == potential_parent)
6783 return true;
6784 while (window != NULL)
6785 {
6786 if (window == potential_parent)
6787 return true;
6788 window = window->ParentWindow;
6789 }
6790 return false;
6791 }
6792
IsWindowHovered(ImGuiHoveredFlags flags)6793 bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
6794 {
6795 IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function
6796 ImGuiContext& g = *GImGui;
6797
6798 if (flags & ImGuiHoveredFlags_AnyWindow)
6799 {
6800 if (g.HoveredWindow == NULL)
6801 return false;
6802 }
6803 else
6804 {
6805 switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows))
6806 {
6807 case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows:
6808 if (g.HoveredRootWindow != g.CurrentWindow->RootWindow)
6809 return false;
6810 break;
6811 case ImGuiHoveredFlags_RootWindow:
6812 if (g.HoveredWindow != g.CurrentWindow->RootWindow)
6813 return false;
6814 break;
6815 case ImGuiHoveredFlags_ChildWindows:
6816 if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow))
6817 return false;
6818 break;
6819 default:
6820 if (g.HoveredWindow != g.CurrentWindow)
6821 return false;
6822 break;
6823 }
6824 }
6825
6826 if (!IsWindowContentHoverable(g.HoveredRootWindow, flags))
6827 return false;
6828 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
6829 if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId)
6830 return false;
6831 return true;
6832 }
6833
IsWindowFocused(ImGuiFocusedFlags flags)6834 bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
6835 {
6836 ImGuiContext& g = *GImGui;
6837 IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End()
6838
6839 if (flags & ImGuiFocusedFlags_AnyWindow)
6840 return g.NavWindow != NULL;
6841
6842 switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows))
6843 {
6844 case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows:
6845 return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow;
6846 case ImGuiFocusedFlags_RootWindow:
6847 return g.NavWindow == g.CurrentWindow->RootWindow;
6848 case ImGuiFocusedFlags_ChildWindows:
6849 return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow);
6850 default:
6851 return g.NavWindow == g.CurrentWindow;
6852 }
6853 }
6854
6855 // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
IsWindowNavFocusable(ImGuiWindow * window)6856 bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
6857 {
6858 ImGuiContext& g = *GImGui;
6859 return window->Active && window == window->RootWindowForTabbing && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow);
6860 }
6861
GetWindowWidth()6862 float ImGui::GetWindowWidth()
6863 {
6864 ImGuiWindow* window = GImGui->CurrentWindow;
6865 return window->Size.x;
6866 }
6867
GetWindowHeight()6868 float ImGui::GetWindowHeight()
6869 {
6870 ImGuiWindow* window = GImGui->CurrentWindow;
6871 return window->Size.y;
6872 }
6873
GetWindowPos()6874 ImVec2 ImGui::GetWindowPos()
6875 {
6876 ImGuiContext& g = *GImGui;
6877 ImGuiWindow* window = g.CurrentWindow;
6878 return window->Pos;
6879 }
6880
SetWindowScrollX(ImGuiWindow * window,float new_scroll_x)6881 static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x)
6882 {
6883 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.
6884 window->Scroll.x = new_scroll_x;
6885 window->DC.CursorMaxPos.x -= window->Scroll.x;
6886 }
6887
SetWindowScrollY(ImGuiWindow * window,float new_scroll_y)6888 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y)
6889 {
6890 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.
6891 window->Scroll.y = new_scroll_y;
6892 window->DC.CursorMaxPos.y -= window->Scroll.y;
6893 }
6894
SetWindowPos(ImGuiWindow * window,const ImVec2 & pos,ImGuiCond cond)6895 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
6896 {
6897 // Test condition (NB: bit 0 is always true) and clear flags for next time
6898 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
6899 return;
6900
6901 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
6902 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
6903 window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
6904
6905 // Set
6906 const ImVec2 old_pos = window->Pos;
6907 window->PosFloat = pos;
6908 window->Pos = ImFloor(pos);
6909 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
6910 window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected.
6911 }
6912
SetWindowPos(const ImVec2 & pos,ImGuiCond cond)6913 void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
6914 {
6915 ImGuiWindow* window = GetCurrentWindowRead();
6916 SetWindowPos(window, pos, cond);
6917 }
6918
SetWindowPos(const char * name,const ImVec2 & pos,ImGuiCond cond)6919 void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
6920 {
6921 if (ImGuiWindow* window = FindWindowByName(name))
6922 SetWindowPos(window, pos, cond);
6923 }
6924
GetWindowSize()6925 ImVec2 ImGui::GetWindowSize()
6926 {
6927 ImGuiWindow* window = GetCurrentWindowRead();
6928 return window->Size;
6929 }
6930
SetWindowSize(ImGuiWindow * window,const ImVec2 & size,ImGuiCond cond)6931 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
6932 {
6933 // Test condition (NB: bit 0 is always true) and clear flags for next time
6934 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
6935 return;
6936
6937 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
6938 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
6939
6940 // Set
6941 if (size.x > 0.0f)
6942 {
6943 window->AutoFitFramesX = 0;
6944 window->SizeFull.x = size.x;
6945 }
6946 else
6947 {
6948 window->AutoFitFramesX = 2;
6949 window->AutoFitOnlyGrows = false;
6950 }
6951 if (size.y > 0.0f)
6952 {
6953 window->AutoFitFramesY = 0;
6954 window->SizeFull.y = size.y;
6955 }
6956 else
6957 {
6958 window->AutoFitFramesY = 2;
6959 window->AutoFitOnlyGrows = false;
6960 }
6961 }
6962
SetWindowSize(const ImVec2 & size,ImGuiCond cond)6963 void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
6964 {
6965 SetWindowSize(GImGui->CurrentWindow, size, cond);
6966 }
6967
SetWindowSize(const char * name,const ImVec2 & size,ImGuiCond cond)6968 void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
6969 {
6970 if (ImGuiWindow* window = FindWindowByName(name))
6971 SetWindowSize(window, size, cond);
6972 }
6973
SetWindowCollapsed(ImGuiWindow * window,bool collapsed,ImGuiCond cond)6974 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
6975 {
6976 // Test condition (NB: bit 0 is always true) and clear flags for next time
6977 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
6978 return;
6979 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
6980
6981 // Set
6982 window->Collapsed = collapsed;
6983 }
6984
SetWindowCollapsed(bool collapsed,ImGuiCond cond)6985 void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
6986 {
6987 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
6988 }
6989
IsWindowCollapsed()6990 bool ImGui::IsWindowCollapsed()
6991 {
6992 ImGuiWindow* window = GetCurrentWindowRead();
6993 return window->Collapsed;
6994 }
6995
IsWindowAppearing()6996 bool ImGui::IsWindowAppearing()
6997 {
6998 ImGuiWindow* window = GetCurrentWindowRead();
6999 return window->Appearing;
7000 }
7001
SetWindowCollapsed(const char * name,bool collapsed,ImGuiCond cond)7002 void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
7003 {
7004 if (ImGuiWindow* window = FindWindowByName(name))
7005 SetWindowCollapsed(window, collapsed, cond);
7006 }
7007
SetWindowFocus()7008 void ImGui::SetWindowFocus()
7009 {
7010 FocusWindow(GImGui->CurrentWindow);
7011 }
7012
SetWindowFocus(const char * name)7013 void ImGui::SetWindowFocus(const char* name)
7014 {
7015 if (name)
7016 {
7017 if (ImGuiWindow* window = FindWindowByName(name))
7018 FocusWindow(window);
7019 }
7020 else
7021 {
7022 FocusWindow(NULL);
7023 }
7024 }
7025
SetNextWindowPos(const ImVec2 & pos,ImGuiCond cond,const ImVec2 & pivot)7026 void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
7027 {
7028 ImGuiContext& g = *GImGui;
7029 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7030 g.NextWindowData.PosVal = pos;
7031 g.NextWindowData.PosPivotVal = pivot;
7032 g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
7033 }
7034
SetNextWindowSize(const ImVec2 & size,ImGuiCond cond)7035 void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
7036 {
7037 ImGuiContext& g = *GImGui;
7038 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7039 g.NextWindowData.SizeVal = size;
7040 g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
7041 }
7042
SetNextWindowSizeConstraints(const ImVec2 & size_min,const ImVec2 & size_max,ImGuiSizeCallback custom_callback,void * custom_callback_user_data)7043 void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
7044 {
7045 ImGuiContext& g = *GImGui;
7046 g.NextWindowData.SizeConstraintCond = ImGuiCond_Always;
7047 g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
7048 g.NextWindowData.SizeCallback = custom_callback;
7049 g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
7050 }
7051
SetNextWindowContentSize(const ImVec2 & size)7052 void ImGui::SetNextWindowContentSize(const ImVec2& size)
7053 {
7054 ImGuiContext& g = *GImGui;
7055 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.
7056 g.NextWindowData.ContentSizeCond = ImGuiCond_Always;
7057 }
7058
SetNextWindowCollapsed(bool collapsed,ImGuiCond cond)7059 void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
7060 {
7061 ImGuiContext& g = *GImGui;
7062 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7063 g.NextWindowData.CollapsedVal = collapsed;
7064 g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
7065 }
7066
SetNextWindowFocus()7067 void ImGui::SetNextWindowFocus()
7068 {
7069 ImGuiContext& g = *GImGui;
7070 g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
7071 }
7072
SetNextWindowBgAlpha(float alpha)7073 void ImGui::SetNextWindowBgAlpha(float alpha)
7074 {
7075 ImGuiContext& g = *GImGui;
7076 g.NextWindowData.BgAlphaVal = alpha;
7077 g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
7078 }
7079
7080 // In window space (not screen space!)
GetContentRegionMax()7081 ImVec2 ImGui::GetContentRegionMax()
7082 {
7083 ImGuiWindow* window = GetCurrentWindowRead();
7084 ImVec2 mx = window->ContentsRegionRect.Max;
7085 if (window->DC.ColumnsSet)
7086 mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x;
7087 return mx;
7088 }
7089
GetContentRegionAvail()7090 ImVec2 ImGui::GetContentRegionAvail()
7091 {
7092 ImGuiWindow* window = GetCurrentWindowRead();
7093 return GetContentRegionMax() - (window->DC.CursorPos - window->Pos);
7094 }
7095
GetContentRegionAvailWidth()7096 float ImGui::GetContentRegionAvailWidth()
7097 {
7098 return GetContentRegionAvail().x;
7099 }
7100
7101 // In window space (not screen space!)
GetWindowContentRegionMin()7102 ImVec2 ImGui::GetWindowContentRegionMin()
7103 {
7104 ImGuiWindow* window = GetCurrentWindowRead();
7105 return window->ContentsRegionRect.Min;
7106 }
7107
GetWindowContentRegionMax()7108 ImVec2 ImGui::GetWindowContentRegionMax()
7109 {
7110 ImGuiWindow* window = GetCurrentWindowRead();
7111 return window->ContentsRegionRect.Max;
7112 }
7113
GetWindowContentRegionWidth()7114 float ImGui::GetWindowContentRegionWidth()
7115 {
7116 ImGuiWindow* window = GetCurrentWindowRead();
7117 return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x;
7118 }
7119
GetTextLineHeight()7120 float ImGui::GetTextLineHeight()
7121 {
7122 ImGuiContext& g = *GImGui;
7123 return g.FontSize;
7124 }
7125
GetTextLineHeightWithSpacing()7126 float ImGui::GetTextLineHeightWithSpacing()
7127 {
7128 ImGuiContext& g = *GImGui;
7129 return g.FontSize + g.Style.ItemSpacing.y;
7130 }
7131
GetFrameHeight()7132 float ImGui::GetFrameHeight()
7133 {
7134 ImGuiContext& g = *GImGui;
7135 return g.FontSize + g.Style.FramePadding.y * 2.0f;
7136 }
7137
GetFrameHeightWithSpacing()7138 float ImGui::GetFrameHeightWithSpacing()
7139 {
7140 ImGuiContext& g = *GImGui;
7141 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
7142 }
7143
GetWindowDrawList()7144 ImDrawList* ImGui::GetWindowDrawList()
7145 {
7146 ImGuiWindow* window = GetCurrentWindow();
7147 return window->DrawList;
7148 }
7149
GetFont()7150 ImFont* ImGui::GetFont()
7151 {
7152 return GImGui->Font;
7153 }
7154
GetFontSize()7155 float ImGui::GetFontSize()
7156 {
7157 return GImGui->FontSize;
7158 }
7159
GetFontTexUvWhitePixel()7160 ImVec2 ImGui::GetFontTexUvWhitePixel()
7161 {
7162 return GImGui->DrawListSharedData.TexUvWhitePixel;
7163 }
7164
SetWindowFontScale(float scale)7165 void ImGui::SetWindowFontScale(float scale)
7166 {
7167 ImGuiContext& g = *GImGui;
7168 ImGuiWindow* window = GetCurrentWindow();
7169 window->FontWindowScale = scale;
7170 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
7171 }
7172
7173 // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
7174 // 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()7175 ImVec2 ImGui::GetCursorPos()
7176 {
7177 ImGuiWindow* window = GetCurrentWindowRead();
7178 return window->DC.CursorPos - window->Pos + window->Scroll;
7179 }
7180
GetCursorPosX()7181 float ImGui::GetCursorPosX()
7182 {
7183 ImGuiWindow* window = GetCurrentWindowRead();
7184 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
7185 }
7186
GetCursorPosY()7187 float ImGui::GetCursorPosY()
7188 {
7189 ImGuiWindow* window = GetCurrentWindowRead();
7190 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
7191 }
7192
SetCursorPos(const ImVec2 & local_pos)7193 void ImGui::SetCursorPos(const ImVec2& local_pos)
7194 {
7195 ImGuiWindow* window = GetCurrentWindow();
7196 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
7197 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7198 }
7199
SetCursorPosX(float x)7200 void ImGui::SetCursorPosX(float x)
7201 {
7202 ImGuiWindow* window = GetCurrentWindow();
7203 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
7204 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
7205 }
7206
SetCursorPosY(float y)7207 void ImGui::SetCursorPosY(float y)
7208 {
7209 ImGuiWindow* window = GetCurrentWindow();
7210 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
7211 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
7212 }
7213
GetCursorStartPos()7214 ImVec2 ImGui::GetCursorStartPos()
7215 {
7216 ImGuiWindow* window = GetCurrentWindowRead();
7217 return window->DC.CursorStartPos - window->Pos;
7218 }
7219
GetCursorScreenPos()7220 ImVec2 ImGui::GetCursorScreenPos()
7221 {
7222 ImGuiWindow* window = GetCurrentWindowRead();
7223 return window->DC.CursorPos;
7224 }
7225
SetCursorScreenPos(const ImVec2 & screen_pos)7226 void ImGui::SetCursorScreenPos(const ImVec2& screen_pos)
7227 {
7228 ImGuiWindow* window = GetCurrentWindow();
7229 window->DC.CursorPos = screen_pos;
7230 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7231 }
7232
GetScrollX()7233 float ImGui::GetScrollX()
7234 {
7235 return GImGui->CurrentWindow->Scroll.x;
7236 }
7237
GetScrollY()7238 float ImGui::GetScrollY()
7239 {
7240 return GImGui->CurrentWindow->Scroll.y;
7241 }
7242
GetScrollMaxX()7243 float ImGui::GetScrollMaxX()
7244 {
7245 return GetScrollMaxX(GImGui->CurrentWindow);
7246 }
7247
GetScrollMaxY()7248 float ImGui::GetScrollMaxY()
7249 {
7250 return GetScrollMaxY(GImGui->CurrentWindow);
7251 }
7252
SetScrollX(float scroll_x)7253 void ImGui::SetScrollX(float scroll_x)
7254 {
7255 ImGuiWindow* window = GetCurrentWindow();
7256 window->ScrollTarget.x = scroll_x;
7257 window->ScrollTargetCenterRatio.x = 0.0f;
7258 }
7259
SetScrollY(float scroll_y)7260 void ImGui::SetScrollY(float scroll_y)
7261 {
7262 ImGuiWindow* window = GetCurrentWindow();
7263 window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY
7264 window->ScrollTargetCenterRatio.y = 0.0f;
7265 }
7266
SetScrollFromPosY(float pos_y,float center_y_ratio)7267 void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio)
7268 {
7269 // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
7270 ImGuiWindow* window = GetCurrentWindow();
7271 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
7272 window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y);
7273 window->ScrollTargetCenterRatio.y = center_y_ratio;
7274
7275 // Minor hack to to make scrolling to top/bottom of window take account of WindowPadding, it looks more right to the user this way
7276 if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y)
7277 window->ScrollTarget.y = 0.0f;
7278 else if (center_y_ratio >= 1.0f && window->ScrollTarget.y >= window->SizeContents.y - window->WindowPadding.y + GImGui->Style.ItemSpacing.y)
7279 window->ScrollTarget.y = window->SizeContents.y;
7280 }
7281
7282 // 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)7283 void ImGui::SetScrollHere(float center_y_ratio)
7284 {
7285 ImGuiWindow* window = GetCurrentWindow();
7286 float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space
7287 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.
7288 SetScrollFromPosY(target_y, center_y_ratio);
7289 }
7290
ActivateItem(ImGuiID id)7291 void ImGui::ActivateItem(ImGuiID id)
7292 {
7293 ImGuiContext& g = *GImGui;
7294 g.NavNextActivateId = id;
7295 }
7296
SetKeyboardFocusHere(int offset)7297 void ImGui::SetKeyboardFocusHere(int offset)
7298 {
7299 IM_ASSERT(offset >= -1); // -1 is allowed but not below
7300 ImGuiWindow* window = GetCurrentWindow();
7301 window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset;
7302 window->FocusIdxTabRequestNext = INT_MAX;
7303 }
7304
SetItemDefaultFocus()7305 void ImGui::SetItemDefaultFocus()
7306 {
7307 ImGuiContext& g = *GImGui;
7308 ImGuiWindow* window = g.CurrentWindow;
7309 if (!window->Appearing)
7310 return;
7311 if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent)
7312 {
7313 g.NavInitRequest = false;
7314 g.NavInitResultId = g.NavWindow->DC.LastItemId;
7315 g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
7316 NavUpdateAnyRequestFlag();
7317 if (!IsItemVisible())
7318 SetScrollHere();
7319 }
7320 }
7321
SetStateStorage(ImGuiStorage * tree)7322 void ImGui::SetStateStorage(ImGuiStorage* tree)
7323 {
7324 ImGuiWindow* window = GetCurrentWindow();
7325 window->DC.StateStorage = tree ? tree : &window->StateStorage;
7326 }
7327
GetStateStorage()7328 ImGuiStorage* ImGui::GetStateStorage()
7329 {
7330 ImGuiWindow* window = GetCurrentWindowRead();
7331 return window->DC.StateStorage;
7332 }
7333
TextV(const char * fmt,va_list args)7334 void ImGui::TextV(const char* fmt, va_list args)
7335 {
7336 ImGuiWindow* window = GetCurrentWindow();
7337 if (window->SkipItems)
7338 return;
7339
7340 ImGuiContext& g = *GImGui;
7341 const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
7342 TextUnformatted(g.TempBuffer, text_end);
7343 }
7344
Text(const char * fmt,...)7345 void ImGui::Text(const char* fmt, ...)
7346 {
7347 va_list args;
7348 va_start(args, fmt);
7349 TextV(fmt, args);
7350 va_end(args);
7351 }
7352
TextColoredV(const ImVec4 & col,const char * fmt,va_list args)7353 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
7354 {
7355 PushStyleColor(ImGuiCol_Text, col);
7356 TextV(fmt, args);
7357 PopStyleColor();
7358 }
7359
TextColored(const ImVec4 & col,const char * fmt,...)7360 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
7361 {
7362 va_list args;
7363 va_start(args, fmt);
7364 TextColoredV(col, fmt, args);
7365 va_end(args);
7366 }
7367
TextDisabledV(const char * fmt,va_list args)7368 void ImGui::TextDisabledV(const char* fmt, va_list args)
7369 {
7370 PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
7371 TextV(fmt, args);
7372 PopStyleColor();
7373 }
7374
TextDisabled(const char * fmt,...)7375 void ImGui::TextDisabled(const char* fmt, ...)
7376 {
7377 va_list args;
7378 va_start(args, fmt);
7379 TextDisabledV(fmt, args);
7380 va_end(args);
7381 }
7382
TextWrappedV(const char * fmt,va_list args)7383 void ImGui::TextWrappedV(const char* fmt, va_list args)
7384 {
7385 bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
7386 if (need_wrap) PushTextWrapPos(0.0f);
7387 TextV(fmt, args);
7388 if (need_wrap) PopTextWrapPos();
7389 }
7390
TextWrapped(const char * fmt,...)7391 void ImGui::TextWrapped(const char* fmt, ...)
7392 {
7393 va_list args;
7394 va_start(args, fmt);
7395 TextWrappedV(fmt, args);
7396 va_end(args);
7397 }
7398
TextUnformatted(const char * text,const char * text_end)7399 void ImGui::TextUnformatted(const char* text, const char* text_end)
7400 {
7401 ImGuiWindow* window = GetCurrentWindow();
7402 if (window->SkipItems)
7403 return;
7404
7405 ImGuiContext& g = *GImGui;
7406 IM_ASSERT(text != NULL);
7407 const char* text_begin = text;
7408 if (text_end == NULL)
7409 text_end = text + strlen(text); // FIXME-OPT
7410
7411 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
7412 const float wrap_pos_x = window->DC.TextWrapPos;
7413 const bool wrap_enabled = wrap_pos_x >= 0.0f;
7414 if (text_end - text > 2000 && !wrap_enabled)
7415 {
7416 // Long text!
7417 // Perform manual coarse clipping to optimize for long multi-line text
7418 // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
7419 // 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.
7420 const char* line = text;
7421 const float line_height = GetTextLineHeight();
7422 const ImRect clip_rect = window->ClipRect;
7423 ImVec2 text_size(0,0);
7424
7425 if (text_pos.y <= clip_rect.Max.y)
7426 {
7427 ImVec2 pos = text_pos;
7428
7429 // Lines to skip (can't skip when logging text)
7430 if (!g.LogEnabled)
7431 {
7432 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
7433 if (lines_skippable > 0)
7434 {
7435 int lines_skipped = 0;
7436 while (line < text_end && lines_skipped < lines_skippable)
7437 {
7438 const char* line_end = strchr(line, '\n');
7439 if (!line_end)
7440 line_end = text_end;
7441 line = line_end + 1;
7442 lines_skipped++;
7443 }
7444 pos.y += lines_skipped * line_height;
7445 }
7446 }
7447
7448 // Lines to render
7449 if (line < text_end)
7450 {
7451 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
7452 while (line < text_end)
7453 {
7454 const char* line_end = strchr(line, '\n');
7455 if (IsClippedEx(line_rect, 0, false))
7456 break;
7457
7458 const ImVec2 line_size = CalcTextSize(line, line_end, false);
7459 text_size.x = ImMax(text_size.x, line_size.x);
7460 RenderText(pos, line, line_end, false);
7461 if (!line_end)
7462 line_end = text_end;
7463 line = line_end + 1;
7464 line_rect.Min.y += line_height;
7465 line_rect.Max.y += line_height;
7466 pos.y += line_height;
7467 }
7468
7469 // Count remaining lines
7470 int lines_skipped = 0;
7471 while (line < text_end)
7472 {
7473 const char* line_end = strchr(line, '\n');
7474 if (!line_end)
7475 line_end = text_end;
7476 line = line_end + 1;
7477 lines_skipped++;
7478 }
7479 pos.y += lines_skipped * line_height;
7480 }
7481
7482 text_size.y += (pos - text_pos).y;
7483 }
7484
7485 ImRect bb(text_pos, text_pos + text_size);
7486 ItemSize(bb);
7487 ItemAdd(bb, 0);
7488 }
7489 else
7490 {
7491 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
7492 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
7493
7494 // Account of baseline offset
7495 ImRect bb(text_pos, text_pos + text_size);
7496 ItemSize(text_size);
7497 if (!ItemAdd(bb, 0))
7498 return;
7499
7500 // Render (we don't hide text after ## in this end-user function)
7501 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
7502 }
7503 }
7504
AlignTextToFramePadding()7505 void ImGui::AlignTextToFramePadding()
7506 {
7507 ImGuiWindow* window = GetCurrentWindow();
7508 if (window->SkipItems)
7509 return;
7510
7511 ImGuiContext& g = *GImGui;
7512 window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2);
7513 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
7514 }
7515
7516 // Add a label+text combo aligned to other label+value widgets
LabelTextV(const char * label,const char * fmt,va_list args)7517 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
7518 {
7519 ImGuiWindow* window = GetCurrentWindow();
7520 if (window->SkipItems)
7521 return;
7522
7523 ImGuiContext& g = *GImGui;
7524 const ImGuiStyle& style = g.Style;
7525 const float w = CalcItemWidth();
7526
7527 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7528 const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
7529 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);
7530 ItemSize(total_bb, style.FramePadding.y);
7531 if (!ItemAdd(total_bb, 0))
7532 return;
7533
7534 // Render
7535 const char* value_text_begin = &g.TempBuffer[0];
7536 const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
7537 RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
7538 if (label_size.x > 0.0f)
7539 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
7540 }
7541
LabelText(const char * label,const char * fmt,...)7542 void ImGui::LabelText(const char* label, const char* fmt, ...)
7543 {
7544 va_list args;
7545 va_start(args, fmt);
7546 LabelTextV(label, fmt, args);
7547 va_end(args);
7548 }
7549
ButtonBehavior(const ImRect & bb,ImGuiID id,bool * out_hovered,bool * out_held,ImGuiButtonFlags flags)7550 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
7551 {
7552 ImGuiContext& g = *GImGui;
7553 ImGuiWindow* window = GetCurrentWindow();
7554
7555 if (flags & ImGuiButtonFlags_Disabled)
7556 {
7557 if (out_hovered) *out_hovered = false;
7558 if (out_held) *out_held = false;
7559 if (g.ActiveId == id) ClearActiveID();
7560 return false;
7561 }
7562
7563 // Default behavior requires click+release on same spot
7564 if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
7565 flags |= ImGuiButtonFlags_PressedOnClickRelease;
7566
7567 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
7568 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
7569 g.HoveredWindow = window;
7570
7571 bool pressed = false;
7572 bool hovered = ItemHoverable(bb, id);
7573
7574 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
7575 if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && g.DragDropActive && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
7576 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
7577 {
7578 hovered = true;
7579 SetHoveredID(id);
7580 if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
7581 {
7582 pressed = true;
7583 FocusWindow(window);
7584 }
7585 }
7586
7587 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
7588 g.HoveredWindow = backup_hovered_window;
7589
7590 // 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.
7591 if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
7592 hovered = false;
7593
7594 // Mouse
7595 if (hovered)
7596 {
7597 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
7598 {
7599 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
7600 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
7601 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
7602 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
7603 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
7604 // FIXME-NAV: We don't honor those different behaviors.
7605 if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
7606 {
7607 SetActiveID(id, window);
7608 if (!(flags & ImGuiButtonFlags_NoNavFocus))
7609 SetFocusID(id, window);
7610 FocusWindow(window);
7611 }
7612 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
7613 {
7614 pressed = true;
7615 if (flags & ImGuiButtonFlags_NoHoldingActiveID)
7616 ClearActiveID();
7617 else
7618 SetActiveID(id, window); // Hold on ID
7619 FocusWindow(window);
7620 }
7621 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
7622 {
7623 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
7624 pressed = true;
7625 ClearActiveID();
7626 }
7627
7628 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
7629 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
7630 if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
7631 pressed = true;
7632 }
7633
7634 if (pressed)
7635 g.NavDisableHighlight = true;
7636 }
7637
7638 // Gamepad/Keyboard navigation
7639 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
7640 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
7641 hovered = true;
7642
7643 if (g.NavActivateDownId == id)
7644 {
7645 bool nav_activated_by_code = (g.NavActivateId == id);
7646 bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
7647 if (nav_activated_by_code || nav_activated_by_inputs)
7648 pressed = true;
7649 if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
7650 {
7651 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
7652 g.NavActivateId = id; // This is so SetActiveId assign a Nav source
7653 SetActiveID(id, window);
7654 if (!(flags & ImGuiButtonFlags_NoNavFocus))
7655 SetFocusID(id, window);
7656 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
7657 }
7658 }
7659
7660 bool held = false;
7661 if (g.ActiveId == id)
7662 {
7663 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
7664 {
7665 if (g.ActiveIdIsJustActivated)
7666 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
7667 if (g.IO.MouseDown[0])
7668 {
7669 held = true;
7670 }
7671 else
7672 {
7673 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
7674 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
7675 if (!g.DragDropActive)
7676 pressed = true;
7677 ClearActiveID();
7678 }
7679 if (!(flags & ImGuiButtonFlags_NoNavFocus))
7680 g.NavDisableHighlight = true;
7681 }
7682 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
7683 {
7684 if (g.NavActivateDownId != id)
7685 ClearActiveID();
7686 }
7687 }
7688
7689 if (out_hovered) *out_hovered = hovered;
7690 if (out_held) *out_held = held;
7691
7692 return pressed;
7693 }
7694
ButtonEx(const char * label,const ImVec2 & size_arg,ImGuiButtonFlags flags)7695 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
7696 {
7697 ImGuiWindow* window = GetCurrentWindow();
7698 if (window->SkipItems)
7699 return false;
7700
7701 ImGuiContext& g = *GImGui;
7702 const ImGuiStyle& style = g.Style;
7703 const ImGuiID id = window->GetID(label);
7704 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7705
7706 ImVec2 pos = window->DC.CursorPos;
7707 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)
7708 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
7709 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
7710
7711 const ImRect bb(pos, pos + size);
7712 ItemSize(bb, style.FramePadding.y);
7713 if (!ItemAdd(bb, id))
7714 return false;
7715
7716 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
7717 flags |= ImGuiButtonFlags_Repeat;
7718 bool hovered, held;
7719 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
7720
7721 // Render
7722 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
7723 RenderNavHighlight(bb, id);
7724 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
7725 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
7726
7727 // Automatically close popups
7728 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
7729 // CloseCurrentPopup();
7730
7731 return pressed;
7732 }
7733
Button(const char * label,const ImVec2 & size_arg)7734 bool ImGui::Button(const char* label, const ImVec2& size_arg)
7735 {
7736 return ButtonEx(label, size_arg, 0);
7737 }
7738
7739 // Small buttons fits within text without additional vertical spacing.
SmallButton(const char * label)7740 bool ImGui::SmallButton(const char* label)
7741 {
7742 ImGuiContext& g = *GImGui;
7743 float backup_padding_y = g.Style.FramePadding.y;
7744 g.Style.FramePadding.y = 0.0f;
7745 bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine);
7746 g.Style.FramePadding.y = backup_padding_y;
7747 return pressed;
7748 }
7749
ArrowButton(const char * str_id,ImGuiDir dir)7750 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
7751 {
7752 ImGuiWindow* window = GetCurrentWindow();
7753 if (window->SkipItems)
7754 return false;
7755
7756 ImGuiContext& g = *GImGui;
7757 const ImGuiID id = window->GetID(str_id);
7758 float sz = ImGui::GetFrameHeight();
7759 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(sz, sz));
7760 ItemSize(bb);
7761 if (!ItemAdd(bb, id))
7762 return false;
7763
7764 bool hovered, held;
7765 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
7766
7767 // Render
7768 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
7769 RenderNavHighlight(bb, id);
7770 RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
7771 RenderArrow(bb.Min + g.Style.FramePadding, dir);
7772
7773 return pressed;
7774 }
7775
7776 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
7777 // 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)7778 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
7779 {
7780 ImGuiWindow* window = GetCurrentWindow();
7781 if (window->SkipItems)
7782 return false;
7783
7784 const ImGuiID id = window->GetID(str_id);
7785 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
7786 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
7787 ItemSize(bb);
7788 if (!ItemAdd(bb, id))
7789 return false;
7790
7791 bool hovered, held;
7792 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
7793
7794 return pressed;
7795 }
7796
7797 // Button to close a window
CloseButton(ImGuiID id,const ImVec2 & pos,float radius)7798 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
7799 {
7800 ImGuiContext& g = *GImGui;
7801 ImGuiWindow* window = g.CurrentWindow;
7802
7803 // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
7804 // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
7805 const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
7806 bool is_clipped = !ItemAdd(bb, id);
7807
7808 bool hovered, held;
7809 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
7810 if (is_clipped)
7811 return pressed;
7812
7813 // Render
7814 ImVec2 center = bb.GetCenter();
7815 if (hovered)
7816 window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
7817
7818 float cross_extent = (radius * 0.7071f) - 1.0f;
7819 ImU32 cross_col = GetColorU32(ImGuiCol_Text);
7820 center -= ImVec2(0.5f, 0.5f);
7821 window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
7822 window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
7823
7824 return pressed;
7825 }
7826
Image(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec4 & tint_col,const ImVec4 & border_col)7827 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
7828 {
7829 ImGuiWindow* window = GetCurrentWindow();
7830 if (window->SkipItems)
7831 return;
7832
7833 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
7834 if (border_col.w > 0.0f)
7835 bb.Max += ImVec2(2,2);
7836 ItemSize(bb);
7837 if (!ItemAdd(bb, 0))
7838 return;
7839
7840 if (border_col.w > 0.0f)
7841 {
7842 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
7843 window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col));
7844 }
7845 else
7846 {
7847 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
7848 }
7849 }
7850
7851 // frame_padding < 0: uses FramePadding from style (default)
7852 // frame_padding = 0: no framing
7853 // frame_padding > 0: set framing size
7854 // 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)7855 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)
7856 {
7857 ImGuiWindow* window = GetCurrentWindow();
7858 if (window->SkipItems)
7859 return false;
7860
7861 ImGuiContext& g = *GImGui;
7862 const ImGuiStyle& style = g.Style;
7863
7864 // Default to using texture ID as ID. User can still push string/integer prefixes.
7865 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
7866 PushID((void *)user_texture_id);
7867 const ImGuiID id = window->GetID("#image");
7868 PopID();
7869
7870 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
7871 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2);
7872 const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
7873 ItemSize(bb);
7874 if (!ItemAdd(bb, id))
7875 return false;
7876
7877 bool hovered, held;
7878 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
7879
7880 // Render
7881 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
7882 RenderNavHighlight(bb, id);
7883 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
7884 if (bg_col.w > 0.0f)
7885 window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
7886 window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
7887
7888 return pressed;
7889 }
7890
7891 // Start logging ImGui output to TTY
LogToTTY(int max_depth)7892 void ImGui::LogToTTY(int max_depth)
7893 {
7894 ImGuiContext& g = *GImGui;
7895 if (g.LogEnabled)
7896 return;
7897 ImGuiWindow* window = g.CurrentWindow;
7898
7899 IM_ASSERT(g.LogFile == NULL);
7900 g.LogFile = stdout;
7901 g.LogEnabled = true;
7902 g.LogStartDepth = window->DC.TreeDepth;
7903 if (max_depth >= 0)
7904 g.LogAutoExpandMaxDepth = max_depth;
7905 }
7906
7907 // Start logging ImGui output to given file
LogToFile(int max_depth,const char * filename)7908 void ImGui::LogToFile(int max_depth, const char* filename)
7909 {
7910 ImGuiContext& g = *GImGui;
7911 if (g.LogEnabled)
7912 return;
7913 ImGuiWindow* window = g.CurrentWindow;
7914
7915 if (!filename)
7916 {
7917 filename = g.IO.LogFilename;
7918 if (!filename)
7919 return;
7920 }
7921
7922 IM_ASSERT(g.LogFile == NULL);
7923 g.LogFile = ImFileOpen(filename, "ab");
7924 if (!g.LogFile)
7925 {
7926 IM_ASSERT(g.LogFile != NULL); // Consider this an error
7927 return;
7928 }
7929 g.LogEnabled = true;
7930 g.LogStartDepth = window->DC.TreeDepth;
7931 if (max_depth >= 0)
7932 g.LogAutoExpandMaxDepth = max_depth;
7933 }
7934
7935 // Start logging ImGui output to clipboard
LogToClipboard(int max_depth)7936 void ImGui::LogToClipboard(int max_depth)
7937 {
7938 ImGuiContext& g = *GImGui;
7939 if (g.LogEnabled)
7940 return;
7941 ImGuiWindow* window = g.CurrentWindow;
7942
7943 IM_ASSERT(g.LogFile == NULL);
7944 g.LogFile = NULL;
7945 g.LogEnabled = true;
7946 g.LogStartDepth = window->DC.TreeDepth;
7947 if (max_depth >= 0)
7948 g.LogAutoExpandMaxDepth = max_depth;
7949 }
7950
LogFinish()7951 void ImGui::LogFinish()
7952 {
7953 ImGuiContext& g = *GImGui;
7954 if (!g.LogEnabled)
7955 return;
7956
7957 LogText(IM_NEWLINE);
7958 if (g.LogFile != NULL)
7959 {
7960 if (g.LogFile == stdout)
7961 fflush(g.LogFile);
7962 else
7963 fclose(g.LogFile);
7964 g.LogFile = NULL;
7965 }
7966 if (g.LogClipboard->size() > 1)
7967 {
7968 SetClipboardText(g.LogClipboard->begin());
7969 g.LogClipboard->clear();
7970 }
7971 g.LogEnabled = false;
7972 }
7973
7974 // Helper to display logging buttons
LogButtons()7975 void ImGui::LogButtons()
7976 {
7977 ImGuiContext& g = *GImGui;
7978
7979 PushID("LogButtons");
7980 const bool log_to_tty = Button("Log To TTY"); SameLine();
7981 const bool log_to_file = Button("Log To File"); SameLine();
7982 const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
7983 PushItemWidth(80.0f);
7984 PushAllowKeyboardFocus(false);
7985 SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
7986 PopAllowKeyboardFocus();
7987 PopItemWidth();
7988 PopID();
7989
7990 // Start logging at the end of the function so that the buttons don't appear in the log
7991 if (log_to_tty)
7992 LogToTTY(g.LogAutoExpandMaxDepth);
7993 if (log_to_file)
7994 LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
7995 if (log_to_clipboard)
7996 LogToClipboard(g.LogAutoExpandMaxDepth);
7997 }
7998
TreeNodeBehaviorIsOpen(ImGuiID id,ImGuiTreeNodeFlags flags)7999 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
8000 {
8001 if (flags & ImGuiTreeNodeFlags_Leaf)
8002 return true;
8003
8004 // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions)
8005 ImGuiContext& g = *GImGui;
8006 ImGuiWindow* window = g.CurrentWindow;
8007 ImGuiStorage* storage = window->DC.StateStorage;
8008
8009 bool is_open;
8010 if (g.NextTreeNodeOpenCond != 0)
8011 {
8012 if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
8013 {
8014 is_open = g.NextTreeNodeOpenVal;
8015 storage->SetInt(id, is_open);
8016 }
8017 else
8018 {
8019 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
8020 const int stored_value = storage->GetInt(id, -1);
8021 if (stored_value == -1)
8022 {
8023 is_open = g.NextTreeNodeOpenVal;
8024 storage->SetInt(id, is_open);
8025 }
8026 else
8027 {
8028 is_open = stored_value != 0;
8029 }
8030 }
8031 g.NextTreeNodeOpenCond = 0;
8032 }
8033 else
8034 {
8035 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
8036 }
8037
8038 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
8039 // NB- If we are above max depth we still allow manually opened nodes to be logged.
8040 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
8041 is_open = true;
8042
8043 return is_open;
8044 }
8045
TreeNodeBehavior(ImGuiID id,ImGuiTreeNodeFlags flags,const char * label,const char * label_end)8046 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
8047 {
8048 ImGuiWindow* window = GetCurrentWindow();
8049 if (window->SkipItems)
8050 return false;
8051
8052 ImGuiContext& g = *GImGui;
8053 const ImGuiStyle& style = g.Style;
8054 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
8055 const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
8056
8057 if (!label_end)
8058 label_end = FindRenderedTextEnd(label);
8059 const ImVec2 label_size = CalcTextSize(label, label_end, false);
8060
8061 // We vertically grow up to current line height up the typical widget height.
8062 const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
8063 const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
8064 ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
8065 if (display_frame)
8066 {
8067 // Framed header expand a little outside the default padding
8068 frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
8069 frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
8070 }
8071
8072 const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing
8073 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
8074 ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
8075
8076 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
8077 // (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)
8078 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);
8079 bool is_open = TreeNodeBehaviorIsOpen(id, flags);
8080
8081 // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
8082 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
8083 // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
8084 if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8085 window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
8086
8087 bool item_add = ItemAdd(interact_bb, id);
8088 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
8089 window->DC.LastItemDisplayRect = frame_bb;
8090
8091 if (!item_add)
8092 {
8093 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8094 TreePushRawID(id);
8095 return is_open;
8096 }
8097
8098 // Flags that affects opening behavior:
8099 // - 0(default) ..................... single-click anywhere to open
8100 // - OpenOnDoubleClick .............. double-click anywhere to open
8101 // - OpenOnArrow .................... single-click on arrow to open
8102 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
8103 ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
8104 if (!(flags & ImGuiTreeNodeFlags_Leaf))
8105 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
8106 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
8107 button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
8108
8109 bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
8110 if (!(flags & ImGuiTreeNodeFlags_Leaf))
8111 {
8112 bool toggled = false;
8113 if (pressed)
8114 {
8115 toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
8116 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
8117 toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
8118 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
8119 toggled |= g.IO.MouseDoubleClicked[0];
8120 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.
8121 toggled = false;
8122 }
8123
8124 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
8125 {
8126 toggled = true;
8127 NavMoveRequestCancel();
8128 }
8129 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?
8130 {
8131 toggled = true;
8132 NavMoveRequestCancel();
8133 }
8134
8135 if (toggled)
8136 {
8137 is_open = !is_open;
8138 window->DC.StateStorage->SetInt(id, is_open);
8139 }
8140 }
8141 if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
8142 SetItemAllowOverlap();
8143
8144 // Render
8145 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
8146 const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
8147 if (display_frame)
8148 {
8149 // Framed type
8150 RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
8151 RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
8152 RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
8153 if (g.LogEnabled)
8154 {
8155 // 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.
8156 const char log_prefix[] = "\n##";
8157 const char log_suffix[] = "##";
8158 LogRenderedText(&text_pos, log_prefix, log_prefix+3);
8159 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
8160 LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
8161 }
8162 else
8163 {
8164 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
8165 }
8166 }
8167 else
8168 {
8169 // Unframed typed for tree nodes
8170 if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
8171 {
8172 RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
8173 RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
8174 }
8175
8176 if (flags & ImGuiTreeNodeFlags_Bullet)
8177 RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
8178 else if (!(flags & ImGuiTreeNodeFlags_Leaf))
8179 RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
8180 if (g.LogEnabled)
8181 LogRenderedText(&text_pos, ">");
8182 RenderText(text_pos, label, label_end, false);
8183 }
8184
8185 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8186 TreePushRawID(id);
8187 return is_open;
8188 }
8189
8190 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
8191 // 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)8192 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
8193 {
8194 ImGuiWindow* window = GetCurrentWindow();
8195 if (window->SkipItems)
8196 return false;
8197
8198 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label);
8199 }
8200
CollapsingHeader(const char * label,bool * p_open,ImGuiTreeNodeFlags flags)8201 bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
8202 {
8203 ImGuiWindow* window = GetCurrentWindow();
8204 if (window->SkipItems)
8205 return false;
8206
8207 if (p_open && !*p_open)
8208 return false;
8209
8210 ImGuiID id = window->GetID(label);
8211 bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
8212 if (p_open)
8213 {
8214 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
8215 ImGuiContext& g = *GImGui;
8216 float button_sz = g.FontSize * 0.5f;
8217 ImGuiItemHoveredDataBackup last_item_backup;
8218 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))
8219 *p_open = false;
8220 last_item_backup.Restore();
8221 }
8222
8223 return is_open;
8224 }
8225
TreeNodeEx(const char * label,ImGuiTreeNodeFlags flags)8226 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
8227 {
8228 ImGuiWindow* window = GetCurrentWindow();
8229 if (window->SkipItems)
8230 return false;
8231
8232 return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
8233 }
8234
TreeNodeExV(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)8235 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
8236 {
8237 ImGuiWindow* window = GetCurrentWindow();
8238 if (window->SkipItems)
8239 return false;
8240
8241 ImGuiContext& g = *GImGui;
8242 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8243 return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
8244 }
8245
TreeNodeExV(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)8246 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
8247 {
8248 ImGuiWindow* window = GetCurrentWindow();
8249 if (window->SkipItems)
8250 return false;
8251
8252 ImGuiContext& g = *GImGui;
8253 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8254 return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
8255 }
8256
TreeNodeV(const char * str_id,const char * fmt,va_list args)8257 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
8258 {
8259 return TreeNodeExV(str_id, 0, fmt, args);
8260 }
8261
TreeNodeV(const void * ptr_id,const char * fmt,va_list args)8262 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
8263 {
8264 return TreeNodeExV(ptr_id, 0, fmt, args);
8265 }
8266
TreeNodeEx(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,...)8267 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
8268 {
8269 va_list args;
8270 va_start(args, fmt);
8271 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
8272 va_end(args);
8273 return is_open;
8274 }
8275
TreeNodeEx(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,...)8276 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
8277 {
8278 va_list args;
8279 va_start(args, fmt);
8280 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
8281 va_end(args);
8282 return is_open;
8283 }
8284
TreeNode(const char * str_id,const char * fmt,...)8285 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
8286 {
8287 va_list args;
8288 va_start(args, fmt);
8289 bool is_open = TreeNodeExV(str_id, 0, fmt, args);
8290 va_end(args);
8291 return is_open;
8292 }
8293
TreeNode(const void * ptr_id,const char * fmt,...)8294 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
8295 {
8296 va_list args;
8297 va_start(args, fmt);
8298 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
8299 va_end(args);
8300 return is_open;
8301 }
8302
TreeNode(const char * label)8303 bool ImGui::TreeNode(const char* label)
8304 {
8305 ImGuiWindow* window = GetCurrentWindow();
8306 if (window->SkipItems)
8307 return false;
8308 return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
8309 }
8310
TreeAdvanceToLabelPos()8311 void ImGui::TreeAdvanceToLabelPos()
8312 {
8313 ImGuiContext& g = *GImGui;
8314 g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
8315 }
8316
8317 // Horizontal distance preceding label when using TreeNode() or Bullet()
GetTreeNodeToLabelSpacing()8318 float ImGui::GetTreeNodeToLabelSpacing()
8319 {
8320 ImGuiContext& g = *GImGui;
8321 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
8322 }
8323
SetNextTreeNodeOpen(bool is_open,ImGuiCond cond)8324 void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
8325 {
8326 ImGuiContext& g = *GImGui;
8327 if (g.CurrentWindow->SkipItems)
8328 return;
8329 g.NextTreeNodeOpenVal = is_open;
8330 g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
8331 }
8332
PushID(const char * str_id)8333 void ImGui::PushID(const char* str_id)
8334 {
8335 ImGuiWindow* window = GetCurrentWindowRead();
8336 window->IDStack.push_back(window->GetID(str_id));
8337 }
8338
PushID(const char * str_id_begin,const char * str_id_end)8339 void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
8340 {
8341 ImGuiWindow* window = GetCurrentWindowRead();
8342 window->IDStack.push_back(window->GetID(str_id_begin, str_id_end));
8343 }
8344
PushID(const void * ptr_id)8345 void ImGui::PushID(const void* ptr_id)
8346 {
8347 ImGuiWindow* window = GetCurrentWindowRead();
8348 window->IDStack.push_back(window->GetID(ptr_id));
8349 }
8350
PushID(int int_id)8351 void ImGui::PushID(int int_id)
8352 {
8353 const void* ptr_id = (void*)(intptr_t)int_id;
8354 ImGuiWindow* window = GetCurrentWindowRead();
8355 window->IDStack.push_back(window->GetID(ptr_id));
8356 }
8357
PopID()8358 void ImGui::PopID()
8359 {
8360 ImGuiWindow* window = GetCurrentWindowRead();
8361 window->IDStack.pop_back();
8362 }
8363
GetID(const char * str_id)8364 ImGuiID ImGui::GetID(const char* str_id)
8365 {
8366 return GImGui->CurrentWindow->GetID(str_id);
8367 }
8368
GetID(const char * str_id_begin,const char * str_id_end)8369 ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
8370 {
8371 return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
8372 }
8373
GetID(const void * ptr_id)8374 ImGuiID ImGui::GetID(const void* ptr_id)
8375 {
8376 return GImGui->CurrentWindow->GetID(ptr_id);
8377 }
8378
Bullet()8379 void ImGui::Bullet()
8380 {
8381 ImGuiWindow* window = GetCurrentWindow();
8382 if (window->SkipItems)
8383 return;
8384
8385 ImGuiContext& g = *GImGui;
8386 const ImGuiStyle& style = g.Style;
8387 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
8388 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
8389 ItemSize(bb);
8390 if (!ItemAdd(bb, 0))
8391 {
8392 SameLine(0, style.FramePadding.x*2);
8393 return;
8394 }
8395
8396 // Render and stay on same line
8397 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
8398 SameLine(0, style.FramePadding.x*2);
8399 }
8400
8401 // Text with a little bullet aligned to the typical tree node.
BulletTextV(const char * fmt,va_list args)8402 void ImGui::BulletTextV(const char* fmt, va_list args)
8403 {
8404 ImGuiWindow* window = GetCurrentWindow();
8405 if (window->SkipItems)
8406 return;
8407
8408 ImGuiContext& g = *GImGui;
8409 const ImGuiStyle& style = g.Style;
8410
8411 const char* text_begin = g.TempBuffer;
8412 const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8413 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
8414 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
8415 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
8416 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
8417 ItemSize(bb);
8418 if (!ItemAdd(bb, 0))
8419 return;
8420
8421 // Render
8422 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
8423 RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
8424 }
8425
BulletText(const char * fmt,...)8426 void ImGui::BulletText(const char* fmt, ...)
8427 {
8428 va_list args;
8429 va_start(args, fmt);
8430 BulletTextV(fmt, args);
8431 va_end(args);
8432 }
8433
DataTypeFormatString(ImGuiDataType data_type,void * data_ptr,const char * display_format,char * buf,int buf_size)8434 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size)
8435 {
8436 if (data_type == ImGuiDataType_Int)
8437 ImFormatString(buf, buf_size, display_format, *(int*)data_ptr);
8438 else if (data_type == ImGuiDataType_Float)
8439 ImFormatString(buf, buf_size, display_format, *(float*)data_ptr);
8440 else if (data_type == ImGuiDataType_Double)
8441 ImFormatString(buf, buf_size, display_format, *(double*)data_ptr);
8442 }
8443
DataTypeFormatString(ImGuiDataType data_type,void * data_ptr,int decimal_precision,char * buf,int buf_size)8444 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size)
8445 {
8446 if (data_type == ImGuiDataType_Int)
8447 {
8448 if (decimal_precision < 0)
8449 ImFormatString(buf, buf_size, "%d", *(int*)data_ptr);
8450 else
8451 ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr);
8452 }
8453 else if (data_type == ImGuiDataType_Float)
8454 {
8455 if (decimal_precision < 0)
8456 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?
8457 else
8458 ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr);
8459 }
8460 else if (data_type == ImGuiDataType_Double)
8461 {
8462 if (decimal_precision < 0)
8463 ImFormatString(buf, buf_size, "%f", *(double*)data_ptr);
8464 else
8465 ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(double*)data_ptr);
8466 }
8467 }
8468
DataTypeApplyOp(ImGuiDataType data_type,int op,void * output,void * arg1,const void * arg2)8469 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
8470 {
8471 IM_ASSERT(op == '+' || op == '-');
8472 if (data_type == ImGuiDataType_Int)
8473 {
8474 if (op == '+') *(int*)output = *(int*)arg1 + *(const int*)arg2;
8475 else if (op == '-') *(int*)output = *(int*)arg1 - *(const int*)arg2;
8476 }
8477 else if (data_type == ImGuiDataType_Float)
8478 {
8479 if (op == '+') *(float*)output = *(float*)arg1 + *(const float*)arg2;
8480 else if (op == '-') *(float*)output = *(float*)arg1 - *(const float*)arg2;
8481 }
8482 else if (data_type == ImGuiDataType_Double)
8483 {
8484 if (op == '+') *(double*)output = *(double*)arg1 + *(const double*)arg2;
8485 else if (op == '-') *(double*)output = *(double*)arg1 - *(const double*)arg2;
8486 }
8487 }
8488
8489 static size_t GDataTypeSize[ImGuiDataType_COUNT] =
8490 {
8491 sizeof(int),
8492 sizeof(float),
8493 sizeof(double)
8494 };
8495
8496 // User can input math operators (e.g. +100) to edit a numerical values.
8497 // NB: This is _not_ a full expression evaluator. We should probably add one though..
DataTypeApplyOpFromText(const char * buf,const char * initial_value_buf,ImGuiDataType data_type,void * data_ptr,const char * scalar_format)8498 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format)
8499 {
8500 while (ImCharIsSpace((unsigned int)*buf))
8501 buf++;
8502
8503 // We don't support '-' op because it would conflict with inputing negative value.
8504 // Instead you can use +-100 to subtract from an existing value
8505 char op = buf[0];
8506 if (op == '+' || op == '*' || op == '/')
8507 {
8508 buf++;
8509 while (ImCharIsSpace((unsigned int)*buf))
8510 buf++;
8511 }
8512 else
8513 {
8514 op = 0;
8515 }
8516 if (!buf[0])
8517 return false;
8518
8519 IM_ASSERT(data_type < ImGuiDataType_COUNT);
8520 int data_backup[2];
8521 IM_ASSERT(GDataTypeSize[data_type] <= sizeof(data_backup));
8522 memcpy(data_backup, data_ptr, GDataTypeSize[data_type]);
8523
8524 if (data_type == ImGuiDataType_Int)
8525 {
8526 if (!scalar_format)
8527 scalar_format = "%d";
8528 int* v = (int*)data_ptr;
8529 int arg0i = *v;
8530 if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1)
8531 return false;
8532 // 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
8533 float arg1f = 0.0f;
8534 if (op == '+') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i + arg1f); } // Add (use "+-" to subtract)
8535 else if (op == '*') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i * arg1f); } // Multiply
8536 else if (op == '/') { if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }// Divide
8537 else { if (sscanf(buf, scalar_format, &arg0i) == 1) *v = arg0i; } // Assign integer constant
8538 }
8539 else if (data_type == ImGuiDataType_Float)
8540 {
8541 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
8542 scalar_format = "%f";
8543 float* v = (float*)data_ptr;
8544 float arg0f = *v, arg1f = 0.0f;
8545 if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1)
8546 return false;
8547 if (sscanf(buf, scalar_format, &arg1f) < 1)
8548 return false;
8549 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
8550 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
8551 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
8552 else { *v = arg1f; } // Assign constant
8553 }
8554 else if (data_type == ImGuiDataType_Double)
8555 {
8556 scalar_format = "%lf";
8557 double* v = (double*)data_ptr;
8558 double arg0f = *v, arg1f = 0.0f;
8559 if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1)
8560 return false;
8561 if (sscanf(buf, scalar_format, &arg1f) < 1)
8562 return false;
8563 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
8564 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
8565 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
8566 else { *v = arg1f; } // Assign constant
8567 }
8568 return memcmp(data_backup, data_ptr, GDataTypeSize[data_type]) != 0;
8569 }
8570
8571 // Create text input in place of a slider (when CTRL+Clicking on slider)
8572 // FIXME: Logic is messy and confusing.
InputScalarAsWidgetReplacement(const ImRect & aabb,const char * label,ImGuiDataType data_type,void * data_ptr,ImGuiID id,int decimal_precision)8573 bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision)
8574 {
8575 ImGuiContext& g = *GImGui;
8576 ImGuiWindow* window = GetCurrentWindow();
8577
8578 // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
8579 // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
8580 SetActiveID(g.ScalarAsInputTextId, window);
8581 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
8582 SetHoveredID(0);
8583 FocusableItemUnregister(window);
8584
8585 char buf[32];
8586 DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf));
8587 bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll);
8588 if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
8589 {
8590 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)
8591 g.ScalarAsInputTextId = g.ActiveId;
8592 SetHoveredID(id);
8593 }
8594 if (text_value_changed)
8595 return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL);
8596 return false;
8597 }
8598
8599 // Parse display precision back from the display format string
ParseFormatPrecision(const char * fmt,int default_precision)8600 int ImGui::ParseFormatPrecision(const char* fmt, int default_precision)
8601 {
8602 int precision = default_precision;
8603 while ((fmt = strchr(fmt, '%')) != NULL)
8604 {
8605 fmt++;
8606 if (fmt[0] == '%') { fmt++; continue; } // Ignore "%%"
8607 while (*fmt >= '0' && *fmt <= '9')
8608 fmt++;
8609 if (*fmt == '.')
8610 {
8611 fmt = ImAtoi(fmt + 1, &precision);
8612 if (precision < 0 || precision > 10)
8613 precision = default_precision;
8614 }
8615 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
8616 precision = -1;
8617 break;
8618 }
8619 return precision;
8620 }
8621
GetMinimumStepAtDecimalPrecision(int decimal_precision)8622 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
8623 {
8624 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 };
8625 return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision);
8626 }
8627
RoundScalar(float value,int decimal_precision)8628 float ImGui::RoundScalar(float value, int decimal_precision)
8629 {
8630 // Round past decimal precision
8631 // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0
8632 // FIXME: Investigate better rounding methods
8633 if (decimal_precision < 0)
8634 return value;
8635 const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision);
8636 bool negative = value < 0.0f;
8637 value = fabsf(value);
8638 float remainder = fmodf(value, min_step);
8639 if (remainder <= min_step*0.5f)
8640 value -= remainder;
8641 else
8642 value += (min_step - remainder);
8643 return negative ? -value : value;
8644 }
8645
SliderBehaviorCalcRatioFromValue(float v,float v_min,float v_max,float power,float linear_zero_pos)8646 static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos)
8647 {
8648 if (v_min == v_max)
8649 return 0.0f;
8650
8651 const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f);
8652 const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
8653 if (is_non_linear)
8654 {
8655 if (v_clamped < 0.0f)
8656 {
8657 const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f,v_max) - v_min);
8658 return (1.0f - powf(f, 1.0f/power)) * linear_zero_pos;
8659 }
8660 else
8661 {
8662 const float f = (v_clamped - ImMax(0.0f,v_min)) / (v_max - ImMax(0.0f,v_min));
8663 return linear_zero_pos + powf(f, 1.0f/power) * (1.0f - linear_zero_pos);
8664 }
8665 }
8666
8667 // Linear slider
8668 return (v_clamped - v_min) / (v_max - v_min);
8669 }
8670
SliderBehavior(const ImRect & frame_bb,ImGuiID id,float * v,float v_min,float v_max,float power,int decimal_precision,ImGuiSliderFlags flags)8671 bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags)
8672 {
8673 ImGuiContext& g = *GImGui;
8674 ImGuiWindow* window = GetCurrentWindow();
8675 const ImGuiStyle& style = g.Style;
8676
8677 // Draw frame
8678 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
8679 RenderNavHighlight(frame_bb, id);
8680 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
8681
8682 const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f);
8683 const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
8684
8685 const float grab_padding = 2.0f;
8686 const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f);
8687 float grab_sz;
8688 if (decimal_precision != 0)
8689 grab_sz = ImMin(style.GrabMinSize, slider_sz);
8690 else
8691 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
8692 const float slider_usable_sz = slider_sz - grab_sz;
8693 const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz*0.5f;
8694 const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz*0.5f;
8695
8696 // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f
8697 float linear_zero_pos = 0.0f; // 0.0->1.0f
8698 if (v_min * v_max < 0.0f)
8699 {
8700 // Different sign
8701 const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power);
8702 const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power);
8703 linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0);
8704 }
8705 else
8706 {
8707 // Same sign
8708 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
8709 }
8710
8711 // Process interacting with the slider
8712 bool value_changed = false;
8713 if (g.ActiveId == id)
8714 {
8715 bool set_new_value = false;
8716 float clicked_t = 0.0f;
8717 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
8718 {
8719 if (!g.IO.MouseDown[0])
8720 {
8721 ClearActiveID();
8722 }
8723 else
8724 {
8725 const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
8726 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
8727 if (!is_horizontal)
8728 clicked_t = 1.0f - clicked_t;
8729 set_new_value = true;
8730 }
8731 }
8732 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
8733 {
8734 const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
8735 float delta = is_horizontal ? delta2.x : -delta2.y;
8736 if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
8737 {
8738 ClearActiveID();
8739 }
8740 else if (delta != 0.0f)
8741 {
8742 clicked_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos);
8743 if (decimal_precision == 0 && !is_non_linear)
8744 {
8745 if (fabsf(v_max - v_min) <= 100.0f || IsNavInputDown(ImGuiNavInput_TweakSlow))
8746 delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (v_max - v_min); // Gamepad/keyboard tweak speeds in integer steps
8747 else
8748 delta /= 100.0f;
8749 }
8750 else
8751 {
8752 delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
8753 if (IsNavInputDown(ImGuiNavInput_TweakSlow))
8754 delta /= 10.0f;
8755 }
8756 if (IsNavInputDown(ImGuiNavInput_TweakFast))
8757 delta *= 10.0f;
8758 set_new_value = true;
8759 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
8760 set_new_value = false;
8761 else
8762 clicked_t = ImSaturate(clicked_t + delta);
8763 }
8764 }
8765
8766 if (set_new_value)
8767 {
8768 float new_value;
8769 if (is_non_linear)
8770 {
8771 // Account for logarithmic scale on both sides of the zero
8772 if (clicked_t < linear_zero_pos)
8773 {
8774 // Negative: rescale to the negative range before powering
8775 float a = 1.0f - (clicked_t / linear_zero_pos);
8776 a = powf(a, power);
8777 new_value = ImLerp(ImMin(v_max,0.0f), v_min, a);
8778 }
8779 else
8780 {
8781 // Positive: rescale to the positive range before powering
8782 float a;
8783 if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f)
8784 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
8785 else
8786 a = clicked_t;
8787 a = powf(a, power);
8788 new_value = ImLerp(ImMax(v_min,0.0f), v_max, a);
8789 }
8790 }
8791 else
8792 {
8793 // Linear slider
8794 new_value = ImLerp(v_min, v_max, clicked_t);
8795 }
8796
8797 // Round past decimal precision
8798 new_value = RoundScalar(new_value, decimal_precision);
8799 if (*v != new_value)
8800 {
8801 *v = new_value;
8802 value_changed = true;
8803 }
8804 }
8805 }
8806
8807 // Draw
8808 float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos);
8809 if (!is_horizontal)
8810 grab_t = 1.0f - grab_t;
8811 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
8812 ImRect grab_bb;
8813 if (is_horizontal)
8814 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));
8815 else
8816 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));
8817 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
8818
8819 return value_changed;
8820 }
8821
8822 // Use power!=1.0 for logarithmic sliders.
8823 // Adjust display_format to decorate the value with a prefix or a suffix.
8824 // "%.3f" 1.234
8825 // "%5.2f secs" 01.23 secs
8826 // "Gold: %.0f" Gold: 1
SliderFloat(const char * label,float * v,float v_min,float v_max,const char * display_format,float power)8827 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power)
8828 {
8829 ImGuiWindow* window = GetCurrentWindow();
8830 if (window->SkipItems)
8831 return false;
8832
8833 ImGuiContext& g = *GImGui;
8834 const ImGuiStyle& style = g.Style;
8835 const ImGuiID id = window->GetID(label);
8836 const float w = CalcItemWidth();
8837
8838 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8839 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
8840 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));
8841
8842 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
8843 if (!ItemAdd(total_bb, id, &frame_bb))
8844 {
8845 ItemSize(total_bb, style.FramePadding.y);
8846 return false;
8847 }
8848 const bool hovered = ItemHoverable(frame_bb, id);
8849
8850 if (!display_format)
8851 display_format = "%.3f";
8852 int decimal_precision = ParseFormatPrecision(display_format, 3);
8853
8854 // Tabbing or CTRL-clicking on Slider turns it into an input box
8855 bool start_text_input = false;
8856 const bool tab_focus_requested = FocusableItemRegister(window, id);
8857 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
8858 {
8859 SetActiveID(id, window);
8860 SetFocusID(id, window);
8861 FocusWindow(window);
8862 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
8863 if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
8864 {
8865 start_text_input = true;
8866 g.ScalarAsInputTextId = 0;
8867 }
8868 }
8869 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
8870 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
8871
8872 // Actual slider behavior + render grab
8873 ItemSize(total_bb, style.FramePadding.y);
8874 const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision);
8875
8876 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
8877 char value_buf[64];
8878 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
8879 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
8880
8881 if (label_size.x > 0.0f)
8882 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
8883
8884 return value_changed;
8885 }
8886
VSliderFloat(const char * label,const ImVec2 & size,float * v,float v_min,float v_max,const char * display_format,float power)8887 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power)
8888 {
8889 ImGuiWindow* window = GetCurrentWindow();
8890 if (window->SkipItems)
8891 return false;
8892
8893 ImGuiContext& g = *GImGui;
8894 const ImGuiStyle& style = g.Style;
8895 const ImGuiID id = window->GetID(label);
8896
8897 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8898 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
8899 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));
8900
8901 ItemSize(bb, style.FramePadding.y);
8902 if (!ItemAdd(frame_bb, id))
8903 return false;
8904 const bool hovered = ItemHoverable(frame_bb, id);
8905
8906 if (!display_format)
8907 display_format = "%.3f";
8908 int decimal_precision = ParseFormatPrecision(display_format, 3);
8909
8910 if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
8911 {
8912 SetActiveID(id, window);
8913 SetFocusID(id, window);
8914 FocusWindow(window);
8915 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
8916 }
8917
8918 // Actual slider behavior + render grab
8919 bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical);
8920
8921 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
8922 // For the vertical slider we allow centered text to overlap the frame padding
8923 char value_buf[64];
8924 char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
8925 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));
8926 if (label_size.x > 0.0f)
8927 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
8928
8929 return value_changed;
8930 }
8931
SliderAngle(const char * label,float * v_rad,float v_degrees_min,float v_degrees_max)8932 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max)
8933 {
8934 float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
8935 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
8936 *v_rad = v_deg * (2*IM_PI) / 360.0f;
8937 return value_changed;
8938 }
8939
SliderInt(const char * label,int * v,int v_min,int v_max,const char * display_format)8940 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format)
8941 {
8942 if (!display_format)
8943 display_format = "%.0f";
8944 float v_f = (float)*v;
8945 bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f);
8946 *v = (int)v_f;
8947 return value_changed;
8948 }
8949
VSliderInt(const char * label,const ImVec2 & size,int * v,int v_min,int v_max,const char * display_format)8950 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format)
8951 {
8952 if (!display_format)
8953 display_format = "%.0f";
8954 float v_f = (float)*v;
8955 bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f);
8956 *v = (int)v_f;
8957 return value_changed;
8958 }
8959
8960 // 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)8961 bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power)
8962 {
8963 ImGuiWindow* window = GetCurrentWindow();
8964 if (window->SkipItems)
8965 return false;
8966
8967 ImGuiContext& g = *GImGui;
8968 bool value_changed = false;
8969 BeginGroup();
8970 PushID(label);
8971 PushMultiItemsWidths(components);
8972 for (int i = 0; i < components; i++)
8973 {
8974 PushID(i);
8975 value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power);
8976 SameLine(0, g.Style.ItemInnerSpacing.x);
8977 PopID();
8978 PopItemWidth();
8979 }
8980 PopID();
8981
8982 TextUnformatted(label, FindRenderedTextEnd(label));
8983 EndGroup();
8984
8985 return value_changed;
8986 }
8987
SliderFloat2(const char * label,float v[2],float v_min,float v_max,const char * display_format,float power)8988 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power)
8989 {
8990 return SliderFloatN(label, v, 2, v_min, v_max, display_format, power);
8991 }
8992
SliderFloat3(const char * label,float v[3],float v_min,float v_max,const char * display_format,float power)8993 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power)
8994 {
8995 return SliderFloatN(label, v, 3, v_min, v_max, display_format, power);
8996 }
8997
SliderFloat4(const char * label,float v[4],float v_min,float v_max,const char * display_format,float power)8998 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power)
8999 {
9000 return SliderFloatN(label, v, 4, v_min, v_max, display_format, power);
9001 }
9002
SliderIntN(const char * label,int * v,int components,int v_min,int v_max,const char * display_format)9003 bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format)
9004 {
9005 ImGuiWindow* window = GetCurrentWindow();
9006 if (window->SkipItems)
9007 return false;
9008
9009 ImGuiContext& g = *GImGui;
9010 bool value_changed = false;
9011 BeginGroup();
9012 PushID(label);
9013 PushMultiItemsWidths(components);
9014 for (int i = 0; i < components; i++)
9015 {
9016 PushID(i);
9017 value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format);
9018 SameLine(0, g.Style.ItemInnerSpacing.x);
9019 PopID();
9020 PopItemWidth();
9021 }
9022 PopID();
9023
9024 TextUnformatted(label, FindRenderedTextEnd(label));
9025 EndGroup();
9026
9027 return value_changed;
9028 }
9029
SliderInt2(const char * label,int v[2],int v_min,int v_max,const char * display_format)9030 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format)
9031 {
9032 return SliderIntN(label, v, 2, v_min, v_max, display_format);
9033 }
9034
SliderInt3(const char * label,int v[3],int v_min,int v_max,const char * display_format)9035 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format)
9036 {
9037 return SliderIntN(label, v, 3, v_min, v_max, display_format);
9038 }
9039
SliderInt4(const char * label,int v[4],int v_min,int v_max,const char * display_format)9040 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format)
9041 {
9042 return SliderIntN(label, v, 4, v_min, v_max, display_format);
9043 }
9044
DragBehavior(const ImRect & frame_bb,ImGuiID id,float * v,float v_speed,float v_min,float v_max,int decimal_precision,float power)9045 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)
9046 {
9047 ImGuiContext& g = *GImGui;
9048 const ImGuiStyle& style = g.Style;
9049
9050 // Draw frame
9051 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
9052 RenderNavHighlight(frame_bb, id);
9053 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
9054
9055 bool value_changed = false;
9056
9057 // Process interacting with the drag
9058 if (g.ActiveId == id)
9059 {
9060 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
9061 ClearActiveID();
9062 else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
9063 ClearActiveID();
9064 }
9065 if (g.ActiveId == id)
9066 {
9067 if (g.ActiveIdIsJustActivated)
9068 {
9069 // Lock current value on click
9070 g.DragCurrentValue = *v;
9071 g.DragLastMouseDelta = ImVec2(0.f, 0.f);
9072 }
9073
9074 if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX)
9075 v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio;
9076
9077 float v_cur = g.DragCurrentValue;
9078 const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f);
9079 float adjust_delta = 0.0f;
9080 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid())
9081 {
9082 adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x;
9083 if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f)
9084 adjust_delta *= g.DragSpeedScaleFast;
9085 if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f)
9086 adjust_delta *= g.DragSpeedScaleSlow;
9087 g.DragLastMouseDelta.x = mouse_drag_delta.x;
9088 }
9089 if (g.ActiveIdSource == ImGuiInputSource_Nav)
9090 {
9091 adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x;
9092 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
9093 adjust_delta = 0.0f;
9094 v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
9095 }
9096 adjust_delta *= v_speed;
9097
9098 if (fabsf(adjust_delta) > 0.0f)
9099 {
9100 if (fabsf(power - 1.0f) > 0.001f)
9101 {
9102 // Logarithmic curve on both side of 0.0
9103 float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur;
9104 float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f;
9105 float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign);
9106 float v1_abs = v1 >= 0.0f ? v1 : -v1;
9107 float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line
9108 v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign
9109 }
9110 else
9111 {
9112 v_cur += adjust_delta;
9113 }
9114
9115 // Clamp
9116 if (v_min < v_max)
9117 v_cur = ImClamp(v_cur, v_min, v_max);
9118 g.DragCurrentValue = v_cur;
9119 }
9120
9121 // Round to user desired precision, then apply
9122 v_cur = RoundScalar(v_cur, decimal_precision);
9123 if (*v != v_cur)
9124 {
9125 *v = v_cur;
9126 value_changed = true;
9127 }
9128 }
9129
9130 return value_changed;
9131 }
9132
DragFloat(const char * label,float * v,float v_speed,float v_min,float v_max,const char * display_format,float power)9133 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power)
9134 {
9135 ImGuiWindow* window = GetCurrentWindow();
9136 if (window->SkipItems)
9137 return false;
9138
9139 ImGuiContext& g = *GImGui;
9140 const ImGuiStyle& style = g.Style;
9141 const ImGuiID id = window->GetID(label);
9142 const float w = CalcItemWidth();
9143
9144 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9145 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
9146 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
9147 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));
9148
9149 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
9150 if (!ItemAdd(total_bb, id, &frame_bb))
9151 {
9152 ItemSize(total_bb, style.FramePadding.y);
9153 return false;
9154 }
9155 const bool hovered = ItemHoverable(frame_bb, id);
9156
9157 if (!display_format)
9158 display_format = "%.3f";
9159 int decimal_precision = ParseFormatPrecision(display_format, 3);
9160
9161 // Tabbing or CTRL-clicking on Drag turns it into an input box
9162 bool start_text_input = false;
9163 const bool tab_focus_requested = FocusableItemRegister(window, id);
9164 if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
9165 {
9166 SetActiveID(id, window);
9167 SetFocusID(id, window);
9168 FocusWindow(window);
9169 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
9170 if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
9171 {
9172 start_text_input = true;
9173 g.ScalarAsInputTextId = 0;
9174 }
9175 }
9176 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
9177 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
9178
9179 // Actual drag behavior
9180 ItemSize(total_bb, style.FramePadding.y);
9181 const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power);
9182
9183 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9184 char value_buf[64];
9185 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
9186 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
9187
9188 if (label_size.x > 0.0f)
9189 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
9190
9191 return value_changed;
9192 }
9193
DragFloatN(const char * label,float * v,int components,float v_speed,float v_min,float v_max,const char * display_format,float power)9194 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)
9195 {
9196 ImGuiWindow* window = GetCurrentWindow();
9197 if (window->SkipItems)
9198 return false;
9199
9200 ImGuiContext& g = *GImGui;
9201 bool value_changed = false;
9202 BeginGroup();
9203 PushID(label);
9204 PushMultiItemsWidths(components);
9205 for (int i = 0; i < components; i++)
9206 {
9207 PushID(i);
9208 value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power);
9209 SameLine(0, g.Style.ItemInnerSpacing.x);
9210 PopID();
9211 PopItemWidth();
9212 }
9213 PopID();
9214
9215 TextUnformatted(label, FindRenderedTextEnd(label));
9216 EndGroup();
9217
9218 return value_changed;
9219 }
9220
DragFloat2(const char * label,float v[2],float v_speed,float v_min,float v_max,const char * display_format,float power)9221 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power)
9222 {
9223 return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power);
9224 }
9225
DragFloat3(const char * label,float v[3],float v_speed,float v_min,float v_max,const char * display_format,float power)9226 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power)
9227 {
9228 return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power);
9229 }
9230
DragFloat4(const char * label,float v[4],float v_speed,float v_min,float v_max,const char * display_format,float power)9231 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power)
9232 {
9233 return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power);
9234 }
9235
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)9236 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)
9237 {
9238 ImGuiWindow* window = GetCurrentWindow();
9239 if (window->SkipItems)
9240 return false;
9241
9242 ImGuiContext& g = *GImGui;
9243 PushID(label);
9244 BeginGroup();
9245 PushMultiItemsWidths(2);
9246
9247 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);
9248 PopItemWidth();
9249 SameLine(0, g.Style.ItemInnerSpacing.x);
9250 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);
9251 PopItemWidth();
9252 SameLine(0, g.Style.ItemInnerSpacing.x);
9253
9254 TextUnformatted(label, FindRenderedTextEnd(label));
9255 EndGroup();
9256 PopID();
9257
9258 return value_changed;
9259 }
9260
9261 // 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)9262 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format)
9263 {
9264 if (!display_format)
9265 display_format = "%.0f";
9266 float v_f = (float)*v;
9267 bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format);
9268 *v = (int)v_f;
9269 return value_changed;
9270 }
9271
DragIntN(const char * label,int * v,int components,float v_speed,int v_min,int v_max,const char * display_format)9272 bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format)
9273 {
9274 ImGuiWindow* window = GetCurrentWindow();
9275 if (window->SkipItems)
9276 return false;
9277
9278 ImGuiContext& g = *GImGui;
9279 bool value_changed = false;
9280 BeginGroup();
9281 PushID(label);
9282 PushMultiItemsWidths(components);
9283 for (int i = 0; i < components; i++)
9284 {
9285 PushID(i);
9286 value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format);
9287 SameLine(0, g.Style.ItemInnerSpacing.x);
9288 PopID();
9289 PopItemWidth();
9290 }
9291 PopID();
9292
9293 TextUnformatted(label, FindRenderedTextEnd(label));
9294 EndGroup();
9295
9296 return value_changed;
9297 }
9298
DragInt2(const char * label,int v[2],float v_speed,int v_min,int v_max,const char * display_format)9299 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format)
9300 {
9301 return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format);
9302 }
9303
DragInt3(const char * label,int v[3],float v_speed,int v_min,int v_max,const char * display_format)9304 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format)
9305 {
9306 return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format);
9307 }
9308
DragInt4(const char * label,int v[4],float v_speed,int v_min,int v_max,const char * display_format)9309 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format)
9310 {
9311 return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format);
9312 }
9313
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)9314 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)
9315 {
9316 ImGuiWindow* window = GetCurrentWindow();
9317 if (window->SkipItems)
9318 return false;
9319
9320 ImGuiContext& g = *GImGui;
9321 PushID(label);
9322 BeginGroup();
9323 PushMultiItemsWidths(2);
9324
9325 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);
9326 PopItemWidth();
9327 SameLine(0, g.Style.ItemInnerSpacing.x);
9328 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);
9329 PopItemWidth();
9330 SameLine(0, g.Style.ItemInnerSpacing.x);
9331
9332 TextUnformatted(label, FindRenderedTextEnd(label));
9333 EndGroup();
9334 PopID();
9335
9336 return value_changed;
9337 }
9338
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)9339 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)
9340 {
9341 ImGuiWindow* window = GetCurrentWindow();
9342 if (window->SkipItems)
9343 return;
9344
9345 ImGuiContext& g = *GImGui;
9346 const ImGuiStyle& style = g.Style;
9347
9348 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9349 if (graph_size.x == 0.0f)
9350 graph_size.x = CalcItemWidth();
9351 if (graph_size.y == 0.0f)
9352 graph_size.y = label_size.y + (style.FramePadding.y * 2);
9353
9354 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
9355 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
9356 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));
9357 ItemSize(total_bb, style.FramePadding.y);
9358 if (!ItemAdd(total_bb, 0, &frame_bb))
9359 return;
9360 const bool hovered = ItemHoverable(inner_bb, 0);
9361
9362 // Determine scale from values if not specified
9363 if (scale_min == FLT_MAX || scale_max == FLT_MAX)
9364 {
9365 float v_min = FLT_MAX;
9366 float v_max = -FLT_MAX;
9367 for (int i = 0; i < values_count; i++)
9368 {
9369 const float v = values_getter(data, i);
9370 v_min = ImMin(v_min, v);
9371 v_max = ImMax(v_max, v);
9372 }
9373 if (scale_min == FLT_MAX)
9374 scale_min = v_min;
9375 if (scale_max == FLT_MAX)
9376 scale_max = v_max;
9377 }
9378
9379 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
9380
9381 if (values_count > 0)
9382 {
9383 int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
9384 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
9385
9386 // Tooltip on hover
9387 int v_hovered = -1;
9388 if (hovered)
9389 {
9390 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
9391 const int v_idx = (int)(t * item_count);
9392 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
9393
9394 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
9395 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
9396 if (plot_type == ImGuiPlotType_Lines)
9397 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
9398 else if (plot_type == ImGuiPlotType_Histogram)
9399 SetTooltip("%d: %8.4g", v_idx, v0);
9400 v_hovered = v_idx;
9401 }
9402
9403 const float t_step = 1.0f / (float)res_w;
9404 const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
9405
9406 float v0 = values_getter(data, (0 + values_offset) % values_count);
9407 float t0 = 0.0f;
9408 ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
9409 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
9410
9411 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
9412 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
9413
9414 for (int n = 0; n < res_w; n++)
9415 {
9416 const float t1 = t0 + t_step;
9417 const int v1_idx = (int)(t0 * item_count + 0.5f);
9418 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
9419 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
9420 const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
9421
9422 // 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.
9423 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
9424 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
9425 if (plot_type == ImGuiPlotType_Lines)
9426 {
9427 window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
9428 }
9429 else if (plot_type == ImGuiPlotType_Histogram)
9430 {
9431 if (pos1.x >= pos0.x + 2.0f)
9432 pos1.x -= 1.0f;
9433 window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
9434 }
9435
9436 t0 = t1;
9437 tp0 = tp1;
9438 }
9439 }
9440
9441 // Text overlay
9442 if (overlay_text)
9443 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));
9444
9445 if (label_size.x > 0.0f)
9446 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
9447 }
9448
9449 struct ImGuiPlotArrayGetterData
9450 {
9451 const float* Values;
9452 int Stride;
9453
ImGuiPlotArrayGetterDataImGuiPlotArrayGetterData9454 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
9455 };
9456
Plot_ArrayGetter(void * data,int idx)9457 static float Plot_ArrayGetter(void* data, int idx)
9458 {
9459 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
9460 const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
9461 return v;
9462 }
9463
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)9464 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)
9465 {
9466 ImGuiPlotArrayGetterData data(values, stride);
9467 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9468 }
9469
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)9470 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)
9471 {
9472 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9473 }
9474
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)9475 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)
9476 {
9477 ImGuiPlotArrayGetterData data(values, stride);
9478 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9479 }
9480
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)9481 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)
9482 {
9483 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9484 }
9485
9486 // 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)9487 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
9488 {
9489 ImGuiWindow* window = GetCurrentWindow();
9490 if (window->SkipItems)
9491 return;
9492
9493 ImGuiContext& g = *GImGui;
9494 const ImGuiStyle& style = g.Style;
9495
9496 ImVec2 pos = window->DC.CursorPos;
9497 ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
9498 ItemSize(bb, style.FramePadding.y);
9499 if (!ItemAdd(bb, 0))
9500 return;
9501
9502 // Render
9503 fraction = ImSaturate(fraction);
9504 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
9505 bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
9506 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
9507 RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
9508
9509 // Default displaying the fraction as percentage string, but user can override it
9510 char overlay_buf[32];
9511 if (!overlay)
9512 {
9513 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
9514 overlay = overlay_buf;
9515 }
9516
9517 ImVec2 overlay_size = CalcTextSize(overlay, NULL);
9518 if (overlay_size.x > 0.0f)
9519 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);
9520 }
9521
Checkbox(const char * label,bool * v)9522 bool ImGui::Checkbox(const char* label, bool* v)
9523 {
9524 ImGuiWindow* window = GetCurrentWindow();
9525 if (window->SkipItems)
9526 return false;
9527
9528 ImGuiContext& g = *GImGui;
9529 const ImGuiStyle& style = g.Style;
9530 const ImGuiID id = window->GetID(label);
9531 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9532
9533 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
9534 ItemSize(check_bb, style.FramePadding.y);
9535
9536 ImRect total_bb = check_bb;
9537 if (label_size.x > 0)
9538 SameLine(0, style.ItemInnerSpacing.x);
9539 const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
9540 if (label_size.x > 0)
9541 {
9542 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
9543 total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
9544 }
9545
9546 if (!ItemAdd(total_bb, id))
9547 return false;
9548
9549 bool hovered, held;
9550 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
9551 if (pressed)
9552 *v = !(*v);
9553
9554 RenderNavHighlight(total_bb, id);
9555 RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
9556 if (*v)
9557 {
9558 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
9559 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
9560 RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f);
9561 }
9562
9563 if (g.LogEnabled)
9564 LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
9565 if (label_size.x > 0.0f)
9566 RenderText(text_bb.Min, label);
9567
9568 return pressed;
9569 }
9570
CheckboxFlags(const char * label,unsigned int * flags,unsigned int flags_value)9571 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
9572 {
9573 bool v = ((*flags & flags_value) == flags_value);
9574 bool pressed = Checkbox(label, &v);
9575 if (pressed)
9576 {
9577 if (v)
9578 *flags |= flags_value;
9579 else
9580 *flags &= ~flags_value;
9581 }
9582
9583 return pressed;
9584 }
9585
RadioButton(const char * label,bool active)9586 bool ImGui::RadioButton(const char* label, bool active)
9587 {
9588 ImGuiWindow* window = GetCurrentWindow();
9589 if (window->SkipItems)
9590 return false;
9591
9592 ImGuiContext& g = *GImGui;
9593 const ImGuiStyle& style = g.Style;
9594 const ImGuiID id = window->GetID(label);
9595 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9596
9597 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));
9598 ItemSize(check_bb, style.FramePadding.y);
9599
9600 ImRect total_bb = check_bb;
9601 if (label_size.x > 0)
9602 SameLine(0, style.ItemInnerSpacing.x);
9603 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
9604 if (label_size.x > 0)
9605 {
9606 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
9607 total_bb.Add(text_bb);
9608 }
9609
9610 if (!ItemAdd(total_bb, id))
9611 return false;
9612
9613 ImVec2 center = check_bb.GetCenter();
9614 center.x = (float)(int)center.x + 0.5f;
9615 center.y = (float)(int)center.y + 0.5f;
9616 const float radius = check_bb.GetHeight() * 0.5f;
9617
9618 bool hovered, held;
9619 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
9620
9621 RenderNavHighlight(total_bb, id);
9622 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
9623 if (active)
9624 {
9625 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
9626 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
9627 window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16);
9628 }
9629
9630 if (style.FrameBorderSize > 0.0f)
9631 {
9632 window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
9633 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
9634 }
9635
9636 if (g.LogEnabled)
9637 LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
9638 if (label_size.x > 0.0f)
9639 RenderText(text_bb.Min, label);
9640
9641 return pressed;
9642 }
9643
RadioButton(const char * label,int * v,int v_button)9644 bool ImGui::RadioButton(const char* label, int* v, int v_button)
9645 {
9646 const bool pressed = RadioButton(label, *v == v_button);
9647 if (pressed)
9648 {
9649 *v = v_button;
9650 }
9651 return pressed;
9652 }
9653
InputTextCalcTextLenAndLineCount(const char * text_begin,const char ** out_text_end)9654 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
9655 {
9656 int line_count = 0;
9657 const char* s = text_begin;
9658 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
9659 if (c == '\n')
9660 line_count++;
9661 s--;
9662 if (s[0] != '\n' && s[0] != '\r')
9663 line_count++;
9664 *out_text_end = s;
9665 return line_count;
9666 }
9667
InputTextCalcTextSizeW(const ImWchar * text_begin,const ImWchar * text_end,const ImWchar ** remaining,ImVec2 * out_offset,bool stop_on_new_line)9668 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
9669 {
9670 ImFont* font = GImGui->Font;
9671 const float line_height = GImGui->FontSize;
9672 const float scale = line_height / font->FontSize;
9673
9674 ImVec2 text_size = ImVec2(0,0);
9675 float line_width = 0.0f;
9676
9677 const ImWchar* s = text_begin;
9678 while (s < text_end)
9679 {
9680 unsigned int c = (unsigned int)(*s++);
9681 if (c == '\n')
9682 {
9683 text_size.x = ImMax(text_size.x, line_width);
9684 text_size.y += line_height;
9685 line_width = 0.0f;
9686 if (stop_on_new_line)
9687 break;
9688 continue;
9689 }
9690 if (c == '\r')
9691 continue;
9692
9693 const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
9694 line_width += char_width;
9695 }
9696
9697 if (text_size.x < line_width)
9698 text_size.x = line_width;
9699
9700 if (out_offset)
9701 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
9702
9703 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
9704 text_size.y += line_height;
9705
9706 if (remaining)
9707 *remaining = s;
9708
9709 return text_size;
9710 }
9711
9712 // 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)
9713 namespace ImGuiStb
9714 {
9715
STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING * obj)9716 static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING * obj,int idx)9717 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)9718 static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
STB_TEXTEDIT_KEYTOTEXT(int key)9719 static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
9720 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
STB_TEXTEDIT_LAYOUTROW(StbTexteditRow * r,STB_TEXTEDIT_STRING * obj,int line_start_idx)9721 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
9722 {
9723 const ImWchar* text = obj->Text.Data;
9724 const ImWchar* text_remaining = NULL;
9725 const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
9726 r->x0 = 0.0f;
9727 r->x1 = size.x;
9728 r->baseline_y_delta = size.y;
9729 r->ymin = 0.0f;
9730 r->ymax = size.y;
9731 r->num_chars = (int)(text_remaining - (text + line_start_idx));
9732 }
9733
is_separator(unsigned int c)9734 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)9735 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)9736 static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
9737 #ifdef __APPLE__ // FIXME: Move setting to IO structure
is_word_boundary_from_left(STB_TEXTEDIT_STRING * obj,int idx)9738 static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; }
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)9739 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
9740 #else
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)9741 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
9742 #endif
9743 #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
9744 #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
9745
STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING * obj,int pos,int n)9746 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
9747 {
9748 ImWchar* dst = obj->Text.Data + pos;
9749
9750 // We maintain our buffer length in both UTF-8 and wchar formats
9751 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
9752 obj->CurLenW -= n;
9753
9754 // Offset remaining text
9755 const ImWchar* src = obj->Text.Data + pos + n;
9756 while (ImWchar c = *src++)
9757 *dst++ = c;
9758 *dst = '\0';
9759 }
9760
STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING * obj,int pos,const ImWchar * new_text,int new_text_len)9761 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
9762 {
9763 const int text_len = obj->CurLenW;
9764 IM_ASSERT(pos <= text_len);
9765 if (new_text_len + text_len + 1 > obj->Text.Size)
9766 return false;
9767
9768 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
9769 if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA)
9770 return false;
9771
9772 ImWchar* text = obj->Text.Data;
9773 if (pos != text_len)
9774 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
9775 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
9776
9777 obj->CurLenW += new_text_len;
9778 obj->CurLenA += new_text_len_utf8;
9779 obj->Text[obj->CurLenW] = '\0';
9780
9781 return true;
9782 }
9783
9784 // 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)
9785 #define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
9786 #define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
9787 #define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
9788 #define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
9789 #define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
9790 #define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
9791 #define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
9792 #define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
9793 #define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
9794 #define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
9795 #define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
9796 #define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
9797 #define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
9798 #define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
9799 #define STB_TEXTEDIT_K_SHIFT 0x20000
9800
9801 #define STB_TEXTEDIT_IMPLEMENTATION
9802 #include "stb_textedit.h"
9803
9804 }
9805
OnKeyPressed(int key)9806 void ImGuiTextEditState::OnKeyPressed(int key)
9807 {
9808 stb_textedit_key(this, &StbState, key);
9809 CursorFollow = true;
9810 CursorAnimReset();
9811 }
9812
9813 // Public API to manipulate UTF-8 text
9814 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
9815 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
DeleteChars(int pos,int bytes_count)9816 void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count)
9817 {
9818 IM_ASSERT(pos + bytes_count <= BufTextLen);
9819 char* dst = Buf + pos;
9820 const char* src = Buf + pos + bytes_count;
9821 while (char c = *src++)
9822 *dst++ = c;
9823 *dst = '\0';
9824
9825 if (CursorPos + bytes_count >= pos)
9826 CursorPos -= bytes_count;
9827 else if (CursorPos >= pos)
9828 CursorPos = pos;
9829 SelectionStart = SelectionEnd = CursorPos;
9830 BufDirty = true;
9831 BufTextLen -= bytes_count;
9832 }
9833
InsertChars(int pos,const char * new_text,const char * new_text_end)9834 void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
9835 {
9836 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
9837 if (new_text_len + BufTextLen + 1 >= BufSize)
9838 return;
9839
9840 if (BufTextLen != pos)
9841 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
9842 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
9843 Buf[BufTextLen + new_text_len] = '\0';
9844
9845 if (CursorPos >= pos)
9846 CursorPos += new_text_len;
9847 SelectionStart = SelectionEnd = CursorPos;
9848 BufDirty = true;
9849 BufTextLen += new_text_len;
9850 }
9851
9852 // Return false to discard a character.
InputTextFilterCharacter(unsigned int * p_char,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)9853 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
9854 {
9855 unsigned int c = *p_char;
9856
9857 if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
9858 {
9859 bool pass = false;
9860 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
9861 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
9862 if (!pass)
9863 return false;
9864 }
9865
9866 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.
9867 return false;
9868
9869 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
9870 {
9871 if (flags & ImGuiInputTextFlags_CharsDecimal)
9872 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
9873 return false;
9874
9875 if (flags & ImGuiInputTextFlags_CharsScientific)
9876 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
9877 return false;
9878
9879 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
9880 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
9881 return false;
9882
9883 if (flags & ImGuiInputTextFlags_CharsUppercase)
9884 if (c >= 'a' && c <= 'z')
9885 *p_char = (c += (unsigned int)('A'-'a'));
9886
9887 if (flags & ImGuiInputTextFlags_CharsNoBlank)
9888 if (ImCharIsSpace(c))
9889 return false;
9890 }
9891
9892 if (flags & ImGuiInputTextFlags_CallbackCharFilter)
9893 {
9894 ImGuiTextEditCallbackData callback_data;
9895 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
9896 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
9897 callback_data.EventChar = (ImWchar)c;
9898 callback_data.Flags = flags;
9899 callback_data.UserData = user_data;
9900 if (callback(&callback_data) != 0)
9901 return false;
9902 *p_char = callback_data.EventChar;
9903 if (!callback_data.EventChar)
9904 return false;
9905 }
9906
9907 return true;
9908 }
9909
9910 // Edit a string of text
9911 // 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.
9912 // 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)9913 bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
9914 {
9915 ImGuiWindow* window = GetCurrentWindow();
9916 if (window->SkipItems)
9917 return false;
9918
9919 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
9920 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
9921
9922 ImGuiContext& g = *GImGui;
9923 const ImGuiIO& io = g.IO;
9924 const ImGuiStyle& style = g.Style;
9925
9926 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
9927 const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
9928 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
9929 const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
9930
9931 if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn
9932 BeginGroup();
9933 const ImGuiID id = window->GetID(label);
9934 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9935 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
9936 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
9937 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));
9938
9939 ImGuiWindow* draw_window = window;
9940 if (is_multiline)
9941 {
9942 ItemAdd(total_bb, id, &frame_bb);
9943 if (!BeginChildFrame(id, frame_bb.GetSize()))
9944 {
9945 EndChildFrame();
9946 EndGroup();
9947 return false;
9948 }
9949 draw_window = GetCurrentWindow();
9950 size.x -= draw_window->ScrollbarSizes.x;
9951 }
9952 else
9953 {
9954 ItemSize(total_bb, style.FramePadding.y);
9955 if (!ItemAdd(total_bb, id, &frame_bb))
9956 return false;
9957 }
9958 const bool hovered = ItemHoverable(frame_bb, id);
9959 if (hovered)
9960 g.MouseCursor = ImGuiMouseCursor_TextInput;
9961
9962 // Password pushes a temporary font with only a fallback glyph
9963 if (is_password)
9964 {
9965 const ImFontGlyph* glyph = g.Font->FindGlyph('*');
9966 ImFont* password_font = &g.InputTextPasswordFont;
9967 password_font->FontSize = g.Font->FontSize;
9968 password_font->Scale = g.Font->Scale;
9969 password_font->DisplayOffset = g.Font->DisplayOffset;
9970 password_font->Ascent = g.Font->Ascent;
9971 password_font->Descent = g.Font->Descent;
9972 password_font->ContainerAtlas = g.Font->ContainerAtlas;
9973 password_font->FallbackGlyph = glyph;
9974 password_font->FallbackAdvanceX = glyph->AdvanceX;
9975 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
9976 PushFont(password_font);
9977 }
9978
9979 // NB: we are only allowed to access 'edit_state' if we are the active widget.
9980 ImGuiTextEditState& edit_state = g.InputTextState;
9981
9982 const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
9983 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
9984 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
9985
9986 const bool user_clicked = hovered && io.MouseClicked[0];
9987 const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
9988 const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
9989
9990 bool clear_active_id = false;
9991
9992 bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
9993 if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
9994 {
9995 if (g.ActiveId != id)
9996 {
9997 // Start edition
9998 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
9999 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
10000 const int prev_len_w = edit_state.CurLenW;
10001 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.
10002 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.
10003 ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size);
10004 const char* buf_end = NULL;
10005 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
10006 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.
10007 edit_state.CursorAnimReset();
10008
10009 // Preserve cursor position and undo/redo stack if we come back to same widget
10010 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
10011 const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW);
10012 if (recycle_state)
10013 {
10014 // Recycle existing cursor/selection/undo stack but clamp position
10015 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
10016 edit_state.CursorClamp();
10017 }
10018 else
10019 {
10020 edit_state.Id = id;
10021 edit_state.ScrollX = 0.0f;
10022 stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
10023 if (!is_multiline && focus_requested_by_code)
10024 select_all = true;
10025 }
10026 if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
10027 edit_state.StbState.insert_mode = true;
10028 if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
10029 select_all = true;
10030 }
10031 SetActiveID(id, window);
10032 SetFocusID(id, window);
10033 FocusWindow(window);
10034 if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
10035 g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
10036 }
10037 else if (io.MouseClicked[0])
10038 {
10039 // Release focus when we click outside
10040 clear_active_id = true;
10041 }
10042
10043 bool value_changed = false;
10044 bool enter_pressed = false;
10045
10046 if (g.ActiveId == id)
10047 {
10048 if (!is_editable && !g.ActiveIdIsJustActivated)
10049 {
10050 // When read-only we always use the live data passed to the function
10051 edit_state.Text.resize(buf_size+1);
10052 const char* buf_end = NULL;
10053 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
10054 edit_state.CurLenA = (int)(buf_end - buf);
10055 edit_state.CursorClamp();
10056 }
10057
10058 edit_state.BufSizeA = buf_size;
10059
10060 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
10061 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
10062 g.ActiveIdAllowOverlap = !io.MouseDown[0];
10063 g.WantTextInputNextFrame = 1;
10064
10065 // Edit in progress
10066 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
10067 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
10068
10069 const bool osx_double_click_selects_words = io.OptMacOSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text
10070 if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0]))
10071 {
10072 edit_state.SelectAll();
10073 edit_state.SelectedAllMouseLock = true;
10074 }
10075 else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0])
10076 {
10077 // Select a word only, OS X style (by simulating keystrokes)
10078 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
10079 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
10080 }
10081 else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
10082 {
10083 if (hovered)
10084 {
10085 stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
10086 edit_state.CursorAnimReset();
10087 }
10088 }
10089 else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
10090 {
10091 stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
10092 edit_state.CursorAnimReset();
10093 edit_state.CursorFollow = true;
10094 }
10095 if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
10096 edit_state.SelectedAllMouseLock = false;
10097
10098 if (io.InputCharacters[0])
10099 {
10100 // Process text input (before we check for Return because using some IME will effectively send a Return?)
10101 // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
10102 if (!(io.KeyCtrl && !io.KeyAlt) && is_editable && !user_nav_input_start)
10103 for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
10104 {
10105 // Insert character if they pass filtering
10106 unsigned int c = (unsigned int)io.InputCharacters[n];
10107 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10108 edit_state.OnKeyPressed((int)c);
10109 }
10110
10111 // Consume characters
10112 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
10113 }
10114 }
10115
10116 bool cancel_edit = false;
10117 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
10118 {
10119 // Handle key-presses
10120 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
10121 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
10122 const bool is_wordmove_key_down = io.OptMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
10123 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
10124 const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
10125 const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
10126
10127 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());
10128 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());
10129 const bool is_paste = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
10130
10131 if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
10132 else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
10133 else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
10134 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
10135 else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
10136 else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
10137 else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
10138 else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
10139 {
10140 if (!edit_state.HasSelection())
10141 {
10142 if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
10143 else if (io.OptMacOSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
10144 }
10145 edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
10146 }
10147 else if (IsKeyPressedMap(ImGuiKey_Enter))
10148 {
10149 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
10150 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
10151 {
10152 enter_pressed = clear_active_id = true;
10153 }
10154 else if (is_editable)
10155 {
10156 unsigned int c = '\n'; // Insert new line
10157 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10158 edit_state.OnKeyPressed((int)c);
10159 }
10160 }
10161 else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
10162 {
10163 unsigned int c = '\t'; // Insert TAB
10164 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10165 edit_state.OnKeyPressed((int)c);
10166 }
10167 else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; }
10168 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); }
10169 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); }
10170 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; }
10171 else if (is_cut || is_copy)
10172 {
10173 // Cut, Copy
10174 if (io.SetClipboardTextFn)
10175 {
10176 const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
10177 const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
10178 edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1);
10179 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie);
10180 SetClipboardText(edit_state.TempTextBuffer.Data);
10181 }
10182
10183 if (is_cut)
10184 {
10185 if (!edit_state.HasSelection())
10186 edit_state.SelectAll();
10187 edit_state.CursorFollow = true;
10188 stb_textedit_cut(&edit_state, &edit_state.StbState);
10189 }
10190 }
10191 else if (is_paste)
10192 {
10193 // Paste
10194 if (const char* clipboard = GetClipboardText())
10195 {
10196 // Filter pasted buffer
10197 const int clipboard_len = (int)strlen(clipboard);
10198 ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
10199 int clipboard_filtered_len = 0;
10200 for (const char* s = clipboard; *s; )
10201 {
10202 unsigned int c;
10203 s += ImTextCharFromUtf8(&c, s, NULL);
10204 if (c == 0)
10205 break;
10206 if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data))
10207 continue;
10208 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
10209 }
10210 clipboard_filtered[clipboard_filtered_len] = 0;
10211 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
10212 {
10213 stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
10214 edit_state.CursorFollow = true;
10215 }
10216 ImGui::MemFree(clipboard_filtered);
10217 }
10218 }
10219 }
10220
10221 if (g.ActiveId == id)
10222 {
10223 if (cancel_edit)
10224 {
10225 // Restore initial value
10226 if (is_editable)
10227 {
10228 ImStrncpy(buf, edit_state.InitialText.Data, buf_size);
10229 value_changed = true;
10230 }
10231 }
10232
10233 // 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.
10234 // 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.
10235 bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
10236 if (apply_edit_back_to_user_buffer)
10237 {
10238 // Apply new value immediately - copy modified buffer back
10239 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
10240 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
10241 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
10242 if (is_editable)
10243 {
10244 edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4);
10245 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL);
10246 }
10247
10248 // User callback
10249 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
10250 {
10251 IM_ASSERT(callback != NULL);
10252
10253 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
10254 ImGuiInputTextFlags event_flag = 0;
10255 ImGuiKey event_key = ImGuiKey_COUNT;
10256 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
10257 {
10258 event_flag = ImGuiInputTextFlags_CallbackCompletion;
10259 event_key = ImGuiKey_Tab;
10260 }
10261 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
10262 {
10263 event_flag = ImGuiInputTextFlags_CallbackHistory;
10264 event_key = ImGuiKey_UpArrow;
10265 }
10266 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
10267 {
10268 event_flag = ImGuiInputTextFlags_CallbackHistory;
10269 event_key = ImGuiKey_DownArrow;
10270 }
10271 else if (flags & ImGuiInputTextFlags_CallbackAlways)
10272 event_flag = ImGuiInputTextFlags_CallbackAlways;
10273
10274 if (event_flag)
10275 {
10276 ImGuiTextEditCallbackData callback_data;
10277 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
10278 callback_data.EventFlag = event_flag;
10279 callback_data.Flags = flags;
10280 callback_data.UserData = user_data;
10281 callback_data.ReadOnly = !is_editable;
10282
10283 callback_data.EventKey = event_key;
10284 callback_data.Buf = edit_state.TempTextBuffer.Data;
10285 callback_data.BufTextLen = edit_state.CurLenA;
10286 callback_data.BufSize = edit_state.BufSizeA;
10287 callback_data.BufDirty = false;
10288
10289 // 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)
10290 ImWchar* text = edit_state.Text.Data;
10291 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
10292 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
10293 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
10294
10295 // Call user code
10296 callback(&callback_data);
10297
10298 // Read back what user may have modified
10299 IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields
10300 IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA);
10301 IM_ASSERT(callback_data.Flags == flags);
10302 if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos);
10303 if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart);
10304 if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd);
10305 if (callback_data.BufDirty)
10306 {
10307 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
10308 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL);
10309 edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
10310 edit_state.CursorAnimReset();
10311 }
10312 }
10313 }
10314
10315 // Copy back to user buffer
10316 if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0)
10317 {
10318 ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size);
10319 value_changed = true;
10320 }
10321 }
10322 }
10323
10324 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
10325 if (clear_active_id && g.ActiveId == id)
10326 ClearActiveID();
10327
10328 // Render
10329 // 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.
10330 const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL;
10331
10332 RenderNavHighlight(frame_bb, id);
10333 if (!is_multiline)
10334 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
10335
10336 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
10337 ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
10338 ImVec2 text_size(0.f, 0.f);
10339 const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
10340 if (g.ActiveId == id || is_currently_scrolling)
10341 {
10342 edit_state.CursorAnim += io.DeltaTime;
10343
10344 // This is going to be messy. We need to:
10345 // - Display the text (this alone can be more easily clipped)
10346 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
10347 // - Measure text height (for scrollbar)
10348 // 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)
10349 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
10350 const ImWchar* text_begin = edit_state.Text.Data;
10351 ImVec2 cursor_offset, select_start_offset;
10352
10353 {
10354 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
10355 const ImWchar* searches_input_ptr[2];
10356 searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
10357 searches_input_ptr[1] = NULL;
10358 int searches_remaining = 1;
10359 int searches_result_line_number[2] = { -1, -999 };
10360 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
10361 {
10362 searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
10363 searches_result_line_number[1] = -1;
10364 searches_remaining++;
10365 }
10366
10367 // Iterate all lines to find our line numbers
10368 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
10369 searches_remaining += is_multiline ? 1 : 0;
10370 int line_count = 0;
10371 for (const ImWchar* s = text_begin; *s != 0; s++)
10372 if (*s == '\n')
10373 {
10374 line_count++;
10375 if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
10376 if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
10377 }
10378 line_count++;
10379 if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
10380 if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
10381
10382 // Calculate 2d position by finding the beginning of the line and measuring distance
10383 cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
10384 cursor_offset.y = searches_result_line_number[0] * g.FontSize;
10385 if (searches_result_line_number[1] >= 0)
10386 {
10387 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
10388 select_start_offset.y = searches_result_line_number[1] * g.FontSize;
10389 }
10390
10391 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
10392 if (is_multiline)
10393 text_size = ImVec2(size.x, line_count * g.FontSize);
10394 }
10395
10396 // Scroll
10397 if (edit_state.CursorFollow)
10398 {
10399 // Horizontal scroll in chunks of quarter width
10400 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
10401 {
10402 const float scroll_increment_x = size.x * 0.25f;
10403 if (cursor_offset.x < edit_state.ScrollX)
10404 edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
10405 else if (cursor_offset.x - size.x >= edit_state.ScrollX)
10406 edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
10407 }
10408 else
10409 {
10410 edit_state.ScrollX = 0.0f;
10411 }
10412
10413 // Vertical scroll
10414 if (is_multiline)
10415 {
10416 float scroll_y = draw_window->Scroll.y;
10417 if (cursor_offset.y - g.FontSize < scroll_y)
10418 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
10419 else if (cursor_offset.y - size.y >= scroll_y)
10420 scroll_y = cursor_offset.y - size.y;
10421 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
10422 draw_window->Scroll.y = scroll_y;
10423 render_pos.y = draw_window->DC.CursorPos.y;
10424 }
10425 }
10426 edit_state.CursorFollow = false;
10427 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
10428
10429 // Draw selection
10430 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
10431 {
10432 const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
10433 const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
10434
10435 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.
10436 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
10437 ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
10438 ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
10439 for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
10440 {
10441 if (rect_pos.y > clip_rect.w + g.FontSize)
10442 break;
10443 if (rect_pos.y < clip_rect.y)
10444 {
10445 while (p < text_selected_end)
10446 if (*p++ == '\n')
10447 break;
10448 }
10449 else
10450 {
10451 ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
10452 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
10453 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
10454 rect.ClipWith(clip_rect);
10455 if (rect.Overlaps(clip_rect))
10456 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
10457 }
10458 rect_pos.x = render_pos.x - render_scroll.x;
10459 rect_pos.y += g.FontSize;
10460 }
10461 }
10462
10463 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);
10464
10465 // Draw blinking cursor
10466 bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
10467 ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
10468 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);
10469 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
10470 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
10471
10472 // 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.)
10473 if (is_editable)
10474 g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
10475 }
10476 else
10477 {
10478 // Render text only
10479 const char* buf_end = NULL;
10480 if (is_multiline)
10481 text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
10482 draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
10483 }
10484
10485 if (is_multiline)
10486 {
10487 Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
10488 EndChildFrame();
10489 EndGroup();
10490 }
10491
10492 if (is_password)
10493 PopFont();
10494
10495 // Log as text
10496 if (g.LogEnabled && !is_password)
10497 LogRenderedText(&render_pos, buf_display, NULL);
10498
10499 if (label_size.x > 0)
10500 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
10501
10502 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
10503 return enter_pressed;
10504 else
10505 return value_changed;
10506 }
10507
InputText(const char * label,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)10508 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10509 {
10510 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
10511 return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
10512 }
10513
InputTextMultiline(const char * label,char * buf,size_t buf_size,const ImVec2 & size,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)10514 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10515 {
10516 return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
10517 }
10518
10519 // 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)10520 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)
10521 {
10522 ImGuiWindow* window = GetCurrentWindow();
10523 if (window->SkipItems)
10524 return false;
10525
10526 ImGuiContext& g = *GImGui;
10527 const ImGuiStyle& style = g.Style;
10528 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10529
10530 BeginGroup();
10531 PushID(label);
10532 const ImVec2 button_sz = ImVec2(GetFrameHeight(), GetFrameHeight());
10533 if (step_ptr)
10534 PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2));
10535
10536 char buf[64];
10537 DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf));
10538
10539 bool value_changed = false;
10540 if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
10541 extra_flags |= ImGuiInputTextFlags_CharsDecimal;
10542 extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
10543 if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
10544 value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format);
10545
10546 // Step buttons
10547 if (step_ptr)
10548 {
10549 PopItemWidth();
10550 SameLine(0, style.ItemInnerSpacing.x);
10551 if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
10552 {
10553 DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr);
10554 value_changed = true;
10555 }
10556 SameLine(0, style.ItemInnerSpacing.x);
10557 if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
10558 {
10559 DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr);
10560 value_changed = true;
10561 }
10562 }
10563 PopID();
10564
10565 if (label_size.x > 0)
10566 {
10567 SameLine(0, style.ItemInnerSpacing.x);
10568 RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label);
10569 ItemSize(label_size, style.FramePadding.y);
10570 }
10571 EndGroup();
10572
10573 return value_changed;
10574 }
10575
InputFloat(const char * label,float * v,float step,float step_fast,int decimal_precision,ImGuiInputTextFlags extra_flags)10576 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
10577 {
10578 extra_flags |= ImGuiInputTextFlags_CharsScientific;
10579 if (decimal_precision < 0)
10580 {
10581 // 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
10582 return InputScalarEx(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), "%f", extra_flags);
10583 }
10584 else
10585 {
10586 char display_format[16];
10587 ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision);
10588 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);
10589 }
10590 }
10591
InputDouble(const char * label,double * v,double step,double step_fast,const char * display_format,ImGuiInputTextFlags extra_flags)10592 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* display_format, ImGuiInputTextFlags extra_flags)
10593 {
10594 extra_flags |= ImGuiInputTextFlags_CharsScientific;
10595 return InputScalarEx(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), display_format, extra_flags);
10596 }
10597
InputInt(const char * label,int * v,int step,int step_fast,ImGuiInputTextFlags extra_flags)10598 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
10599 {
10600 // 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.
10601 const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
10602 return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), scalar_format, extra_flags);
10603 }
10604
InputFloatN(const char * label,float * v,int components,int decimal_precision,ImGuiInputTextFlags extra_flags)10605 bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags)
10606 {
10607 ImGuiWindow* window = GetCurrentWindow();
10608 if (window->SkipItems)
10609 return false;
10610
10611 ImGuiContext& g = *GImGui;
10612 bool value_changed = false;
10613 BeginGroup();
10614 PushID(label);
10615 PushMultiItemsWidths(components);
10616 for (int i = 0; i < components; i++)
10617 {
10618 PushID(i);
10619 value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags);
10620 SameLine(0, g.Style.ItemInnerSpacing.x);
10621 PopID();
10622 PopItemWidth();
10623 }
10624 PopID();
10625
10626 TextUnformatted(label, FindRenderedTextEnd(label));
10627 EndGroup();
10628
10629 return value_changed;
10630 }
10631
InputFloat2(const char * label,float v[2],int decimal_precision,ImGuiInputTextFlags extra_flags)10632 bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
10633 {
10634 return InputFloatN(label, v, 2, decimal_precision, extra_flags);
10635 }
10636
InputFloat3(const char * label,float v[3],int decimal_precision,ImGuiInputTextFlags extra_flags)10637 bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
10638 {
10639 return InputFloatN(label, v, 3, decimal_precision, extra_flags);
10640 }
10641
InputFloat4(const char * label,float v[4],int decimal_precision,ImGuiInputTextFlags extra_flags)10642 bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
10643 {
10644 return InputFloatN(label, v, 4, decimal_precision, extra_flags);
10645 }
10646
InputIntN(const char * label,int * v,int components,ImGuiInputTextFlags extra_flags)10647 bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags)
10648 {
10649 ImGuiWindow* window = GetCurrentWindow();
10650 if (window->SkipItems)
10651 return false;
10652
10653 ImGuiContext& g = *GImGui;
10654 bool value_changed = false;
10655 BeginGroup();
10656 PushID(label);
10657 PushMultiItemsWidths(components);
10658 for (int i = 0; i < components; i++)
10659 {
10660 PushID(i);
10661 value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags);
10662 SameLine(0, g.Style.ItemInnerSpacing.x);
10663 PopID();
10664 PopItemWidth();
10665 }
10666 PopID();
10667
10668 TextUnformatted(label, FindRenderedTextEnd(label));
10669 EndGroup();
10670
10671 return value_changed;
10672 }
10673
InputInt2(const char * label,int v[2],ImGuiInputTextFlags extra_flags)10674 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
10675 {
10676 return InputIntN(label, v, 2, extra_flags);
10677 }
10678
InputInt3(const char * label,int v[3],ImGuiInputTextFlags extra_flags)10679 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
10680 {
10681 return InputIntN(label, v, 3, extra_flags);
10682 }
10683
InputInt4(const char * label,int v[4],ImGuiInputTextFlags extra_flags)10684 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
10685 {
10686 return InputIntN(label, v, 4, extra_flags);
10687 }
10688
CalcMaxPopupHeightFromItemCount(int items_count)10689 static float CalcMaxPopupHeightFromItemCount(int items_count)
10690 {
10691 ImGuiContext& g = *GImGui;
10692 if (items_count <= 0)
10693 return FLT_MAX;
10694 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
10695 }
10696
BeginCombo(const char * label,const char * preview_value,ImGuiComboFlags flags)10697 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
10698 {
10699 // Always consume the SetNextWindowSizeConstraint() call in our early return paths
10700 ImGuiContext& g = *GImGui;
10701 ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
10702 g.NextWindowData.SizeConstraintCond = 0;
10703
10704 ImGuiWindow* window = GetCurrentWindow();
10705 if (window->SkipItems)
10706 return false;
10707
10708 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
10709
10710 const ImGuiStyle& style = g.Style;
10711 const ImGuiID id = window->GetID(label);
10712
10713 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
10714 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10715 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
10716 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
10717 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));
10718 ItemSize(total_bb, style.FramePadding.y);
10719 if (!ItemAdd(total_bb, id, &frame_bb))
10720 return false;
10721
10722 bool hovered, held;
10723 bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
10724 bool popup_open = IsPopupOpen(id);
10725
10726 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
10727 const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
10728 RenderNavHighlight(frame_bb, id);
10729 if (!(flags & ImGuiComboFlags_NoPreview))
10730 window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
10731 if (!(flags & ImGuiComboFlags_NoArrowButton))
10732 {
10733 window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
10734 RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
10735 }
10736 RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
10737 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
10738 RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
10739 if (label_size.x > 0)
10740 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
10741
10742 if ((pressed || g.NavActivateId == id) && !popup_open)
10743 {
10744 if (window->DC.NavLayerCurrent == 0)
10745 window->NavLastIds[0] = id;
10746 OpenPopupEx(id);
10747 popup_open = true;
10748 }
10749
10750 if (!popup_open)
10751 return false;
10752
10753 if (backup_next_window_size_constraint)
10754 {
10755 g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
10756 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
10757 }
10758 else
10759 {
10760 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
10761 flags |= ImGuiComboFlags_HeightRegular;
10762 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
10763 int popup_max_height_in_items = -1;
10764 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
10765 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
10766 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
10767 SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
10768 }
10769
10770 char name[16];
10771 ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
10772
10773 // Peak into expected window size so we can position it
10774 if (ImGuiWindow* popup_window = FindWindowByName(name))
10775 if (popup_window->WasActive)
10776 {
10777 ImVec2 size_contents = CalcSizeContents(popup_window);
10778 ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents));
10779 if (flags & ImGuiComboFlags_PopupAlignLeft)
10780 popup_window->AutoPosLastDirection = ImGuiDir_Left;
10781 ImRect r_outer = FindScreenRectForWindow(popup_window);
10782 ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
10783 SetNextWindowPos(pos);
10784 }
10785
10786 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
10787 if (!Begin(name, NULL, window_flags))
10788 {
10789 EndPopup();
10790 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
10791 return false;
10792 }
10793
10794 // Horizontally align ourselves with the framed text
10795 if (style.FramePadding.x != style.WindowPadding.x)
10796 Indent(style.FramePadding.x - style.WindowPadding.x);
10797
10798 return true;
10799 }
10800
EndCombo()10801 void ImGui::EndCombo()
10802 {
10803 const ImGuiStyle& style = GImGui->Style;
10804 if (style.FramePadding.x != style.WindowPadding.x)
10805 Unindent(style.FramePadding.x - style.WindowPadding.x);
10806 EndPopup();
10807 }
10808
10809 // 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)10810 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)
10811 {
10812 ImGuiContext& g = *GImGui;
10813
10814 const char* preview_text = NULL;
10815 if (*current_item >= 0 && *current_item < items_count)
10816 items_getter(data, *current_item, &preview_text);
10817
10818 // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here.
10819 if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
10820 {
10821 float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
10822 SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, popup_max_height));
10823 }
10824
10825 if (!BeginCombo(label, preview_text, 0))
10826 return false;
10827
10828 // Display items
10829 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
10830 bool value_changed = false;
10831 for (int i = 0; i < items_count; i++)
10832 {
10833 PushID((void*)(intptr_t)i);
10834 const bool item_selected = (i == *current_item);
10835 const char* item_text;
10836 if (!items_getter(data, i, &item_text))
10837 item_text = "*Unknown item*";
10838 if (Selectable(item_text, item_selected))
10839 {
10840 value_changed = true;
10841 *current_item = i;
10842 }
10843 if (item_selected)
10844 SetItemDefaultFocus();
10845 PopID();
10846 }
10847
10848 EndCombo();
10849 return value_changed;
10850 }
10851
Items_ArrayGetter(void * data,int idx,const char ** out_text)10852 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
10853 {
10854 const char* const* items = (const char* const*)data;
10855 if (out_text)
10856 *out_text = items[idx];
10857 return true;
10858 }
10859
Items_SingleStringGetter(void * data,int idx,const char ** out_text)10860 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
10861 {
10862 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
10863 const char* items_separated_by_zeros = (const char*)data;
10864 int items_count = 0;
10865 const char* p = items_separated_by_zeros;
10866 while (*p)
10867 {
10868 if (idx == items_count)
10869 break;
10870 p += strlen(p) + 1;
10871 items_count++;
10872 }
10873 if (!*p)
10874 return false;
10875 if (out_text)
10876 *out_text = p;
10877 return true;
10878 }
10879
10880 // 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)10881 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
10882 {
10883 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
10884 return value_changed;
10885 }
10886
10887 // 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)10888 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
10889 {
10890 int items_count = 0;
10891 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
10892 while (*p)
10893 {
10894 p += strlen(p) + 1;
10895 items_count++;
10896 }
10897 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
10898 return value_changed;
10899 }
10900
10901 // Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
10902 // 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)10903 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
10904 {
10905 ImGuiWindow* window = GetCurrentWindow();
10906 if (window->SkipItems)
10907 return false;
10908
10909 ImGuiContext& g = *GImGui;
10910 const ImGuiStyle& style = g.Style;
10911
10912 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
10913 PopClipRect();
10914
10915 ImGuiID id = window->GetID(label);
10916 ImVec2 label_size = CalcTextSize(label, NULL, true);
10917 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
10918 ImVec2 pos = window->DC.CursorPos;
10919 pos.y += window->DC.CurrentLineTextBaseOffset;
10920 ImRect bb(pos, pos + size);
10921 ItemSize(bb);
10922
10923 // Fill horizontal space.
10924 ImVec2 window_padding = window->WindowPadding;
10925 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
10926 float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
10927 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);
10928 ImRect bb_with_spacing(pos, pos + size_draw);
10929 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
10930 bb_with_spacing.Max.x += window_padding.x;
10931
10932 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
10933 float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
10934 float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
10935 float spacing_R = style.ItemSpacing.x - spacing_L;
10936 float spacing_D = style.ItemSpacing.y - spacing_U;
10937 bb_with_spacing.Min.x -= spacing_L;
10938 bb_with_spacing.Min.y -= spacing_U;
10939 bb_with_spacing.Max.x += spacing_R;
10940 bb_with_spacing.Max.y += spacing_D;
10941 if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
10942 {
10943 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
10944 PushColumnClipRect();
10945 return false;
10946 }
10947
10948 ImGuiButtonFlags button_flags = 0;
10949 if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID;
10950 if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease;
10951 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
10952 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
10953 bool hovered, held;
10954 bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags);
10955 if (flags & ImGuiSelectableFlags_Disabled)
10956 selected = false;
10957
10958 // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
10959 if (pressed || hovered)// && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f))
10960 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerActiveMask)
10961 {
10962 g.NavDisableHighlight = true;
10963 SetNavID(id, window->DC.NavLayerCurrent);
10964 }
10965
10966 // Render
10967 if (hovered || selected)
10968 {
10969 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
10970 RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f);
10971 RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
10972 }
10973
10974 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
10975 {
10976 PushColumnClipRect();
10977 bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x);
10978 }
10979
10980 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
10981 RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
10982 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
10983
10984 // Automatically close popups
10985 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
10986 CloseCurrentPopup();
10987 return pressed;
10988 }
10989
Selectable(const char * label,bool * p_selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)10990 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
10991 {
10992 if (Selectable(label, *p_selected, flags, size_arg))
10993 {
10994 *p_selected = !*p_selected;
10995 return true;
10996 }
10997 return false;
10998 }
10999
11000 // Helper to calculate the size of a listbox and display a label on the right.
11001 // 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)11002 bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
11003 {
11004 ImGuiWindow* window = GetCurrentWindow();
11005 if (window->SkipItems)
11006 return false;
11007
11008 const ImGuiStyle& style = GetStyle();
11009 const ImGuiID id = GetID(label);
11010 const ImVec2 label_size = CalcTextSize(label, NULL, true);
11011
11012 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
11013 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
11014 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
11015 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
11016 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
11017 window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
11018
11019 BeginGroup();
11020 if (label_size.x > 0)
11021 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
11022
11023 BeginChildFrame(id, frame_bb.GetSize());
11024 return true;
11025 }
11026
ListBoxHeader(const char * label,int items_count,int height_in_items)11027 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
11028 {
11029 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
11030 // 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.
11031 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
11032 if (height_in_items < 0)
11033 height_in_items = ImMin(items_count, 7);
11034 float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
11035
11036 // 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().
11037 ImVec2 size;
11038 size.x = 0.0f;
11039 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
11040 return ListBoxHeader(label, size);
11041 }
11042
ListBoxFooter()11043 void ImGui::ListBoxFooter()
11044 {
11045 ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
11046 const ImRect bb = parent_window->DC.LastItemRect;
11047 const ImGuiStyle& style = GetStyle();
11048
11049 EndChildFrame();
11050
11051 // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
11052 // We call SameLine() to restore DC.CurrentLine* data
11053 SameLine();
11054 parent_window->DC.CursorPos = bb.Min;
11055 ItemSize(bb, style.FramePadding.y);
11056 EndGroup();
11057 }
11058
ListBox(const char * label,int * current_item,const char * const items[],int items_count,int height_items)11059 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
11060 {
11061 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
11062 return value_changed;
11063 }
11064
ListBox(const char * label,int * current_item,bool (* items_getter)(void *,int,const char **),void * data,int items_count,int height_in_items)11065 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)
11066 {
11067 if (!ListBoxHeader(label, items_count, height_in_items))
11068 return false;
11069
11070 // 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.
11071 bool value_changed = false;
11072 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.
11073 while (clipper.Step())
11074 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
11075 {
11076 const bool item_selected = (i == *current_item);
11077 const char* item_text;
11078 if (!items_getter(data, i, &item_text))
11079 item_text = "*Unknown item*";
11080
11081 PushID(i);
11082 if (Selectable(item_text, item_selected))
11083 {
11084 *current_item = i;
11085 value_changed = true;
11086 }
11087 if (item_selected)
11088 SetItemDefaultFocus();
11089 PopID();
11090 }
11091 ListBoxFooter();
11092 return value_changed;
11093 }
11094
MenuItem(const char * label,const char * shortcut,bool selected,bool enabled)11095 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
11096 {
11097 ImGuiWindow* window = GetCurrentWindow();
11098 if (window->SkipItems)
11099 return false;
11100
11101 ImGuiContext& g = *GImGui;
11102 ImGuiStyle& style = g.Style;
11103 ImVec2 pos = window->DC.CursorPos;
11104 ImVec2 label_size = CalcTextSize(label, NULL, true);
11105
11106 ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
11107 bool pressed;
11108 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
11109 {
11110 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
11111 // Note that in this situation we render neither the shortcut neither the selected tick mark
11112 float w = label_size.x;
11113 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
11114 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
11115 pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
11116 PopStyleVar();
11117 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().
11118 }
11119 else
11120 {
11121 ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
11122 float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
11123 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
11124 pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
11125 if (shortcut_size.x > 0.0f)
11126 {
11127 PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11128 if (shortcut)
11129 RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
11130 PopStyleColor();
11131 }
11132 if (selected)
11133 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);
11134 }
11135 return pressed;
11136 }
11137
MenuItem(const char * label,const char * shortcut,bool * p_selected,bool enabled)11138 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
11139 {
11140 if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
11141 {
11142 if (p_selected)
11143 *p_selected = !*p_selected;
11144 return true;
11145 }
11146 return false;
11147 }
11148
BeginMainMenuBar()11149 bool ImGui::BeginMainMenuBar()
11150 {
11151 ImGuiContext& g = *GImGui;
11152 SetNextWindowPos(ImVec2(0.0f, 0.0f));
11153 SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f));
11154 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
11155 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
11156 if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar)
11157 || !BeginMenuBar())
11158 {
11159 End();
11160 PopStyleVar(2);
11161 return false;
11162 }
11163 g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x;
11164 return true;
11165 }
11166
EndMainMenuBar()11167 void ImGui::EndMainMenuBar()
11168 {
11169 EndMenuBar();
11170
11171 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
11172 ImGuiContext& g = *GImGui;
11173 if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
11174 FocusFrontMostActiveWindow(g.NavWindow);
11175
11176 End();
11177 PopStyleVar(2);
11178 }
11179
BeginMenuBar()11180 bool ImGui::BeginMenuBar()
11181 {
11182 ImGuiWindow* window = GetCurrentWindow();
11183 if (window->SkipItems)
11184 return false;
11185 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
11186 return false;
11187
11188 IM_ASSERT(!window->DC.MenuBarAppending);
11189 BeginGroup(); // Save position
11190 PushID("##menubar");
11191
11192 // 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.
11193 // 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.
11194 ImRect bar_rect = window->MenuBarRect();
11195 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));
11196 clip_rect.ClipWith(window->WindowRectClipped);
11197 PushClipRect(clip_rect.Min, clip_rect.Max, false);
11198
11199 window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y);// + g.Style.FramePadding.y);
11200 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
11201 window->DC.NavLayerCurrent++;
11202 window->DC.NavLayerCurrentMask <<= 1;
11203 window->DC.MenuBarAppending = true;
11204 AlignTextToFramePadding();
11205 return true;
11206 }
11207
EndMenuBar()11208 void ImGui::EndMenuBar()
11209 {
11210 ImGuiWindow* window = GetCurrentWindow();
11211 if (window->SkipItems)
11212 return;
11213 ImGuiContext& g = *GImGui;
11214
11215 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
11216 if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
11217 {
11218 ImGuiWindow* nav_earliest_child = g.NavWindow;
11219 while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
11220 nav_earliest_child = nav_earliest_child->ParentWindow;
11221 if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
11222 {
11223 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
11224 // 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)
11225 IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
11226 FocusWindow(window);
11227 SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
11228 g.NavLayer = 1;
11229 g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
11230 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
11231 NavMoveRequestCancel();
11232 }
11233 }
11234
11235 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
11236 IM_ASSERT(window->DC.MenuBarAppending);
11237 PopClipRect();
11238 PopID();
11239 window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x;
11240 window->DC.GroupStack.back().AdvanceCursor = false;
11241 EndGroup();
11242 window->DC.LayoutType = ImGuiLayoutType_Vertical;
11243 window->DC.NavLayerCurrent--;
11244 window->DC.NavLayerCurrentMask >>= 1;
11245 window->DC.MenuBarAppending = false;
11246 }
11247
BeginMenu(const char * label,bool enabled)11248 bool ImGui::BeginMenu(const char* label, bool enabled)
11249 {
11250 ImGuiWindow* window = GetCurrentWindow();
11251 if (window->SkipItems)
11252 return false;
11253
11254 ImGuiContext& g = *GImGui;
11255 const ImGuiStyle& style = g.Style;
11256 const ImGuiID id = window->GetID(label);
11257
11258 ImVec2 label_size = CalcTextSize(label, NULL, true);
11259
11260 bool pressed;
11261 bool menu_is_open = IsPopupOpen(id);
11262 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
11263 ImGuiWindow* backed_nav_window = g.NavWindow;
11264 if (menuset_is_open)
11265 g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
11266
11267 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos).
11268 ImVec2 popup_pos, pos = window->DC.CursorPos;
11269 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
11270 {
11271 // Menu inside an horizontal menu bar
11272 // Selectable extend their highlight by half ItemSpacing in each direction.
11273 // For ChildMenu, the popup position will be overwritten by the call to FindBestPopupWindowPos() in Begin()
11274 popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
11275 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
11276 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
11277 float w = label_size.x;
11278 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
11279 PopStyleVar();
11280 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().
11281 }
11282 else
11283 {
11284 // Menu inside a menu
11285 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
11286 float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
11287 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
11288 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
11289 if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11290 RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
11291 if (!enabled) PopStyleColor();
11292 }
11293
11294 const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
11295 if (menuset_is_open)
11296 g.NavWindow = backed_nav_window;
11297
11298 bool want_open = false, want_close = false;
11299 if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
11300 {
11301 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
11302 bool moving_within_opened_triangle = false;
11303 if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
11304 {
11305 if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
11306 {
11307 ImRect next_window_rect = next_window->Rect();
11308 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
11309 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
11310 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
11311 float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
11312 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
11313 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?
11314 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
11315 moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
11316 //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
11317 }
11318 }
11319
11320 want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
11321 want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
11322
11323 if (g.NavActivateId == id)
11324 {
11325 want_close = menu_is_open;
11326 want_open = !menu_is_open;
11327 }
11328 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
11329 {
11330 want_open = true;
11331 NavMoveRequestCancel();
11332 }
11333 }
11334 else
11335 {
11336 // Menu bar
11337 if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
11338 {
11339 want_close = true;
11340 want_open = menu_is_open = false;
11341 }
11342 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
11343 {
11344 want_open = true;
11345 }
11346 else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
11347 {
11348 want_open = true;
11349 NavMoveRequestCancel();
11350 }
11351 }
11352
11353 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.. }'
11354 want_close = true;
11355 if (want_close && IsPopupOpen(id))
11356 ClosePopupToLevel(g.CurrentPopupStack.Size);
11357
11358 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
11359 {
11360 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
11361 OpenPopup(label);
11362 return false;
11363 }
11364
11365 menu_is_open |= want_open;
11366 if (want_open)
11367 OpenPopup(label);
11368
11369 if (menu_is_open)
11370 {
11371 SetNextWindowPos(popup_pos, ImGuiCond_Always);
11372 ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu);
11373 menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
11374 }
11375
11376 return menu_is_open;
11377 }
11378
EndMenu()11379 void ImGui::EndMenu()
11380 {
11381 // Nav: When a left move request _within our child menu_ failed, close the menu.
11382 // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
11383 // 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.
11384 ImGuiContext& g = *GImGui;
11385 ImGuiWindow* window = g.CurrentWindow;
11386 if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
11387 {
11388 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
11389 NavMoveRequestCancel();
11390 }
11391
11392 EndPopup();
11393 }
11394
11395 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
ColorTooltip(const char * text,const float * col,ImGuiColorEditFlags flags)11396 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
11397 {
11398 ImGuiContext& g = *GImGui;
11399
11400 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]);
11401 BeginTooltipEx(0, true);
11402
11403 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
11404 if (text_end > text)
11405 {
11406 TextUnformatted(text, text_end);
11407 Separator();
11408 }
11409
11410 ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
11411 ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
11412 SameLine();
11413 if (flags & ImGuiColorEditFlags_NoAlpha)
11414 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]);
11415 else
11416 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]);
11417 EndTooltip();
11418 }
11419
ImAlphaBlendColor(ImU32 col_a,ImU32 col_b)11420 static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
11421 {
11422 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
11423 int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
11424 int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
11425 int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
11426 return IM_COL32(r, g, b, 0xFF);
11427 }
11428
11429 // 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.
11430 // 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)11431 void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
11432 {
11433 ImGuiWindow* window = GetCurrentWindow();
11434 if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
11435 {
11436 ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
11437 ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
11438 window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
11439
11440 int yi = 0;
11441 for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
11442 {
11443 float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
11444 if (y2 <= y1)
11445 continue;
11446 for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
11447 {
11448 float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
11449 if (x2 <= x1)
11450 continue;
11451 int rounding_corners_flags_cell = 0;
11452 if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
11453 if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
11454 rounding_corners_flags_cell &= rounding_corners_flags;
11455 window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
11456 }
11457 }
11458 }
11459 else
11460 {
11461 window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
11462 }
11463 }
11464
SetColorEditOptions(ImGuiColorEditFlags flags)11465 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
11466 {
11467 ImGuiContext& g = *GImGui;
11468 if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
11469 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
11470 if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
11471 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
11472 if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
11473 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
11474 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
11475 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
11476 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
11477 g.ColorEditOptions = flags;
11478 }
11479
11480 // A little colored square. Return true when clicked.
11481 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
11482 // '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)11483 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
11484 {
11485 ImGuiWindow* window = GetCurrentWindow();
11486 if (window->SkipItems)
11487 return false;
11488
11489 ImGuiContext& g = *GImGui;
11490 const ImGuiID id = window->GetID(desc_id);
11491 float default_size = GetFrameHeight();
11492 if (size.x == 0.0f)
11493 size.x = default_size;
11494 if (size.y == 0.0f)
11495 size.y = default_size;
11496 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
11497 ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
11498 if (!ItemAdd(bb, id))
11499 return false;
11500
11501 bool hovered, held;
11502 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
11503
11504 if (flags & ImGuiColorEditFlags_NoAlpha)
11505 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
11506
11507 ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
11508 float grid_step = ImMin(size.x, size.y) / 2.99f;
11509 float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
11510 ImRect bb_inner = bb;
11511 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.
11512 bb_inner.Expand(off);
11513 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
11514 {
11515 float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
11516 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);
11517 window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
11518 }
11519 else
11520 {
11521 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
11522 ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
11523 if (col_source.w < 1.0f)
11524 RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
11525 else
11526 window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
11527 }
11528 RenderNavHighlight(bb, id);
11529 if (g.Style.FrameBorderSize > 0.0f)
11530 RenderFrameBorder(bb.Min, bb.Max, rounding);
11531 else
11532 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
11533
11534 // Drag and Drop Source
11535 if (g.ActiveId == id && BeginDragDropSource()) // NB: The ActiveId test is merely an optional micro-optimization
11536 {
11537 if (flags & ImGuiColorEditFlags_NoAlpha)
11538 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
11539 else
11540 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
11541 ColorButton(desc_id, col, flags);
11542 SameLine();
11543 TextUnformatted("Color");
11544 EndDragDropSource();
11545 hovered = false;
11546 }
11547
11548 // Tooltip
11549 if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
11550 ColorTooltip(desc_id, (const float*)&col, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
11551
11552 return pressed;
11553 }
11554
ColorEdit3(const char * label,float col[3],ImGuiColorEditFlags flags)11555 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
11556 {
11557 return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
11558 }
11559
ColorEditOptionsPopup(const float * col,ImGuiColorEditFlags flags)11560 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
11561 {
11562 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
11563 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
11564 if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
11565 return;
11566 ImGuiContext& g = *GImGui;
11567 ImGuiColorEditFlags opts = g.ColorEditOptions;
11568 if (allow_opt_inputs)
11569 {
11570 if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
11571 if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
11572 if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
11573 }
11574 if (allow_opt_datatype)
11575 {
11576 if (allow_opt_inputs) Separator();
11577 if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
11578 if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
11579 }
11580
11581 if (allow_opt_inputs || allow_opt_datatype)
11582 Separator();
11583 if (Button("Copy as..", ImVec2(-1,0)))
11584 OpenPopup("Copy");
11585 if (BeginPopup("Copy"))
11586 {
11587 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]);
11588 char buf[64];
11589 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
11590 if (Selectable(buf))
11591 SetClipboardText(buf);
11592 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
11593 if (Selectable(buf))
11594 SetClipboardText(buf);
11595 if (flags & ImGuiColorEditFlags_NoAlpha)
11596 ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
11597 else
11598 ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
11599 if (Selectable(buf))
11600 SetClipboardText(buf);
11601 EndPopup();
11602 }
11603
11604 g.ColorEditOptions = opts;
11605 EndPopup();
11606 }
11607
ColorPickerOptionsPopup(ImGuiColorEditFlags flags,const float * ref_col)11608 static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col)
11609 {
11610 bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
11611 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
11612 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
11613 return;
11614 ImGuiContext& g = *GImGui;
11615 if (allow_opt_picker)
11616 {
11617 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
11618 ImGui::PushItemWidth(picker_size.x);
11619 for (int picker_type = 0; picker_type < 2; picker_type++)
11620 {
11621 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
11622 if (picker_type > 0) ImGui::Separator();
11623 ImGui::PushID(picker_type);
11624 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
11625 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
11626 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
11627 ImVec2 backup_pos = ImGui::GetCursorScreenPos();
11628 if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
11629 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
11630 ImGui::SetCursorScreenPos(backup_pos);
11631 ImVec4 dummy_ref_col;
11632 memcpy(&dummy_ref_col, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
11633 ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
11634 ImGui::PopID();
11635 }
11636 ImGui::PopItemWidth();
11637 }
11638 if (allow_opt_alpha_bar)
11639 {
11640 if (allow_opt_picker) ImGui::Separator();
11641 ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
11642 }
11643 ImGui::EndPopup();
11644 }
11645
11646 // Edit colors components (each component in 0.0f..1.0f range).
11647 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
11648 // 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)11649 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
11650 {
11651 ImGuiWindow* window = GetCurrentWindow();
11652 if (window->SkipItems)
11653 return false;
11654
11655 ImGuiContext& g = *GImGui;
11656 const ImGuiStyle& style = g.Style;
11657 const float square_sz = GetFrameHeight();
11658 const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
11659 const float w_items_all = CalcItemWidth() - w_extra;
11660 const char* label_display_end = FindRenderedTextEnd(label);
11661
11662 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
11663 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
11664 const int components = alpha ? 4 : 3;
11665 const ImGuiColorEditFlags flags_untouched = flags;
11666
11667 BeginGroup();
11668 PushID(label);
11669
11670 // If we're not showing any slider there's no point in doing any HSV conversions
11671 if (flags & ImGuiColorEditFlags_NoInputs)
11672 flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
11673
11674 // Context menu: display and modify options (before defaults are applied)
11675 if (!(flags & ImGuiColorEditFlags_NoOptions))
11676 ColorEditOptionsPopup(col, flags);
11677
11678 // Read stored options
11679 if (!(flags & ImGuiColorEditFlags__InputsMask))
11680 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
11681 if (!(flags & ImGuiColorEditFlags__DataTypeMask))
11682 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
11683 if (!(flags & ImGuiColorEditFlags__PickerMask))
11684 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
11685 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
11686
11687 // Convert to the formats we need
11688 float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
11689 if (flags & ImGuiColorEditFlags_HSV)
11690 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
11691 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]) };
11692
11693 bool value_changed = false;
11694 bool value_changed_as_float = false;
11695
11696 if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
11697 {
11698 // RGB/HSV 0..255 Sliders
11699 const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
11700 const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
11701
11702 const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
11703 const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
11704 const char* fmt_table_int[3][4] =
11705 {
11706 { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, // Short display
11707 { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, // Long display for RGBA
11708 { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } // Long display for HSVA
11709 };
11710 const char* fmt_table_float[3][4] =
11711 {
11712 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
11713 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
11714 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
11715 };
11716 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
11717
11718 PushItemWidth(w_item_one);
11719 for (int n = 0; n < components; n++)
11720 {
11721 if (n > 0)
11722 SameLine(0, style.ItemInnerSpacing.x);
11723 if (n + 1 == components)
11724 PushItemWidth(w_item_last);
11725 if (flags & ImGuiColorEditFlags_Float)
11726 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]);
11727 else
11728 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
11729 if (!(flags & ImGuiColorEditFlags_NoOptions))
11730 OpenPopupOnItemClick("context");
11731 }
11732 PopItemWidth();
11733 PopItemWidth();
11734 }
11735 else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
11736 {
11737 // RGB Hexadecimal Input
11738 char buf[64];
11739 if (alpha)
11740 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));
11741 else
11742 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
11743 PushItemWidth(w_items_all);
11744 if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
11745 {
11746 value_changed = true;
11747 char* p = buf;
11748 while (*p == '#' || ImCharIsSpace((unsigned int)*p))
11749 p++;
11750 i[0] = i[1] = i[2] = i[3] = 0;
11751 if (alpha)
11752 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)
11753 else
11754 sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
11755 }
11756 if (!(flags & ImGuiColorEditFlags_NoOptions))
11757 OpenPopupOnItemClick("context");
11758 PopItemWidth();
11759 }
11760
11761 ImGuiWindow* picker_active_window = NULL;
11762 if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
11763 {
11764 if (!(flags & ImGuiColorEditFlags_NoInputs))
11765 SameLine(0, style.ItemInnerSpacing.x);
11766
11767 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
11768 if (ColorButton("##ColorButton", col_v4, flags))
11769 {
11770 if (!(flags & ImGuiColorEditFlags_NoPicker))
11771 {
11772 // Store current color and open a picker
11773 g.ColorPickerRef = col_v4;
11774 OpenPopup("picker");
11775 SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
11776 }
11777 }
11778 if (!(flags & ImGuiColorEditFlags_NoOptions))
11779 OpenPopupOnItemClick("context");
11780
11781 if (BeginPopup("picker"))
11782 {
11783 picker_active_window = g.CurrentWindow;
11784 if (label != label_display_end)
11785 {
11786 TextUnformatted(label, label_display_end);
11787 Separator();
11788 }
11789 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
11790 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
11791 PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
11792 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
11793 PopItemWidth();
11794 EndPopup();
11795 }
11796 }
11797
11798 if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
11799 {
11800 SameLine(0, style.ItemInnerSpacing.x);
11801 TextUnformatted(label, label_display_end);
11802 }
11803
11804 // Convert back
11805 if (picker_active_window == NULL)
11806 {
11807 if (!value_changed_as_float)
11808 for (int n = 0; n < 4; n++)
11809 f[n] = i[n] / 255.0f;
11810 if (flags & ImGuiColorEditFlags_HSV)
11811 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
11812 if (value_changed)
11813 {
11814 col[0] = f[0];
11815 col[1] = f[1];
11816 col[2] = f[2];
11817 if (alpha)
11818 col[3] = f[3];
11819 }
11820 }
11821
11822 PopID();
11823 EndGroup();
11824
11825 // Drag and Drop Target
11826 if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && BeginDragDropTarget()) // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
11827 {
11828 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
11829 {
11830 memcpy((float*)col, payload->Data, sizeof(float) * 3);
11831 value_changed = true;
11832 }
11833 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
11834 {
11835 memcpy((float*)col, payload->Data, sizeof(float) * components);
11836 value_changed = true;
11837 }
11838 EndDragDropTarget();
11839 }
11840
11841 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
11842 if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
11843 window->DC.LastItemId = g.ActiveId;
11844
11845 return value_changed;
11846 }
11847
ColorPicker3(const char * label,float col[3],ImGuiColorEditFlags flags)11848 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
11849 {
11850 float col4[4] = { col[0], col[1], col[2], 1.0f };
11851 if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
11852 return false;
11853 col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
11854 return true;
11855 }
11856
11857 // '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)11858 static void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col)
11859 {
11860 switch (direction)
11861 {
11862 case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return;
11863 case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return;
11864 case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return;
11865 case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return;
11866 case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings
11867 }
11868 }
11869
RenderArrowsForVerticalBar(ImDrawList * draw_list,ImVec2 pos,ImVec2 half_sz,float bar_w)11870 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
11871 {
11872 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);
11873 RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
11874 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);
11875 RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
11876 }
11877
11878 // ColorPicker
11879 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
11880 // 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)11881 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
11882 {
11883 ImGuiContext& g = *GImGui;
11884 ImGuiWindow* window = GetCurrentWindow();
11885 ImDrawList* draw_list = window->DrawList;
11886
11887 ImGuiStyle& style = g.Style;
11888 ImGuiIO& io = g.IO;
11889
11890 PushID(label);
11891 BeginGroup();
11892
11893 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
11894 flags |= ImGuiColorEditFlags_NoSmallPreview;
11895
11896 // Context menu: display and store options.
11897 if (!(flags & ImGuiColorEditFlags_NoOptions))
11898 ColorPickerOptionsPopup(flags, col);
11899
11900 // Read stored options
11901 if (!(flags & ImGuiColorEditFlags__PickerMask))
11902 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
11903 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
11904 if (!(flags & ImGuiColorEditFlags_NoOptions))
11905 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
11906
11907 // Setup
11908 int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
11909 bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
11910 ImVec2 picker_pos = window->DC.CursorPos;
11911 float square_sz = GetFrameHeight();
11912 float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
11913 float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
11914 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
11915 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
11916 float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
11917
11918 float backup_initial_col[4];
11919 memcpy(backup_initial_col, col, components * sizeof(float));
11920
11921 float wheel_thickness = sv_picker_size * 0.08f;
11922 float wheel_r_outer = sv_picker_size * 0.50f;
11923 float wheel_r_inner = wheel_r_outer - wheel_thickness;
11924 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
11925
11926 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
11927 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
11928 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
11929 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
11930 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
11931
11932 float H,S,V;
11933 ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
11934
11935 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
11936
11937 PushItemFlag(ImGuiItemFlags_NoNav, true);
11938 if (flags & ImGuiColorEditFlags_PickerHueWheel)
11939 {
11940 // Hue wheel + SV triangle logic
11941 InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
11942 if (IsItemActive())
11943 {
11944 ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
11945 ImVec2 current_off = g.IO.MousePos - wheel_center;
11946 float initial_dist2 = ImLengthSqr(initial_off);
11947 if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
11948 {
11949 // Interactive with Hue wheel
11950 H = atan2f(current_off.y, current_off.x) / IM_PI*0.5f;
11951 if (H < 0.0f)
11952 H += 1.0f;
11953 value_changed = value_changed_h = true;
11954 }
11955 float cos_hue_angle = cosf(-H * 2.0f * IM_PI);
11956 float sin_hue_angle = sinf(-H * 2.0f * IM_PI);
11957 if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
11958 {
11959 // Interacting with SV triangle
11960 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
11961 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
11962 current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
11963 float uu, vv, ww;
11964 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
11965 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
11966 S = ImClamp(uu / V, 0.0001f, 1.0f);
11967 value_changed = value_changed_sv = true;
11968 }
11969 }
11970 if (!(flags & ImGuiColorEditFlags_NoOptions))
11971 OpenPopupOnItemClick("context");
11972 }
11973 else if (flags & ImGuiColorEditFlags_PickerHueBar)
11974 {
11975 // SV rectangle logic
11976 InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
11977 if (IsItemActive())
11978 {
11979 S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
11980 V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
11981 value_changed = value_changed_sv = true;
11982 }
11983 if (!(flags & ImGuiColorEditFlags_NoOptions))
11984 OpenPopupOnItemClick("context");
11985
11986 // Hue bar logic
11987 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
11988 InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
11989 if (IsItemActive())
11990 {
11991 H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
11992 value_changed = value_changed_h = true;
11993 }
11994 }
11995
11996 // Alpha bar logic
11997 if (alpha_bar)
11998 {
11999 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
12000 InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
12001 if (IsItemActive())
12002 {
12003 col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
12004 value_changed = true;
12005 }
12006 }
12007 PopItemFlag(); // ImGuiItemFlags_NoNav
12008
12009 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12010 {
12011 SameLine(0, style.ItemInnerSpacing.x);
12012 BeginGroup();
12013 }
12014
12015 if (!(flags & ImGuiColorEditFlags_NoLabel))
12016 {
12017 const char* label_display_end = FindRenderedTextEnd(label);
12018 if (label != label_display_end)
12019 {
12020 if ((flags & ImGuiColorEditFlags_NoSidePreview))
12021 SameLine(0, style.ItemInnerSpacing.x);
12022 TextUnformatted(label, label_display_end);
12023 }
12024 }
12025
12026 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12027 {
12028 PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
12029 ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
12030 if ((flags & ImGuiColorEditFlags_NoLabel))
12031 Text("Current");
12032 ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
12033 if (ref_col != NULL)
12034 {
12035 Text("Original");
12036 ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
12037 if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
12038 {
12039 memcpy(col, ref_col, components * sizeof(float));
12040 value_changed = true;
12041 }
12042 }
12043 PopItemFlag();
12044 EndGroup();
12045 }
12046
12047 // Convert back color to RGB
12048 if (value_changed_h || value_changed_sv)
12049 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]);
12050
12051 // R,G,B and H,S,V slider color editor
12052 if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
12053 {
12054 PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
12055 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
12056 ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
12057 if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12058 value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB);
12059 if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12060 value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
12061 if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12062 value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
12063 PopItemWidth();
12064 }
12065
12066 // Try to cancel hue wrap (after ColorEdit), if any
12067 if (value_changed)
12068 {
12069 float new_H, new_S, new_V;
12070 ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
12071 if (new_H <= 0 && H > 0)
12072 {
12073 if (new_V <= 0 && V != new_V)
12074 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
12075 else if (new_S <= 0)
12076 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
12077 }
12078 }
12079
12080 ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
12081 ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
12082 ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
12083
12084 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) };
12085 ImVec2 sv_cursor_pos;
12086
12087 if (flags & ImGuiColorEditFlags_PickerHueWheel)
12088 {
12089 // Render Hue Wheel
12090 const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
12091 const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
12092 for (int n = 0; n < 6; n++)
12093 {
12094 const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
12095 const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
12096 const int vert_start_idx = draw_list->VtxBuffer.Size;
12097 draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
12098 draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
12099 const int vert_end_idx = draw_list->VtxBuffer.Size;
12100
12101 // Paint colors over existing vertices
12102 ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner);
12103 ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner);
12104 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]);
12105 }
12106
12107 // Render Cursor + preview on Hue Wheel
12108 float cos_hue_angle = cosf(H * 2.0f * IM_PI);
12109 float sin_hue_angle = sinf(H * 2.0f * IM_PI);
12110 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);
12111 float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
12112 int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
12113 draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
12114 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
12115 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
12116
12117 // Render SV triangle (rotated according to hue)
12118 ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
12119 ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
12120 ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
12121 ImVec2 uv_white = GetFontTexUvWhitePixel();
12122 draw_list->PrimReserve(6, 6);
12123 draw_list->PrimVtx(tra, uv_white, hue_color32);
12124 draw_list->PrimVtx(trb, uv_white, hue_color32);
12125 draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
12126 draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
12127 draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
12128 draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
12129 draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
12130 sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
12131 }
12132 else if (flags & ImGuiColorEditFlags_PickerHueBar)
12133 {
12134 // Render SV Square
12135 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
12136 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);
12137 RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
12138 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
12139 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);
12140
12141 // Render Hue Bar
12142 for (int i = 0; i < 6; ++i)
12143 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]);
12144 float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
12145 RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
12146 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);
12147 }
12148
12149 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
12150 float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
12151 draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
12152 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
12153 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
12154
12155 // Render alpha bar
12156 if (alpha_bar)
12157 {
12158 float alpha = ImSaturate(col[3]);
12159 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
12160 RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
12161 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);
12162 float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
12163 RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
12164 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);
12165 }
12166
12167 EndGroup();
12168 PopID();
12169
12170 return value_changed && memcmp(backup_initial_col, col, components * sizeof(float));
12171 }
12172
12173 // Horizontal separating line.
Separator()12174 void ImGui::Separator()
12175 {
12176 ImGuiWindow* window = GetCurrentWindow();
12177 if (window->SkipItems)
12178 return;
12179 ImGuiContext& g = *GImGui;
12180
12181 ImGuiSeparatorFlags flags = 0;
12182 if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0)
12183 flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
12184 IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected
12185 if (flags & ImGuiSeparatorFlags_Vertical)
12186 {
12187 VerticalSeparator();
12188 return;
12189 }
12190
12191 // Horizontal Separator
12192 if (window->DC.ColumnsSet)
12193 PopClipRect();
12194
12195 float x1 = window->Pos.x;
12196 float x2 = window->Pos.x + window->Size.x;
12197 if (!window->DC.GroupStack.empty())
12198 x1 += window->DC.IndentX;
12199
12200 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
12201 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.
12202 if (!ItemAdd(bb, 0))
12203 {
12204 if (window->DC.ColumnsSet)
12205 PushColumnClipRect();
12206 return;
12207 }
12208
12209 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
12210
12211 if (g.LogEnabled)
12212 LogRenderedText(NULL, IM_NEWLINE "--------------------------------");
12213
12214 if (window->DC.ColumnsSet)
12215 {
12216 PushColumnClipRect();
12217 window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
12218 }
12219 }
12220
VerticalSeparator()12221 void ImGui::VerticalSeparator()
12222 {
12223 ImGuiWindow* window = GetCurrentWindow();
12224 if (window->SkipItems)
12225 return;
12226 ImGuiContext& g = *GImGui;
12227
12228 float y1 = window->DC.CursorPos.y;
12229 float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight;
12230 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
12231 ItemSize(ImVec2(bb.GetWidth(), 0.0f));
12232 if (!ItemAdd(bb, 0))
12233 return;
12234
12235 window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
12236 if (g.LogEnabled)
12237 LogText(" |");
12238 }
12239
SplitterBehavior(ImGuiID id,const ImRect & bb,ImGuiAxis axis,float * size1,float * size2,float min_size1,float min_size2,float hover_extend)12240 bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend)
12241 {
12242 ImGuiContext& g = *GImGui;
12243 ImGuiWindow* window = g.CurrentWindow;
12244
12245 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
12246 window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
12247 bool item_add = ItemAdd(bb, id);
12248 window->DC.ItemFlags = item_flags_backup;
12249 if (!item_add)
12250 return false;
12251
12252 bool hovered, held;
12253 ImRect bb_interact = bb;
12254 bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
12255 ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
12256 if (g.ActiveId != id)
12257 SetItemAllowOverlap();
12258
12259 if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id))
12260 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
12261
12262 ImRect bb_render = bb;
12263 if (held)
12264 {
12265 ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
12266 float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
12267
12268 // Minimum pane size
12269 if (mouse_delta < min_size1 - *size1)
12270 mouse_delta = min_size1 - *size1;
12271 if (mouse_delta > *size2 - min_size2)
12272 mouse_delta = *size2 - min_size2;
12273
12274 // Apply resize
12275 *size1 += mouse_delta;
12276 *size2 -= mouse_delta;
12277 bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
12278 }
12279
12280 // Render
12281 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
12282 window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
12283
12284 return held;
12285 }
12286
Spacing()12287 void ImGui::Spacing()
12288 {
12289 ImGuiWindow* window = GetCurrentWindow();
12290 if (window->SkipItems)
12291 return;
12292 ItemSize(ImVec2(0,0));
12293 }
12294
Dummy(const ImVec2 & size)12295 void ImGui::Dummy(const ImVec2& size)
12296 {
12297 ImGuiWindow* window = GetCurrentWindow();
12298 if (window->SkipItems)
12299 return;
12300
12301 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
12302 ItemSize(bb);
12303 ItemAdd(bb, 0);
12304 }
12305
IsRectVisible(const ImVec2 & size)12306 bool ImGui::IsRectVisible(const ImVec2& size)
12307 {
12308 ImGuiWindow* window = GetCurrentWindowRead();
12309 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
12310 }
12311
IsRectVisible(const ImVec2 & rect_min,const ImVec2 & rect_max)12312 bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
12313 {
12314 ImGuiWindow* window = GetCurrentWindowRead();
12315 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
12316 }
12317
12318 // 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()12319 void ImGui::BeginGroup()
12320 {
12321 ImGuiWindow* window = GetCurrentWindow();
12322
12323 window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
12324 ImGuiGroupData& group_data = window->DC.GroupStack.back();
12325 group_data.BackupCursorPos = window->DC.CursorPos;
12326 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
12327 group_data.BackupIndentX = window->DC.IndentX;
12328 group_data.BackupGroupOffsetX = window->DC.GroupOffsetX;
12329 group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight;
12330 group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
12331 group_data.BackupLogLinePosY = window->DC.LogLinePosY;
12332 group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive;
12333 group_data.AdvanceCursor = true;
12334
12335 window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX;
12336 window->DC.IndentX = window->DC.GroupOffsetX;
12337 window->DC.CursorMaxPos = window->DC.CursorPos;
12338 window->DC.CurrentLineHeight = 0.0f;
12339 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
12340 }
12341
EndGroup()12342 void ImGui::EndGroup()
12343 {
12344 ImGuiContext& g = *GImGui;
12345 ImGuiWindow* window = GetCurrentWindow();
12346
12347 IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
12348
12349 ImGuiGroupData& group_data = window->DC.GroupStack.back();
12350
12351 ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
12352 group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
12353
12354 window->DC.CursorPos = group_data.BackupCursorPos;
12355 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
12356 window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight;
12357 window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
12358 window->DC.IndentX = group_data.BackupIndentX;
12359 window->DC.GroupOffsetX = group_data.BackupGroupOffsetX;
12360 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
12361
12362 if (group_data.AdvanceCursor)
12363 {
12364 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.
12365 ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
12366 ItemAdd(group_bb, 0);
12367 }
12368
12369 // 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.
12370 // 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.
12371 const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow);
12372 if (active_id_within_group)
12373 window->DC.LastItemId = g.ActiveId;
12374 window->DC.LastItemRect = group_bb;
12375
12376 window->DC.GroupStack.pop_back();
12377
12378 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
12379 }
12380
12381 // Gets back to previous line and continue with horizontal layout
12382 // pos_x == 0 : follow right after previous item
12383 // pos_x != 0 : align to specified x position (relative to window/group left)
12384 // spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
12385 // spacing_w >= 0 : enforce spacing amount
SameLine(float pos_x,float spacing_w)12386 void ImGui::SameLine(float pos_x, float spacing_w)
12387 {
12388 ImGuiWindow* window = GetCurrentWindow();
12389 if (window->SkipItems)
12390 return;
12391
12392 ImGuiContext& g = *GImGui;
12393 if (pos_x != 0.0f)
12394 {
12395 if (spacing_w < 0.0f) spacing_w = 0.0f;
12396 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX;
12397 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
12398 }
12399 else
12400 {
12401 if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
12402 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
12403 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
12404 }
12405 window->DC.CurrentLineHeight = window->DC.PrevLineHeight;
12406 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
12407 }
12408
NewLine()12409 void ImGui::NewLine()
12410 {
12411 ImGuiWindow* window = GetCurrentWindow();
12412 if (window->SkipItems)
12413 return;
12414
12415 ImGuiContext& g = *GImGui;
12416 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
12417 window->DC.LayoutType = ImGuiLayoutType_Vertical;
12418 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.
12419 ItemSize(ImVec2(0,0));
12420 else
12421 ItemSize(ImVec2(0.0f, g.FontSize));
12422 window->DC.LayoutType = backup_layout_type;
12423 }
12424
NextColumn()12425 void ImGui::NextColumn()
12426 {
12427 ImGuiWindow* window = GetCurrentWindow();
12428 if (window->SkipItems || window->DC.ColumnsSet == NULL)
12429 return;
12430
12431 ImGuiContext& g = *GImGui;
12432 PopItemWidth();
12433 PopClipRect();
12434
12435 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12436 columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
12437 if (++columns->Current < columns->Count)
12438 {
12439 // Columns 1+ cancel out IndentX
12440 window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x;
12441 window->DrawList->ChannelsSetCurrent(columns->Current);
12442 }
12443 else
12444 {
12445 window->DC.ColumnsOffsetX = 0.0f;
12446 window->DrawList->ChannelsSetCurrent(0);
12447 columns->Current = 0;
12448 columns->LineMinY = columns->LineMaxY;
12449 }
12450 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
12451 window->DC.CursorPos.y = columns->LineMinY;
12452 window->DC.CurrentLineHeight = 0.0f;
12453 window->DC.CurrentLineTextBaseOffset = 0.0f;
12454
12455 PushColumnClipRect();
12456 PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup
12457 }
12458
GetColumnIndex()12459 int ImGui::GetColumnIndex()
12460 {
12461 ImGuiWindow* window = GetCurrentWindowRead();
12462 return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0;
12463 }
12464
GetColumnsCount()12465 int ImGui::GetColumnsCount()
12466 {
12467 ImGuiWindow* window = GetCurrentWindowRead();
12468 return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1;
12469 }
12470
OffsetNormToPixels(const ImGuiColumnsSet * columns,float offset_norm)12471 static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm)
12472 {
12473 return offset_norm * (columns->MaxX - columns->MinX);
12474 }
12475
PixelsToOffsetNorm(const ImGuiColumnsSet * columns,float offset)12476 static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset)
12477 {
12478 return offset / (columns->MaxX - columns->MinX);
12479 }
12480
GetColumnsRectHalfWidth()12481 static inline float GetColumnsRectHalfWidth() { return 4.0f; }
12482
GetDraggedColumnOffset(ImGuiColumnsSet * columns,int column_index)12483 static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index)
12484 {
12485 // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
12486 // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
12487 ImGuiContext& g = *GImGui;
12488 ImGuiWindow* window = g.CurrentWindow;
12489 IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.
12490 IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
12491
12492 float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x;
12493 x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
12494 if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths))
12495 x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
12496
12497 return x;
12498 }
12499
GetColumnOffset(int column_index)12500 float ImGui::GetColumnOffset(int column_index)
12501 {
12502 ImGuiWindow* window = GetCurrentWindowRead();
12503 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12504 IM_ASSERT(columns != NULL);
12505
12506 if (column_index < 0)
12507 column_index = columns->Current;
12508 IM_ASSERT(column_index < columns->Columns.Size);
12509
12510 const float t = columns->Columns[column_index].OffsetNorm;
12511 const float x_offset = ImLerp(columns->MinX, columns->MaxX, t);
12512 return x_offset;
12513 }
12514
GetColumnWidthEx(ImGuiColumnsSet * columns,int column_index,bool before_resize=false)12515 static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false)
12516 {
12517 if (column_index < 0)
12518 column_index = columns->Current;
12519
12520 float offset_norm;
12521 if (before_resize)
12522 offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
12523 else
12524 offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
12525 return OffsetNormToPixels(columns, offset_norm);
12526 }
12527
GetColumnWidth(int column_index)12528 float ImGui::GetColumnWidth(int column_index)
12529 {
12530 ImGuiWindow* window = GetCurrentWindowRead();
12531 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12532 IM_ASSERT(columns != NULL);
12533
12534 if (column_index < 0)
12535 column_index = columns->Current;
12536 return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
12537 }
12538
SetColumnOffset(int column_index,float offset)12539 void ImGui::SetColumnOffset(int column_index, float offset)
12540 {
12541 ImGuiContext& g = *GImGui;
12542 ImGuiWindow* window = g.CurrentWindow;
12543 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12544 IM_ASSERT(columns != NULL);
12545
12546 if (column_index < 0)
12547 column_index = columns->Current;
12548 IM_ASSERT(column_index < columns->Columns.Size);
12549
12550 const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1);
12551 const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
12552
12553 if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
12554 offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
12555 columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX);
12556
12557 if (preserve_width)
12558 SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
12559 }
12560
SetColumnWidth(int column_index,float width)12561 void ImGui::SetColumnWidth(int column_index, float width)
12562 {
12563 ImGuiWindow* window = GetCurrentWindowRead();
12564 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12565 IM_ASSERT(columns != NULL);
12566
12567 if (column_index < 0)
12568 column_index = columns->Current;
12569 SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
12570 }
12571
PushColumnClipRect(int column_index)12572 void ImGui::PushColumnClipRect(int column_index)
12573 {
12574 ImGuiWindow* window = GetCurrentWindowRead();
12575 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12576 if (column_index < 0)
12577 column_index = columns->Current;
12578
12579 PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false);
12580 }
12581
FindOrAddColumnsSet(ImGuiWindow * window,ImGuiID id)12582 static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id)
12583 {
12584 for (int n = 0; n < window->ColumnsStorage.Size; n++)
12585 if (window->ColumnsStorage[n].ID == id)
12586 return &window->ColumnsStorage[n];
12587
12588 window->ColumnsStorage.push_back(ImGuiColumnsSet());
12589 ImGuiColumnsSet* columns = &window->ColumnsStorage.back();
12590 columns->ID = id;
12591 return columns;
12592 }
12593
BeginColumns(const char * str_id,int columns_count,ImGuiColumnsFlags flags)12594 void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags)
12595 {
12596 ImGuiContext& g = *GImGui;
12597 ImGuiWindow* window = GetCurrentWindow();
12598
12599 IM_ASSERT(columns_count > 1);
12600 IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported
12601
12602 // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
12603 // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
12604 PushID(0x11223347 + (str_id ? 0 : columns_count));
12605 ImGuiID id = window->GetID(str_id ? str_id : "columns");
12606 PopID();
12607
12608 // Acquire storage for the columns set
12609 ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id);
12610 IM_ASSERT(columns->ID == id);
12611 columns->Current = 0;
12612 columns->Count = columns_count;
12613 columns->Flags = flags;
12614 window->DC.ColumnsSet = columns;
12615
12616 // Set state for first column
12617 const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x);
12618 columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range
12619 columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f);
12620 columns->StartPosY = window->DC.CursorPos.y;
12621 columns->StartMaxPosX = window->DC.CursorMaxPos.x;
12622 columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;
12623 window->DC.ColumnsOffsetX = 0.0f;
12624 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
12625
12626 // Clear data if columns count changed
12627 if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
12628 columns->Columns.resize(0);
12629
12630 // Initialize defaults
12631 columns->IsFirstFrame = (columns->Columns.Size == 0);
12632 if (columns->Columns.Size == 0)
12633 {
12634 columns->Columns.reserve(columns_count + 1);
12635 for (int n = 0; n < columns_count + 1; n++)
12636 {
12637 ImGuiColumnData column;
12638 column.OffsetNorm = n / (float)columns_count;
12639 columns->Columns.push_back(column);
12640 }
12641 }
12642
12643 for (int n = 0; n < columns_count; n++)
12644 {
12645 // Compute clipping rectangle
12646 ImGuiColumnData* column = &columns->Columns[n];
12647 float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f);
12648 float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
12649 column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
12650 column->ClipRect.ClipWith(window->ClipRect);
12651 }
12652
12653 window->DrawList->ChannelsSplit(columns->Count);
12654 PushColumnClipRect();
12655 PushItemWidth(GetColumnWidth() * 0.65f);
12656 }
12657
EndColumns()12658 void ImGui::EndColumns()
12659 {
12660 ImGuiContext& g = *GImGui;
12661 ImGuiWindow* window = GetCurrentWindow();
12662 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12663 IM_ASSERT(columns != NULL);
12664
12665 PopItemWidth();
12666 PopClipRect();
12667 window->DrawList->ChannelsMerge();
12668
12669 columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
12670 window->DC.CursorPos.y = columns->LineMaxY;
12671 if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize))
12672 window->DC.CursorMaxPos.x = ImMax(columns->StartMaxPosX, columns->MaxX); // Restore cursor max pos, as columns don't grow parent
12673
12674 // Draw columns borders and handle resize
12675 bool is_being_resized = false;
12676 if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems)
12677 {
12678 const float y1 = columns->StartPosY;
12679 const float y2 = window->DC.CursorPos.y;
12680 int dragging_column = -1;
12681 for (int n = 1; n < columns->Count; n++)
12682 {
12683 float x = window->Pos.x + GetColumnOffset(n);
12684 const ImGuiID column_id = columns->ID + ImGuiID(n);
12685 const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction
12686 const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2));
12687 KeepAliveID(column_id);
12688 if (IsClippedEx(column_rect, column_id, false))
12689 continue;
12690
12691 bool hovered = false, held = false;
12692 if (!(columns->Flags & ImGuiColumnsFlags_NoResize))
12693 {
12694 ButtonBehavior(column_rect, column_id, &hovered, &held);
12695 if (hovered || held)
12696 g.MouseCursor = ImGuiMouseCursor_ResizeEW;
12697 if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize))
12698 dragging_column = n;
12699 }
12700
12701 // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.)
12702 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
12703 const float xi = (float)(int)x;
12704 window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col);
12705 }
12706
12707 // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
12708 if (dragging_column != -1)
12709 {
12710 if (!columns->IsBeingResized)
12711 for (int n = 0; n < columns->Count + 1; n++)
12712 columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
12713 columns->IsBeingResized = is_being_resized = true;
12714 float x = GetDraggedColumnOffset(columns, dragging_column);
12715 SetColumnOffset(dragging_column, x);
12716 }
12717 }
12718 columns->IsBeingResized = is_being_resized;
12719
12720 window->DC.ColumnsSet = NULL;
12721 window->DC.ColumnsOffsetX = 0.0f;
12722 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
12723 }
12724
12725 // [2018-03: 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)12726 void ImGui::Columns(int columns_count, const char* id, bool border)
12727 {
12728 ImGuiWindow* window = GetCurrentWindow();
12729 IM_ASSERT(columns_count >= 1);
12730
12731 ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
12732 //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
12733 if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags)
12734 return;
12735
12736 if (window->DC.ColumnsSet != NULL)
12737 EndColumns();
12738
12739 if (columns_count != 1)
12740 BeginColumns(id, columns_count, flags);
12741 }
12742
Indent(float indent_w)12743 void ImGui::Indent(float indent_w)
12744 {
12745 ImGuiContext& g = *GImGui;
12746 ImGuiWindow* window = GetCurrentWindow();
12747 window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
12748 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
12749 }
12750
Unindent(float indent_w)12751 void ImGui::Unindent(float indent_w)
12752 {
12753 ImGuiContext& g = *GImGui;
12754 ImGuiWindow* window = GetCurrentWindow();
12755 window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
12756 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
12757 }
12758
TreePush(const char * str_id)12759 void ImGui::TreePush(const char* str_id)
12760 {
12761 ImGuiWindow* window = GetCurrentWindow();
12762 Indent();
12763 window->DC.TreeDepth++;
12764 PushID(str_id ? str_id : "#TreePush");
12765 }
12766
TreePush(const void * ptr_id)12767 void ImGui::TreePush(const void* ptr_id)
12768 {
12769 ImGuiWindow* window = GetCurrentWindow();
12770 Indent();
12771 window->DC.TreeDepth++;
12772 PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
12773 }
12774
TreePushRawID(ImGuiID id)12775 void ImGui::TreePushRawID(ImGuiID id)
12776 {
12777 ImGuiWindow* window = GetCurrentWindow();
12778 Indent();
12779 window->DC.TreeDepth++;
12780 window->IDStack.push_back(id);
12781 }
12782
TreePop()12783 void ImGui::TreePop()
12784 {
12785 ImGuiContext& g = *GImGui;
12786 ImGuiWindow* window = g.CurrentWindow;
12787 Unindent();
12788
12789 window->DC.TreeDepth--;
12790 if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
12791 if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
12792 {
12793 SetNavID(window->IDStack.back(), g.NavLayer);
12794 NavMoveRequestCancel();
12795 }
12796 window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
12797
12798 PopID();
12799 }
12800
Value(const char * prefix,bool b)12801 void ImGui::Value(const char* prefix, bool b)
12802 {
12803 Text("%s: %s", prefix, (b ? "true" : "false"));
12804 }
12805
Value(const char * prefix,int v)12806 void ImGui::Value(const char* prefix, int v)
12807 {
12808 Text("%s: %d", prefix, v);
12809 }
12810
Value(const char * prefix,unsigned int v)12811 void ImGui::Value(const char* prefix, unsigned int v)
12812 {
12813 Text("%s: %d", prefix, v);
12814 }
12815
Value(const char * prefix,float v,const char * float_format)12816 void ImGui::Value(const char* prefix, float v, const char* float_format)
12817 {
12818 if (float_format)
12819 {
12820 char fmt[64];
12821 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
12822 Text(fmt, prefix, v);
12823 }
12824 else
12825 {
12826 Text("%s: %.3f", prefix, v);
12827 }
12828 }
12829
12830 //-----------------------------------------------------------------------------
12831 // DRAG AND DROP
12832 //-----------------------------------------------------------------------------
12833
ClearDragDrop()12834 void ImGui::ClearDragDrop()
12835 {
12836 ImGuiContext& g = *GImGui;
12837 g.DragDropActive = false;
12838 g.DragDropPayload.Clear();
12839 g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
12840 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
12841 g.DragDropAcceptFrameCount = -1;
12842 }
12843
12844 // Call when current ID is active.
12845 // 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)12846 bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
12847 {
12848 ImGuiContext& g = *GImGui;
12849 ImGuiWindow* window = g.CurrentWindow;
12850
12851 bool source_drag_active = false;
12852 ImGuiID source_id = 0;
12853 ImGuiID source_parent_id = 0;
12854 int mouse_button = 0;
12855 if (!(flags & ImGuiDragDropFlags_SourceExtern))
12856 {
12857 source_id = window->DC.LastItemId;
12858 if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case
12859 return false;
12860 if (g.IO.MouseDown[mouse_button] == false)
12861 return false;
12862
12863 if (source_id == 0)
12864 {
12865 // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:
12866 // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride.
12867 if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
12868 {
12869 IM_ASSERT(0);
12870 return false;
12871 }
12872
12873 // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image()
12874 // We build a throwaway ID based on current ID stack + relative AABB of items in window.
12875 // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled.
12876 // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.
12877 bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0;
12878 if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window))
12879 return false;
12880 source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect);
12881 if (is_hovered)
12882 SetHoveredID(source_id);
12883 if (is_hovered && g.IO.MouseClicked[mouse_button])
12884 {
12885 SetActiveID(source_id, window);
12886 FocusWindow(window);
12887 }
12888 if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.
12889 g.ActiveIdAllowOverlap = is_hovered;
12890 }
12891 if (g.ActiveId != source_id)
12892 return false;
12893 source_parent_id = window->IDStack.back();
12894 source_drag_active = IsMouseDragging(mouse_button);
12895 }
12896 else
12897 {
12898 window = NULL;
12899 source_id = ImHash("#SourceExtern", 0);
12900 source_drag_active = true;
12901 }
12902
12903 if (source_drag_active)
12904 {
12905 if (!g.DragDropActive)
12906 {
12907 IM_ASSERT(source_id != 0);
12908 ClearDragDrop();
12909 ImGuiPayload& payload = g.DragDropPayload;
12910 payload.SourceId = source_id;
12911 payload.SourceParentId = source_parent_id;
12912 g.DragDropActive = true;
12913 g.DragDropSourceFlags = flags;
12914 g.DragDropMouseButton = mouse_button;
12915 }
12916
12917 if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
12918 {
12919 // FIXME-DRAG
12920 //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding);
12921 //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :(
12922 SetNextWindowPos(g.IO.MousePos);
12923 PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f));
12924 BeginTooltip();
12925 }
12926
12927 if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
12928 window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
12929
12930 return true;
12931 }
12932 return false;
12933 }
12934
EndDragDropSource()12935 void ImGui::EndDragDropSource()
12936 {
12937 ImGuiContext& g = *GImGui;
12938 IM_ASSERT(g.DragDropActive);
12939
12940 if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
12941 {
12942 EndTooltip();
12943 PopStyleColor();
12944 //PopStyleVar();
12945 }
12946
12947 // Discard the drag if have not called SetDragDropPayload()
12948 if (g.DragDropPayload.DataFrameCount == -1)
12949 ClearDragDrop();
12950 }
12951
12952 // 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)12953 bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
12954 {
12955 ImGuiContext& g = *GImGui;
12956 ImGuiPayload& payload = g.DragDropPayload;
12957 if (cond == 0)
12958 cond = ImGuiCond_Always;
12959
12960 IM_ASSERT(type != NULL);
12961 IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 12 characters long");
12962 IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
12963 IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
12964 IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
12965
12966 if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
12967 {
12968 // Copy payload
12969 ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));
12970 g.DragDropPayloadBufHeap.resize(0);
12971 if (data_size > sizeof(g.DragDropPayloadBufLocal))
12972 {
12973 // Store in heap
12974 g.DragDropPayloadBufHeap.resize((int)data_size);
12975 payload.Data = g.DragDropPayloadBufHeap.Data;
12976 memcpy((void*)(intptr_t)payload.Data, data, data_size);
12977 }
12978 else if (data_size > 0)
12979 {
12980 // Store locally
12981 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
12982 payload.Data = g.DragDropPayloadBufLocal;
12983 memcpy((void*)(intptr_t)payload.Data, data, data_size);
12984 }
12985 else
12986 {
12987 payload.Data = NULL;
12988 }
12989 payload.DataSize = (int)data_size;
12990 }
12991 payload.DataFrameCount = g.FrameCount;
12992
12993 return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
12994 }
12995
BeginDragDropTargetCustom(const ImRect & bb,ImGuiID id)12996 bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
12997 {
12998 ImGuiContext& g = *GImGui;
12999 if (!g.DragDropActive)
13000 return false;
13001
13002 ImGuiWindow* window = g.CurrentWindow;
13003 if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
13004 return false;
13005 IM_ASSERT(id != 0);
13006 if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
13007 return false;
13008
13009 g.DragDropTargetRect = bb;
13010 g.DragDropTargetId = id;
13011 return true;
13012 }
13013
13014 // We don't use BeginDragDropTargetCustom() and duplicate its code because:
13015 // 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.
13016 // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.
13017 // Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)
BeginDragDropTarget()13018 bool ImGui::BeginDragDropTarget()
13019 {
13020 ImGuiContext& g = *GImGui;
13021 if (!g.DragDropActive)
13022 return false;
13023
13024 ImGuiWindow* window = g.CurrentWindow;
13025 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
13026 return false;
13027 if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
13028 return false;
13029
13030 const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect;
13031 ImGuiID id = window->DC.LastItemId;
13032 if (id == 0)
13033 id = window->GetIDFromRectangle(display_rect);
13034 if (g.DragDropPayload.SourceId == id)
13035 return false;
13036
13037 g.DragDropTargetRect = display_rect;
13038 g.DragDropTargetId = id;
13039 return true;
13040 }
13041
IsDragDropPayloadBeingAccepted()13042 bool ImGui::IsDragDropPayloadBeingAccepted()
13043 {
13044 ImGuiContext& g = *GImGui;
13045 return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
13046 }
13047
AcceptDragDropPayload(const char * type,ImGuiDragDropFlags flags)13048 const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)
13049 {
13050 ImGuiContext& g = *GImGui;
13051 ImGuiWindow* window = g.CurrentWindow;
13052 ImGuiPayload& payload = g.DragDropPayload;
13053 IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
13054 IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
13055 if (type != NULL && !payload.IsDataType(type))
13056 return NULL;
13057
13058 // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
13059 // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
13060 const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
13061 ImRect r = g.DragDropTargetRect;
13062 float r_surface = r.GetWidth() * r.GetHeight();
13063 if (r_surface < g.DragDropAcceptIdCurrRectSurface)
13064 {
13065 g.DragDropAcceptIdCurr = g.DragDropTargetId;
13066 g.DragDropAcceptIdCurrRectSurface = r_surface;
13067 }
13068
13069 // Render default drop visuals
13070 payload.Preview = was_accepted_previously;
13071 flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame)
13072 if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
13073 {
13074 // FIXME-DRAG: Settle on a proper default visuals for drop target.
13075 r.Expand(3.5f);
13076 bool push_clip_rect = !window->ClipRect.Contains(r);
13077 if (push_clip_rect) window->DrawList->PushClipRectFullScreen();
13078 window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f);
13079 if (push_clip_rect) window->DrawList->PopClipRect();
13080 }
13081
13082 g.DragDropAcceptFrameCount = g.FrameCount;
13083 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()
13084 if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
13085 return NULL;
13086
13087 return &payload;
13088 }
13089
13090 // We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
EndDragDropTarget()13091 void ImGui::EndDragDropTarget()
13092 {
13093 ImGuiContext& g = *GImGui; (void)g;
13094 IM_ASSERT(g.DragDropActive);
13095 }
13096
13097 //-----------------------------------------------------------------------------
13098 // PLATFORM DEPENDENT HELPERS
13099 //-----------------------------------------------------------------------------
13100
13101 #if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS))
13102 #undef WIN32_LEAN_AND_MEAN
13103 #define WIN32_LEAN_AND_MEAN
13104 #ifndef __MINGW32__
13105 #include <Windows.h>
13106 #else
13107 #include <windows.h>
13108 #endif
13109 #endif
13110
13111 // Win32 API clipboard implementation
13112 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
13113
13114 #ifdef _MSC_VER
13115 #pragma comment(lib, "user32")
13116 #endif
13117
GetClipboardTextFn_DefaultImpl(void *)13118 static const char* GetClipboardTextFn_DefaultImpl(void*)
13119 {
13120 static ImVector<char> buf_local;
13121 buf_local.clear();
13122 if (!OpenClipboard(NULL))
13123 return NULL;
13124 HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT);
13125 if (wbuf_handle == NULL)
13126 {
13127 CloseClipboard();
13128 return NULL;
13129 }
13130 if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle))
13131 {
13132 int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1;
13133 buf_local.resize(buf_len);
13134 ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL);
13135 }
13136 GlobalUnlock(wbuf_handle);
13137 CloseClipboard();
13138 return buf_local.Data;
13139 }
13140
SetClipboardTextFn_DefaultImpl(void *,const char * text)13141 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
13142 {
13143 if (!OpenClipboard(NULL))
13144 return;
13145 const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1;
13146 HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar));
13147 if (wbuf_handle == NULL)
13148 {
13149 CloseClipboard();
13150 return;
13151 }
13152 ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle);
13153 ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL);
13154 GlobalUnlock(wbuf_handle);
13155 EmptyClipboard();
13156 SetClipboardData(CF_UNICODETEXT, wbuf_handle);
13157 CloseClipboard();
13158 }
13159
13160 #else
13161
13162 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
GetClipboardTextFn_DefaultImpl(void *)13163 static const char* GetClipboardTextFn_DefaultImpl(void*)
13164 {
13165 ImGuiContext& g = *GImGui;
13166 return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin();
13167 }
13168
13169 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
SetClipboardTextFn_DefaultImpl(void *,const char * text)13170 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
13171 {
13172 ImGuiContext& g = *GImGui;
13173 g.PrivateClipboard.clear();
13174 const char* text_end = text + strlen(text);
13175 g.PrivateClipboard.resize((int)(text_end - text) + 1);
13176 memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text));
13177 g.PrivateClipboard[(int)(text_end - text)] = 0;
13178 }
13179
13180 #endif
13181
13182 // Win32 API IME support (for Asian languages, etc.)
13183 #if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
13184
13185 #include <imm.h>
13186 #ifdef _MSC_VER
13187 #pragma comment(lib, "imm32")
13188 #endif
13189
ImeSetInputScreenPosFn_DefaultImpl(int x,int y)13190 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y)
13191 {
13192 // Notify OS Input Method Editor of text input position
13193 if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle)
13194 if (HIMC himc = ImmGetContext(hwnd))
13195 {
13196 COMPOSITIONFORM cf;
13197 cf.ptCurrentPos.x = x;
13198 cf.ptCurrentPos.y = y;
13199 cf.dwStyle = CFS_FORCE_POSITION;
13200 ImmSetCompositionWindow(himc, &cf);
13201 }
13202 }
13203
13204 #else
13205
ImeSetInputScreenPosFn_DefaultImpl(int,int)13206 static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {}
13207
13208 #endif
13209
13210 //-----------------------------------------------------------------------------
13211 // HELP
13212 //-----------------------------------------------------------------------------
13213
ShowMetricsWindow(bool * p_open)13214 void ImGui::ShowMetricsWindow(bool* p_open)
13215 {
13216 if (ImGui::Begin("ImGui Metrics", p_open))
13217 {
13218 ImGui::Text("Dear ImGui %s", ImGui::GetVersion());
13219 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
13220 ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3);
13221 ImGui::Text("%d allocations", (int)GImAllocatorActiveAllocationsCount);
13222 static bool show_clip_rects = true;
13223 ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_clip_rects);
13224 ImGui::Separator();
13225
13226 struct Funcs
13227 {
13228 static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label)
13229 {
13230 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);
13231 if (draw_list == ImGui::GetWindowDrawList())
13232 {
13233 ImGui::SameLine();
13234 ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
13235 if (node_open) ImGui::TreePop();
13236 return;
13237 }
13238
13239 ImDrawList* overlay_draw_list = ImGui::GetOverlayDrawList(); // Render additional visuals into the top-most draw list
13240 if (window && ImGui::IsItemHovered())
13241 overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
13242 if (!node_open)
13243 return;
13244
13245 int elem_offset = 0;
13246 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++)
13247 {
13248 if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0)
13249 continue;
13250 if (pcmd->UserCallback)
13251 {
13252 ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
13253 continue;
13254 }
13255 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
13256 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);
13257 if (show_clip_rects && ImGui::IsItemHovered())
13258 {
13259 ImRect clip_rect = pcmd->ClipRect;
13260 ImRect vtxs_rect;
13261 for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++)
13262 vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos);
13263 clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255));
13264 vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255));
13265 }
13266 if (!pcmd_node_open)
13267 continue;
13268
13269 // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
13270 ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
13271 while (clipper.Step())
13272 for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++)
13273 {
13274 char buf[300];
13275 char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
13276 ImVec2 triangles_pos[3];
13277 for (int n = 0; n < 3; n++, vtx_i++)
13278 {
13279 ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i];
13280 triangles_pos[n] = v.pos;
13281 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);
13282 }
13283 ImGui::Selectable(buf, false);
13284 if (ImGui::IsItemHovered())
13285 {
13286 ImDrawListFlags backup_flags = overlay_draw_list->Flags;
13287 overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles.
13288 overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f);
13289 overlay_draw_list->Flags = backup_flags;
13290 }
13291 }
13292 ImGui::TreePop();
13293 }
13294 ImGui::TreePop();
13295 }
13296
13297 static void NodeWindows(ImVector<ImGuiWindow*>& windows, const char* label)
13298 {
13299 if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size))
13300 return;
13301 for (int i = 0; i < windows.Size; i++)
13302 Funcs::NodeWindow(windows[i], "Window");
13303 ImGui::TreePop();
13304 }
13305
13306 static void NodeWindow(ImGuiWindow* window, const char* label)
13307 {
13308 if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window))
13309 return;
13310 ImGuiWindowFlags flags = window->Flags;
13311 NodeDrawList(window, window->DrawList, "DrawList");
13312 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);
13313 ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s..)", flags,
13314 (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
13315 (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "");
13316 ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window));
13317 ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed);
13318 ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask);
13319 ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
13320 if (window->NavRectRel[0].IsInverted())
13321 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);
13322 else
13323 ImGui::BulletText("NavRectRel[0]: <None>");
13324 if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
13325 if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
13326 if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size))
13327 {
13328 for (int n = 0; n < window->ColumnsStorage.Size; n++)
13329 {
13330 const ImGuiColumnsSet* columns = &window->ColumnsStorage[n];
13331 if (ImGui::TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags))
13332 {
13333 ImGui::BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->MaxX - columns->MinX, columns->MinX, columns->MaxX);
13334 for (int column_n = 0; column_n < columns->Columns.Size; column_n++)
13335 ImGui::BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, OffsetNormToPixels(columns, columns->Columns[column_n].OffsetNorm));
13336 ImGui::TreePop();
13337 }
13338 }
13339 ImGui::TreePop();
13340 }
13341 ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair));
13342 ImGui::TreePop();
13343 }
13344 };
13345
13346 // Access private state, we are going to display the draw lists from last frame
13347 ImGuiContext& g = *GImGui;
13348 Funcs::NodeWindows(g.Windows, "Windows");
13349 if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size))
13350 {
13351 for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++)
13352 Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList");
13353 ImGui::TreePop();
13354 }
13355 if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size))
13356 {
13357 for (int i = 0; i < g.OpenPopupStack.Size; i++)
13358 {
13359 ImGuiWindow* window = g.OpenPopupStack[i].Window;
13360 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" : "");
13361 }
13362 ImGui::TreePop();
13363 }
13364 if (ImGui::TreeNode("Internal state"))
13365 {
13366 const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
13367 ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
13368 ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
13369 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
13370 ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, input_source_names[g.ActiveIdSource]);
13371 ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
13372 ImGui::Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
13373 ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
13374 ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
13375 ImGui::Text("NavInputSource: %s", input_source_names[g.NavInputSource]);
13376 ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
13377 ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
13378 ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
13379 ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
13380 ImGui::TreePop();
13381 }
13382 }
13383 ImGui::End();
13384 }
13385
13386 //-----------------------------------------------------------------------------
13387
13388 // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
13389 // 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.
13390 #ifdef IMGUI_INCLUDE_IMGUI_USER_INL
13391 #include "imgui_user.inl"
13392 #endif
13393
13394 //-----------------------------------------------------------------------------
13395