· 6 years ago · Sep 06, 2019, 02:14 PM
1// dear imgui, v1.51 WIP
2// (main code and documentation)
3
4// See ImGui::ShowTestWindow() in imgui_demo.cpp for demo code.
5// Newcomers, read 'Programmer guide' below for notes on how to setup 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/772
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, e.g: https://www.patreon.com/imgui
12
13/*
14
15 Index
16 - MISSION STATEMENT
17 - END-USER GUIDE
18 - PROGRAMMER GUIDE (read me!)
19 - Read first
20 - How to update to a newer version of ImGui
21 - Getting started with integrating ImGui in your code/engine
22 - API BREAKING CHANGES (read me when you update!)
23 - ISSUES & TODO LIST
24 - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
25 - How can I help?
26 - What is ImTextureID and how do I display an image?
27 - I integrated ImGui in my engine and the text or lines are blurry..
28 - I integrated ImGui in my engine and some elements are clipping or disappearing when I move windows around..
29 - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels/IDs.
30 - How can I tell when ImGui wants my mouse/keyboard inputs VS when I can pass them to my application?
31 - How can I load a different font than the default?
32 - How can I easily use icons in my application?
33 - How can I load multiple fonts?
34 - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
35 - How can I preserve my ImGui context across reloading a DLL? (loss of the global/static variables)
36 - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
37 - ISSUES & TODO-LIST
38 - CODE
39
40 MISSION STATEMENT
41 =================
42
43 - Easy to use to create code-driven and data-driven tools
44 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools
45 - Easy to hack and improve
46 - Minimize screen real-estate usage
47 - Minimize setup and maintenance
48 - Minimize state storage on user side
49 - Portable, minimize dependencies, run on target (consoles, phones, etc.)
50 - Efficient runtime and memory consumption (NB- we do allocate when "growing" content - creating a window / opening a tree node for the first time, etc. - but a typical frame won't allocate anything)
51
52 Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes:
53 - Doesn't look fancy, doesn't animate
54 - Limited layout features, intricate layouts are typically crafted in code
55
56 END-USER GUIDE
57 ==============
58
59 - Double-click title bar to collapse window
60 - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin()
61 - Click and drag on lower right corner to resize window
62 - Click and drag on any empty space to move window
63 - Double-click/double-tap on lower right corner grip to auto-fit to content
64 - TAB/SHIFT+TAB to cycle through keyboard editable fields
65 - Use mouse wheel to scroll
66 - Use CTRL+mouse wheel to zoom window contents (if io.FontAllowScaling is true)
67 - CTRL+Click on a slider or drag box to input value as text
68 - Text editor:
69 - Hold SHIFT or use mouse to select text.
70 - CTRL+Left/Right to word jump
71 - CTRL+Shift+Left/Right to select words
72 - CTRL+A our Double-Click to select all
73 - CTRL+X,CTRL+C,CTRL+V to use OS clipboard
74 - CTRL+Z,CTRL+Y to undo/redo
75 - ESCAPE to revert text to its original value
76 - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
77 - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
78
79 PROGRAMMER GUIDE
80 ================
81
82 READ FIRST
83
84 - Read the FAQ below this section!
85 - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention on your side, no state duplication, less sync, less bugs.
86 - Call and read ImGui::ShowTestWindow() for demo code demonstrating most features.
87 - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861
88
89 HOW TO UPDATE TO A NEWER VERSION OF IMGUI
90
91 - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h)
92 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
93 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed from the public API.
94 If you have a problem with a missing function/symbols, search for its name in the code, there will likely be a comment about it.
95 Please report any issue to the GitHub page!
96 - Try to keep your copy of dear imgui reasonably up to date.
97
98 GETTING STARTED WITH INTEGRATING IMGUI IN YOUR CODE/ENGINE
99
100 - Add the ImGui source files to your projects, using your preferred build system. It is recommended you build the .cpp files as part of your project and not as a library.
101 - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types.
102 - See examples/ folder for standalone sample applications. To understand the integration process, you can read examples/opengl2_example/ because it is short,
103 then switch to the one more appropriate to your use case.
104 - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/.
105 - When using ImGui, your programming IDE if your friend: follow the declaration of variables, functions and types to find comments about them.
106
107 - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize (application resolution).
108 Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic integration you don't need to worry about it all.
109 - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory.
110 - Every frame:
111 - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.)
112 - Call ImGui::NewFrame() to begin the imgui frame
113 - You can use any ImGui function you want between NewFrame() and Render()
114 - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler.
115 (if you don't need to render, you still need to call Render() and ignore the callback, or call EndFrame() instead. if you call neither some aspects of windows focusing/moving will appear broken.)
116 - All rendering information are stored into command-lists until ImGui::Render() is called.
117 - ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide.
118 - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases of your own application.
119 - Refer to the examples applications in the examples/ folder for instruction on how to setup your code.
120 - A minimal application skeleton may be:
121
122 // Application init
123 ImGuiIO& io = ImGui::GetIO();
124 io.DisplaySize.x = 1920.0f;
125 io.DisplaySize.y = 1280.0f;
126 io.RenderDrawListsFn = MyRenderFunction; // Setup a render function, or set to NULL and call GetDrawData() after Render() to access the render data.
127 // TODO: Fill others settings of the io structure later.
128
129 // Load texture atlas (there is a default font so you don't need to care about choosing a font yet)
130 unsigned char* pixels;
131 int width, height;
132 io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height);
133 // TODO: At this points you've got the texture data and you need to upload that your your graphic system:
134 MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA)
135 // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer.
136 io.Fonts->TexID = (void*)texture;
137
138 // Application main loop
139 while (true)
140 {
141 // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.)
142 ImGuiIO& io = ImGui::GetIO();
143 io.DeltaTime = 1.0f/60.0f;
144 io.MousePos = mouse_pos;
145 io.MouseDown[0] = mouse_button_0;
146 io.MouseDown[1] = mouse_button_1;
147
148 // Call NewFrame(), after this point you can use ImGui::* functions anytime
149 ImGui::NewFrame();
150
151 // Most of your application code here
152 MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
153 MyGameRender(); // may use any ImGui functions as well!
154
155 // Render & swap video buffers
156 ImGui::Render();
157 SwapBuffers();
158 }
159
160 - A minimal render function skeleton may be:
161
162 void void MyRenderFunction(ImDrawData* draw_data)(ImDrawData* draw_data)
163 {
164 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
165 // TODO: Setup viewport, orthographic projection matrix
166 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
167 for (int n = 0; n < draw_data->CmdListsCount; n++)
168 {
169 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui
170 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui
171 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
172 {
173 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
174 if (pcmd->UserCallback)
175 {
176 pcmd->UserCallback(cmd_list, pcmd);
177 }
178 else
179 {
180 // Render 'pcmd->ElemCount/3' texture triangles
181 MyEngineBindTexture(pcmd->TextureId);
182 MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y));
183 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
184 }
185 idx_buffer += pcmd->ElemCount;
186 }
187 }
188 }
189
190 - The examples/ folders contains many functional implementation of the pseudo-code above.
191 - When calling NewFrame(), the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'io.WantTextInput' flags are updated.
192 They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide
193 mouse inputs from the rest of your application. Read the FAQ below for more information about those flags.
194
195 API BREAKING CHANGES
196 ====================
197
198 Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix.
199 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.
200 Also read releases logs https://github.com/ocornut/imgui/releases for more details.
201
202 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete).
203 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
204 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
205 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
206 - 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.
207 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame.
208 - 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.
209 - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete).
210 - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete).
211 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
212 - 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.
213 - 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.
214 - 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))'
215 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
216 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
217 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
218 - 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().
219 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
220 - 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.
221 - 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.
222 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
223 If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you.
224 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.
225 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.
226 ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col)
227 {
228 float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a;
229 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);
230 }
231 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.
232 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
233 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
234 - 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).
235 - 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.
236 - 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).
237 - 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)
238 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
239 - 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.
240 - 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.
241 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
242 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
243 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
244 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.
245 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!
246 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
247 - 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.
248 - 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
249 - 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.
250 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
251 - 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.
252 this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
253 - 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.
254 - the signature of the io.RenderDrawListsFn handler has changed!
255 ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
256 became:
257 ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
258 argument 'cmd_lists' -> 'draw_data->CmdLists'
259 argument 'cmd_lists_count' -> 'draw_data->CmdListsCount'
260 ImDrawList 'commands' -> 'CmdBuffer'
261 ImDrawList 'vtx_buffer' -> 'VtxBuffer'
262 ImDrawList n/a -> 'IdxBuffer' (new)
263 ImDrawCmd 'vtx_count' -> 'ElemCount'
264 ImDrawCmd 'clip_rect' -> 'ClipRect'
265 ImDrawCmd 'user_callback' -> 'UserCallback'
266 ImDrawCmd 'texture_id' -> 'TextureId'
267 - 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.
268 - 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!
269 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
270 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
271 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
272 - 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.
273 - 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
274 - 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!
275 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
276 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
277 - 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.
278 - 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.
279 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
280 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
281 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
282 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
283 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
284 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
285 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
286 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
287 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
288 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
289 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
290 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
291 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
292 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
293 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
294 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
295 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
296 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
297 this sequence:
298 const void* png_data;
299 unsigned int png_size;
300 ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size);
301 // <Copy to GPU>
302 became:
303 unsigned char* pixels;
304 int width, height;
305 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
306 // <Copy to GPU>
307 io.Fonts->TexID = (your_texture_identifier);
308 you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs.
309 it is now recommended that you sample the font texture with bilinear interpolation.
310 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID.
311 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
312 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
313 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
314 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
315 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
316 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
317 - 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)
318 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
319 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
320 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
321 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
322 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
323 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
324
325 ISSUES & TODO-LIST
326 ==================
327 See TODO.txt
328
329 FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
330 ======================================
331
332 Q: How can I help?
333 A: - If you are experienced enough with ImGui and with C/C++, look at the todo list and see how you want/can help!
334 - Become a Patron/donate! Convince your company to become a Patron or provide serious funding for development time! See http://www.patreon.com/imgui
335
336 Q: What is ImTextureID and how do I display an image?
337 A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function.
338 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!
339 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.
340 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.
341 Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing.
342 (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!)
343 To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions.
344 ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use.
345 It is your responsibility to get textures uploaded to your GPU.
346
347 Q: I integrated ImGui in my engine and the text or lines are blurry..
348 A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f).
349 Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension.
350
351 Q: I integrated ImGui in my engine and some elements are clipping or disappearing when I move windows around..
352 A: Most likely you are mishandling the clipping rectangles in your render function. Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height).
353
354 Q: Can I have multiple widgets with the same label? Can I have widget without a label?
355 A: Yes. A primer on the use of labels/IDs in ImGui..
356
357 - Elements that are not clickable, such as Text() items don't need an ID.
358
359 - Interactive widgets require state to be carried over multiple frames (most typically ImGui often needs to remember what is the "active" widget).
360 to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer.
361
362 Button("OK"); // Label = "OK", ID = hash of "OK"
363 Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel"
364
365 - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" in two different windows
366 or in two different locations of a tree.
367
368 - If you have a same ID twice in the same location, you'll have a conflict:
369
370 Button("OK");
371 Button("OK"); // ID collision! Both buttons will be treated as the same.
372
373 Fear not! this is easy to solve and there are many ways to solve it!
374
375 - When passing a label you can optionally specify extra unique ID information within string itself. This helps solving the simpler collision cases.
376 use "##" to pass a complement to the ID that won't be visible to the end-user:
377
378 Button("Play"); // Label = "Play", ID = hash of "Play"
379 Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above)
380 Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above)
381
382 - If you want to completely hide the label, but still need an ID:
383
384 Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!)
385
386 - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels.
387 For example you may want to include varying information in a window title bar (and windows are uniquely identified by their ID.. obviously)
388 Use "###" to pass a label that isn't part of ID:
389
390 Button("Hello###ID"; // Label = "Hello", ID = hash of "ID"
391 Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above)
392
393 sprintf(buf, "My game (%f FPS)###MyGame");
394 Begin(buf); // Variable label, ID = hash of "MyGame"
395
396 - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window.
397 This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements.
398 You can push a pointer, a string or an integer value. Remember that ID are formed from the concatenation of everything in the ID stack!
399
400 for (int i = 0; i < 100; i++)
401 {
402 PushID(i);
403 Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique)
404 PopID();
405 }
406
407 for (int i = 0; i < 100; i++)
408 {
409 MyObject* obj = Objects[i];
410 PushID(obj);
411 Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique)
412 PopID();
413 }
414
415 for (int i = 0; i < 100; i++)
416 {
417 MyObject* obj = Objects[i];
418 PushID(obj->Name);
419 Button("Click"); // Label = "Click", ID = hash of string + "label" (unique)
420 PopID();
421 }
422
423 - More example showing that you can stack multiple prefixes into the ID stack:
424
425 Button("Click"); // Label = "Click", ID = hash of "Click"
426 PushID("node");
427 Button("Click"); // Label = "Click", ID = hash of "node" + "Click"
428 PushID(my_ptr);
429 Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click"
430 PopID();
431 PopID();
432
433 - Tree nodes implicitly creates a scope for you by calling PushID().
434
435 Button("Click"); // Label = "Click", ID = hash of "Click"
436 if (TreeNode("node"))
437 {
438 Button("Click"); // Label = "Click", ID = hash of "node" + "Click"
439 TreePop();
440 }
441
442 - When working with trees, ID are used to preserve the open/close state of each tree node.
443 Depending on your use cases you may want to use strings, indices or pointers as ID.
444 e.g. when displaying a single object that may change over time (1-1 relationship), using a static string as ID will preserve your node open/closed state when the targeted object change.
445 e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. experiment and see what makes more sense!
446
447 Q: How can I tell when ImGui wants my mouse/keyboard inputs VS when I can pass them to my application?
448 A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure.
449 - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application.
450 - 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 without a keyboard).
451 Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is also generally ok,
452 as the bool toggles fairly rarely and you don't generally expect to interact with either ImGui or your application during the same frame when that transition occurs.
453 ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered.
454 (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically have 'io.WantCaptureKeyboard=false'.
455 Depending on your application logic it may or not be inconvenient. You might want to track which key-downs were for ImGui (e.g. with an array of bool) and filter out the corresponding key-ups.)
456
457 Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13)
458 A: Use the font atlas to load the TTF/OTF file you want:
459
460 ImGuiIO& io = ImGui::GetIO();
461 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
462 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
463
464 Q: How can I easily use icons in my application?
465 A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your strings.
466 Read 'How can I load multiple fonts?' and the file 'extra_fonts/README.txt' for instructions.
467
468 Q: How can I load multiple fonts?
469 A: Use the font atlas to pack them into a single texture:
470 (Read extra_fonts/README.txt and the code in ImFontAtlas for more details.)
471
472 ImGuiIO& io = ImGui::GetIO();
473 ImFont* font0 = io.Fonts->AddFontDefault();
474 ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
475 ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels);
476 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
477 // the first loaded font gets used by default
478 // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime
479
480 // Options
481 ImFontConfig config;
482 config.OversampleH = 3;
483 config.OversampleV = 1;
484 config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up
485 config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters
486 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
487
488 // Combine multiple fonts into one (e.g. for icon fonts)
489 ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
490 ImFontConfig config;
491 config.MergeMode = true;
492 io.Fonts->AddFontDefault();
493 io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font
494 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs
495
496 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
497 A: When loading a font, pass custom Unicode ranges to specify the glyphs to load.
498
499 // Add default Japanese ranges
500 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese());
501
502 // 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)
503 ImVector<ImWchar> ranges;
504 ImFontAtlas::GlyphRangesBuilder builder;
505 builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters)
506 builder.AddChar(0x7262); // Add a specific character
507 builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges
508 builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
509 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data);
510
511 All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax.
512 Specifying literal in your source code using a local code page (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work!
513 Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8.
514
515 Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that.
516 For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly.
517
518 Q: How can I preserve my ImGui context across reloading a DLL? (loss of the global/static variables)
519 A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' so you don't rely on the default globals.
520
521 Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
522 A: The easiest way is to create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, zero background alpha,
523 then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like.
524 You can also perfectly create a standalone ImDrawList instance _but_ you need ImGui to be initialized because ImDrawList pulls from ImGui data to retrieve the coordinates of the white pixel.
525
526 - tip: the construct 'IMGUI_ONCE_UPON_A_FRAME { ... }' will run the block of code only once a frame. You can use it to quickly add custom UI in the middle of a deep nested inner loop in your code.
527 - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug"
528 - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. this is also useful to set yourself in the context of another window (to get/set other settings)
529 - tip: you can call Render() multiple times (e.g for VR renders).
530 - tip: call and read the ShowTestWindow() code in imgui_demo.cpp for more example of how to use ImGui!
531
532*/
533
534#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
535#define _CRT_SECURE_NO_WARNINGS
536#endif
537
538#include <Windows.h>
539#include "imgui.h"
540#define IMGUI_DEFINE_MATH_OPERATORS
541#define IMGUI_DEFINE_PLACEMENT_NEW
542#include "imgui_internal.h"
543
544#include <ctype.h> // toupper, isprint
545#include <stdlib.h> // NULL, malloc, free, qsort, atoi
546#include <stdio.h> // vsnprintf, sscanf, printf
547#include <limits.h> // INT_MIN, INT_MAX
548#include <string>
549#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
550#include <stddef.h> // intptr_t
551#else
552#include <stdint.h> // intptr_t
553#endif
554
555#ifdef _MSC_VER
556#pragma warning (disable: 4127) // condition expression is constant
557#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
558#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
559#endif
560
561// Clang warnings with -Weverything
562#ifdef __clang__
563#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!
564#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.
565#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.
566#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.
567#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.
568#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it.
569#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
570#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.
571#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' //
572#elif defined(__GNUC__)
573#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
574#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
575#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
576#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
577#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
578#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers
579#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
580#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
581#endif
582
583//-------------------------------------------------------------------------
584// Forward Declarations
585//-------------------------------------------------------------------------
586
587static void LogRenderedText(const ImVec2& ref_pos, const char* text, const char* text_end = NULL);
588
589static void PushMultiItemsWidths(int components, float w_full = 0.0f);
590static float GetDraggedColumnOffset(int column_index);
591
592static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true);
593
594static ImFont* GetDefaultFont();
595static void SetCurrentFont(ImFont* font);
596static void SetCurrentWindow(ImGuiWindow* window);
597static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y);
598static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond);
599static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond);
600static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond);
601static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs);
602static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
603static inline bool IsWindowContentHoverable(ImGuiWindow* window);
604static void ClearSetNextWindowData();
605static void CheckStacksSize(ImGuiWindow* window, bool write);
606static void Scrollbar(ImGuiWindow* window, bool horizontal);
607
608static void AddDrawListToRenderList(ImVector<ImDrawList*>& out_render_list, ImDrawList* draw_list);
609static void AddWindowToRenderList(ImVector<ImDrawList*>& out_render_list, ImGuiWindow* window);
610static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>& out_sorted_windows, ImGuiWindow* window);
611
612static ImGuiIniData* FindWindowSettings(const char* name);
613static ImGuiIniData* AddWindowSettings(const char* name);
614static void LoadIniSettingsFromDisk(const char* ini_filename);
615static void SaveIniSettingsToDisk(const char* ini_filename);
616static void MarkIniSettingsDirty(ImGuiWindow* window);
617
618static ImRect GetVisibleRect();
619
620static bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags);
621static void CloseInactivePopups();
622static void ClosePopupToLevel(int remaining);
623static void ClosePopup(ImGuiID id);
624static ImGuiWindow* GetFrontMostModalRootWindow();
625static ImVec2 FindBestPopupWindowPos(const ImVec2& base_pos, const ImVec2& size, int* last_dir, const ImRect& rect_to_avoid);
626
627static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data);
628static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
629static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
630
631static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size);
632static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size);
633static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2);
634static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format);
635
636//-----------------------------------------------------------------------------
637// Platform dependent default implementations
638//-----------------------------------------------------------------------------
639
640static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
641static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
642static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
643
644//-----------------------------------------------------------------------------
645// Context
646//-----------------------------------------------------------------------------
647
648// Default font atlas storage.
649// New contexts always point by default to this font atlas. It can be changed by reassigning the GetIO().Fonts variable.
650static ImFontAtlas GImDefaultFontAtlas;
651
652// Default context storage + current context pointer.
653// Implicitely used by all ImGui functions. Always assumed to be != NULL. Change to a different context by calling ImGui::SetCurrentContext()
654// If you are hot-reloading this code in a DLL you will lose the static/global variables. Create your own context+font atlas instead of relying on those default (see FAQ entry "How can I preserve my ImGui context across reloading a DLL?").
655// ImGui is currently not thread-safe because of this variable. If you want thread-safety to allow N threads to access N different contexts, you might work around it by:
656// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts)
657// - or: Changing this variable to be TLS. You may #define GImGui in imconfig.h for further custom hackery. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586
658#ifndef GImGui
659static ImGuiContext GImDefaultContext;
660ImGuiContext* GImGui = &GImDefaultContext;
661#endif
662
663const char* const KeyNames[] = {
664 "unknown",
665 "vk_lbutton",
666 "vk_rbutton",
667 "vk_cancel",
668 "vk_mbutton",
669 "vk_xbutton1",
670 "vk_xbutton2",
671 "unknown",
672 "vk_back",
673 "vk_tab",
674 "unknown",
675 "unknown",
676 "vk_clear",
677 "vk_return",
678 "unknown",
679 "unknown",
680 "vk_shift",
681 "vk_control",
682 "vk_menu",
683 "vk_pause",
684 "capslock",
685 "vk_kana",
686 "unknown",
687 "vk_junja",
688 "vk_final",
689 "vk_kanji",
690 "unknown",
691 "vk_escape",
692 "vk_convert",
693 "vk_nonconvert",
694 "vk_accept",
695 "vk_modechange",
696 "vk_space",
697 "vk_prior",
698 "vk_next",
699 "vk_end",
700 "vk_home",
701 "vk_left",
702 "vk_up",
703 "vk_right",
704 "vk_down",
705 "vk_select",
706 "vk_print",
707 "vk_execute",
708 "vk_snapshot",
709 "vk_insert",
710 "vk_delete",
711 "vk_help",
712 "0",
713 "1",
714 "2",
715 "3",
716 "4",
717 "5",
718 "6",
719 "7",
720 "8",
721 "9",
722 "unknown",
723 "unknown",
724 "unknown",
725 "unknown",
726 "unknown",
727 "unknown",
728 "unknown",
729 "a",
730 "b",
731 "c",
732 "d",
733 "e",
734 "f",
735 "g",
736 "h",
737 "i",
738 "j",
739 "k",
740 "l",
741 "m",
742 "n",
743 "o",
744 "p",
745 "q",
746 "r",
747 "s",
748 "t",
749 "u",
750 "v",
751 "w",
752 "x",
753 "y",
754 "z",
755 "vk_lwin",
756 "vk_rwin",
757 "vk_apps",
758 "unknown",
759 "vk_sleep",
760 "vk_numpad0",
761 "vk_numpad1",
762 "vk_numpad2",
763 "vk_numpad3",
764 "vk_numpad4",
765 "vk_numpad5",
766 "vk_numpad6",
767 "vk_numpad7",
768 "vk_numpad8",
769 "vk_numpad9",
770 "vk_multiply",
771 "vk_add",
772 "vk_separator",
773 "vk_subtract",
774 "vk_decimal",
775 "vk_divide",
776 "vk_f1",
777 "vk_f2",
778 "vk_f3",
779 "vk_f4",
780 "vk_f5",
781 "vk_f6",
782 "vk_f7",
783 "vk_f8",
784 "vk_f9",
785 "vk_f10",
786 "vk_f11",
787 "vk_f12",
788 "vk_f13",
789 "vk_f14",
790 "vk_f15",
791 "vk_f16",
792 "vk_f17",
793 "vk_f18",
794 "vk_f19",
795 "vk_f20",
796 "vk_f21",
797 "vk_f22",
798 "vk_f23",
799 "vk_f24",
800 "unknown",
801 "unknown",
802 "unknown",
803 "unknown",
804 "unknown",
805 "unknown",
806 "unknown",
807 "unknown",
808 "vk_numlock",
809 "vk_scroll",
810 "vk_oem_nec_equal",
811 "vk_oem_fj_masshou",
812 "vk_oem_fj_touroku",
813 "vk_oem_fj_loya",
814 "vk_oem_fj_roya",
815 "unknown",
816 "unknown",
817 "unknown",
818 "unknown",
819 "unknown",
820 "unknown",
821 "unknown",
822 "unknown",
823 "unknown",
824 "vk_lshift",
825 "vk_rshift",
826 "vk_lcontrol",
827 "vk_rcontrol",
828 "vk_lmenu",
829 "vk_rmenu"
830};
831
832//-----------------------------------------------------------------------------
833// User facing structures
834//-----------------------------------------------------------------------------
835
836ImGuiStyle::ImGuiStyle() {
837 Alpha = 1.0f; // Global alpha applies to everything in ImGui
838 WindowPadding = ImVec2(8, 8); // Padding within a window
839 WindowMinSize = ImVec2(32, 32); // Minimum window size
840 WindowRounding = 9.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows
841 WindowTitleAlign = ImVec2(0.0f, 0.5f);// Alignment for title bar text
842 ChildWindowRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
843 FramePadding = ImVec2(4, 3); // Padding within a framed rectangle (used by most widgets)
844 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
845 ItemSpacing = ImVec2(8, 4); // Horizontal and vertical spacing between widgets/lines
846 ItemInnerSpacing = ImVec2(4, 4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
847 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!
848 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
849 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns
850 ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
851 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
852 GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar
853 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
854 ButtonTextAlign = ImVec2(0.5f, 0.5f);// Alignment of button text when button is larger than text.
855 DisplayWindowPadding = ImVec2(22, 22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows.
856 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.
857 AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU.
858 AntiAliasedShapes = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.)
859 CurveTessellationTol = 1.25f; // Tessellation tolerance. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
860
861 Colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
862 Colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
863 Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f);
864 Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
865 Colors[ImGuiCol_PopupBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f);
866 Colors[ImGuiCol_Border] = ImVec4(0.70f, 0.70f, 0.70f, 0.22f);
867 Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
868 Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.30f); // Background of checkbox, radio button, plot, slider, text input
869 Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.90f, 0.80f, 0.80f, 0.40f);
870 Colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f);
871 Colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f);
872 Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f);
873 Colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f);
874 Colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f);
875 Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f);
876 Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f);
877 Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f);
878 Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f);
879 Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f);
880 Colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);
881 Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
882 Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f);
883 Colors[ImGuiCol_Button] = ImVec4(0.67f, 0.40f, 0.40f, 0.60f);
884 Colors[ImGuiCol_ButtonHovered] = ImVec4(0.67f, 0.40f, 0.40f, 1.00f);
885 Colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f);
886 Colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f);
887 Colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f);
888 Colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f);
889 Colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
890 Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f);
891 Colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f);
892 Colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
893 Colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
894 Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
895 Colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f);
896 Colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f);
897 Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f);
898 Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
899 Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
900 Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
901 Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
902 Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
903 Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
904}
905
906ImGuiIO::ImGuiIO() {
907 // Most fields are initialized with zero
908 memset(this, 0, sizeof(*this));
909
910 // Settings
911 DisplaySize = ImVec2(-1.0f, -1.0f);
912 DeltaTime = 1.0f / 60.0f;
913 IniSavingRate = 5.0f;
914 IniFilename = "imgui.ini";
915 LogFilename = "imgui_log.txt";
916 Fonts = &GImDefaultFontAtlas;
917 FontGlobalScale = 1.0f;
918 FontDefault = NULL;
919 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
920 MousePos = ImVec2(-1, -1);
921 MousePosPrev = ImVec2(-1, -1);
922 MouseDoubleClickTime = 0.30f;
923 MouseDoubleClickMaxDist = 6.0f;
924 MouseDragThreshold = 6.0f;
925 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++)
926 MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
927 for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++)
928 KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
929 for (int i = 0; i < ImGuiKey_COUNT; i++)
930 KeyMap[i] = -1;
931 KeyRepeatDelay = 0.250f;
932 KeyRepeatRate = 0.050f;
933 UserData = NULL;
934
935 // User functions
936 RenderDrawListsFn = NULL;
937 MemAllocFn = malloc;
938 MemFreeFn = free;
939 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
940 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
941 ClipboardUserData = NULL;
942 ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl;
943 ImeWindowHandle = NULL;
944
945 // Set OS X style defaults based on __APPLE__ compile time flag
946#ifdef __APPLE__
947 OSXBehaviors = true;
948#endif
949}
950
951// Pass in translated ASCII characters for text input.
952// - with glfw you can get those from the callback set in glfwSetCharCallback()
953// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
954void ImGuiIO::AddInputCharacter(ImWchar c) {
955 const int n = ImStrlenW(InputCharacters);
956 if (n + 1 < IM_ARRAYSIZE(InputCharacters)) {
957 InputCharacters[n] = c;
958 InputCharacters[n + 1] = '\0';
959 }
960}
961
962void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) {
963 // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more
964 const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar);
965 ImWchar wchars[wchars_buf_len];
966 ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL);
967 for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++)
968 AddInputCharacter(wchars[i]);
969}
970
971//-----------------------------------------------------------------------------
972// HELPERS
973//-----------------------------------------------------------------------------
974
975template <typename t>
976inline t nigga(t number, t min, t max) {
977 if (number < min) {
978 return min;
979 }
980 else if (number > max) {
981 return max;
982 }
983 else {
984 return number;
985 }
986}
987
988#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose
989#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
990
991// Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n.
992#ifdef _WIN32
993#define IM_NEWLINE "\r\n"
994#else
995#define IM_NEWLINE "\n"
996#endif
997
998ImVec2 ImLineClosestPoint(const ImVec2 & a, const ImVec2 & b, const ImVec2 & p) {
999 ImVec2 ap = p - a;
1000 ImVec2 ab_dir = b - a;
1001 float ab_len = sqrtf(ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y);
1002 ab_dir *= 1.0f / ab_len;
1003 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
1004 if (dot < 0.0f)
1005 return a;
1006 if (dot > ab_len)
1007 return b;
1008 return a + ab_dir * dot;
1009}
1010
1011bool ImTriangleContainsPoint(const ImVec2 & a, const ImVec2 & b, const ImVec2 & c, const ImVec2 & p) {
1012 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
1013 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
1014 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
1015 return ((b1 == b2) && (b2 == b3));
1016}
1017
1018void ImTriangleBarycentricCoords(const ImVec2 & a, const ImVec2 & b, const ImVec2 & c, const ImVec2 & p, float& out_u, float& out_v, float& out_w) {
1019 ImVec2 v0 = b - a;
1020 ImVec2 v1 = c - a;
1021 ImVec2 v2 = p - a;
1022 const float denom = v0.x * v1.y - v1.x * v0.y;
1023 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
1024 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
1025 out_u = 1.0f - out_v - out_w;
1026}
1027
1028ImVec2 ImTriangleClosestPoint(const ImVec2 & a, const ImVec2 & b, const ImVec2 & c, const ImVec2 & p) {
1029 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
1030 ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
1031 ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
1032 float dist2_ab = ImLengthSqr(p - proj_ab);
1033 float dist2_bc = ImLengthSqr(p - proj_bc);
1034 float dist2_ca = ImLengthSqr(p - proj_ca);
1035 float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
1036 if (m == dist2_ab)
1037 return proj_ab;
1038 if (m == dist2_bc)
1039 return proj_bc;
1040 return proj_ca;
1041}
1042
1043int ImStricmp(const char* str1, const char* str2) {
1044 int d;
1045 while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) {
1046 str1++; str2++;
1047 }
1048 return d;
1049}
1050
1051int ImStrnicmp(const char* str1, const char* str2, int count) {
1052 int d = 0;
1053 while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) {
1054 str1++; str2++; count--;
1055 }
1056 return d;
1057}
1058
1059void ImStrncpy(char* dst, const char* src, int count) {
1060 if (count < 1) return;
1061 strncpy(dst, src, (size_t)count);
1062 dst[count - 1] = 0;
1063}
1064
1065char* ImStrdup(const char* str) {
1066 size_t len = strlen(str) + 1;
1067 void* buff = ImGui::MemAlloc(len);
1068 return (char*)memcpy(buff, (const void*)str, len);
1069}
1070
1071int ImStrlenW(const ImWchar * str) {
1072 int n = 0;
1073 while (*str++) n++;
1074 return n;
1075}
1076
1077const ImWchar* ImStrbolW(const ImWchar * buf_mid_line, const ImWchar * buf_begin) // find beginning-of-line
1078{
1079 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
1080 buf_mid_line--;
1081 return buf_mid_line;
1082}
1083
1084const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) {
1085 if (!needle_end)
1086 needle_end = needle + strlen(needle);
1087
1088 const char un0 = (char)toupper(*needle);
1089 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) {
1090 if (toupper(*haystack) == un0) {
1091 const char* b = needle + 1;
1092 for (const char* a = haystack + 1; b < needle_end; a++, b++)
1093 if (toupper(*a) != toupper(*b))
1094 break;
1095 if (b == needle_end)
1096 return haystack;
1097 }
1098 haystack++;
1099 }
1100 return NULL;
1101}
1102
1103// MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
1104// 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.
1105int ImFormatString(char* buf, int buf_size, const char* fmt, ...) {
1106 IM_ASSERT(buf_size > 0);
1107 va_list args;
1108 va_start(args, fmt);
1109 int w = vsnprintf(buf, buf_size, fmt, args);
1110 va_end(args);
1111 if (w == -1 || w >= buf_size)
1112 w = buf_size - 1;
1113 buf[w] = 0;
1114 return w;
1115}
1116
1117int ImFormatStringV(char* buf, int buf_size, const char* fmt, va_list args) {
1118 IM_ASSERT(buf_size > 0);
1119 int w = vsnprintf(buf, buf_size, fmt, args);
1120 if (w == -1 || w >= buf_size)
1121 w = buf_size - 1;
1122 buf[w] = 0;
1123 return w;
1124}
1125
1126// Pass data_size==0 for zero-terminated strings
1127// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
1128ImU32 ImHash(const void* data, int data_size, ImU32 seed) {
1129 static ImU32 crc32_lut[256] = { 0 };
1130 if (!crc32_lut[1]) {
1131 const ImU32 polynomial = 0xEDB88320;
1132 for (ImU32 i = 0; i < 256; i++) {
1133 ImU32 crc = i;
1134 for (ImU32 j = 0; j < 8; j++)
1135 crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial);
1136 crc32_lut[i] = crc;
1137 }
1138 }
1139
1140 seed = ~seed;
1141 ImU32 crc = seed;
1142 const unsigned char* current = (const unsigned char*)data;
1143
1144 if (data_size > 0) {
1145 // Known size
1146 while (data_size--)
1147 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++];
1148 }
1149 else {
1150 // Zero-terminated string
1151 while (unsigned char c = *current++) {
1152 // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
1153 // Because this syntax is rarely used we are optimizing for the common case.
1154 // - If we reach ### in the string we discard the hash so far and reset to the seed.
1155 // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller.
1156 if (c == '#' && current[0] == '#' && current[1] == '#')
1157 crc = seed;
1158 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1159 }
1160 }
1161 return ~crc;
1162}
1163
1164//-----------------------------------------------------------------------------
1165// ImText* helpers
1166//-----------------------------------------------------------------------------
1167
1168// Convert UTF-8 to 32-bits character, process single character input.
1169// Based on stb_from_utf8() from github.com/nothings/stb/
1170// We handle UTF-8 decoding error by skipping forward.
1171int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) {
1172 unsigned int c = (unsigned int)-1;
1173 const unsigned char* str = (const unsigned char*)in_text;
1174 if (!(*str & 0x80)) {
1175 c = (unsigned int)(*str++);
1176 *out_char = c;
1177 return 1;
1178 }
1179 if ((*str & 0xe0) == 0xc0) {
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 *out_char = 0xFFFD; // will be invalid but not end of string
1191 if (in_text_end && in_text_end - (const char*)str < 3) return 1;
1192 if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3;
1193 if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below
1194 c = (unsigned int)((*str++ & 0x0f) << 12);
1195 if ((*str & 0xc0) != 0x80) return 3;
1196 c += (unsigned int)((*str++ & 0x3f) << 6);
1197 if ((*str & 0xc0) != 0x80) return 3;
1198 c += (*str++ & 0x3f);
1199 *out_char = c;
1200 return 3;
1201 }
1202 if ((*str & 0xf8) == 0xf0) {
1203 *out_char = 0xFFFD; // will be invalid but not end of string
1204 if (in_text_end && in_text_end - (const char*)str < 4) return 1;
1205 if (*str > 0xf4) return 4;
1206 if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4;
1207 if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below
1208 c = (unsigned int)((*str++ & 0x07) << 18);
1209 if ((*str & 0xc0) != 0x80) return 4;
1210 c += (unsigned int)((*str++ & 0x3f) << 12);
1211 if ((*str & 0xc0) != 0x80) return 4;
1212 c += (unsigned int)((*str++ & 0x3f) << 6);
1213 if ((*str & 0xc0) != 0x80) return 4;
1214 c += (*str++ & 0x3f);
1215 // utf-8 encodings of values used in surrogate pairs are invalid
1216 if ((c & 0xFFFFF800) == 0xD800) return 4;
1217 *out_char = c;
1218 return 4;
1219 }
1220 *out_char = 0;
1221 return 0;
1222}
1223
1224int ImTextStrFromUtf8(ImWchar * buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) {
1225 ImWchar* buf_out = buf;
1226 ImWchar* buf_end = buf + buf_size;
1227 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) {
1228 unsigned int c;
1229 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1230 if (c == 0)
1231 break;
1232 if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes
1233 * buf_out++ = (ImWchar)c;
1234 }
1235 *buf_out = 0;
1236 if (in_text_remaining)
1237 * in_text_remaining = in_text;
1238 return (int)(buf_out - buf);
1239}
1240
1241int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) {
1242 int char_count = 0;
1243 while ((!in_text_end || in_text < in_text_end) && *in_text) {
1244 unsigned int c;
1245 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1246 if (c == 0)
1247 break;
1248 if (c < 0x10000)
1249 char_count++;
1250 }
1251 return char_count;
1252}
1253
1254// Based on stb_to_utf8() from github.com/nothings/stb/
1255static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) {
1256 if (c < 0x80) {
1257 buf[0] = (char)c;
1258 return 1;
1259 }
1260 if (c < 0x800) {
1261 if (buf_size < 2) return 0;
1262 buf[0] = (char)(0xc0 + (c >> 6));
1263 buf[1] = (char)(0x80 + (c & 0x3f));
1264 return 2;
1265 }
1266 if (c >= 0xdc00 && c < 0xe000) {
1267 return 0;
1268 }
1269 if (c >= 0xd800 && c < 0xdc00) {
1270 if (buf_size < 4) return 0;
1271 buf[0] = (char)(0xf0 + (c >> 18));
1272 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
1273 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
1274 buf[3] = (char)(0x80 + ((c) & 0x3f));
1275 return 4;
1276 }
1277 //else if (c < 0x10000)
1278 {
1279 if (buf_size < 3) return 0;
1280 buf[0] = (char)(0xe0 + (c >> 12));
1281 buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
1282 buf[2] = (char)(0x80 + ((c) & 0x3f));
1283 return 3;
1284 }
1285}
1286
1287static inline int ImTextCountUtf8BytesFromChar(unsigned int c) {
1288 if (c < 0x80) return 1;
1289 if (c < 0x800) return 2;
1290 if (c >= 0xdc00 && c < 0xe000) return 0;
1291 if (c >= 0xd800 && c < 0xdc00) return 4;
1292 return 3;
1293}
1294
1295int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar * in_text, const ImWchar * in_text_end) {
1296 char* buf_out = buf;
1297 const char* buf_end = buf + buf_size;
1298 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) {
1299 unsigned int c = (unsigned int)(*in_text++);
1300 if (c < 0x80)
1301 * buf_out++ = (char)c;
1302 else
1303 buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end - buf_out - 1), c);
1304 }
1305 *buf_out = 0;
1306 return (int)(buf_out - buf);
1307}
1308
1309int ImTextCountUtf8BytesFromStr(const ImWchar * in_text, const ImWchar * in_text_end) {
1310 int bytes_count = 0;
1311 while ((!in_text_end || in_text < in_text_end) && *in_text) {
1312 unsigned int c = (unsigned int)(*in_text++);
1313 if (c < 0x80)
1314 bytes_count++;
1315 else
1316 bytes_count += ImTextCountUtf8BytesFromChar(c);
1317 }
1318 return bytes_count;
1319}
1320
1321ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) {
1322 float s = 1.0f / 255.0f;
1323 return ImVec4(
1324 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
1325 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
1326 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
1327 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
1328}
1329
1330ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4 & in) {
1331 ImU32 out;
1332 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
1333 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
1334 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
1335 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
1336 return out;
1337}
1338
1339ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) {
1340 ImGuiStyle& style = GImGui->Style;
1341 ImVec4 c = style.Colors[idx];
1342 c.w *= style.Alpha * alpha_mul;
1343 return ColorConvertFloat4ToU32(c);
1344}
1345
1346ImU32 ImGui::GetColorU32(const ImVec4 & col) {
1347 ImGuiStyle& style = GImGui->Style;
1348 ImVec4 c = col;
1349 c.w *= style.Alpha;
1350 return ColorConvertFloat4ToU32(c);
1351}
1352
1353const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) {
1354 ImGuiStyle& style = GImGui->Style;
1355 return style.Colors[idx];
1356}
1357
1358ImU32 ImGui::GetColorU32(ImU32 col) {
1359 float style_alpha = GImGui->Style.Alpha;
1360 if (style_alpha >= 1.0f)
1361 return col;
1362 int a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
1363 a = (int)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
1364 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
1365}
1366
1367// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
1368// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
1369void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) {
1370 float K = 0.f;
1371 if (g < b) {
1372 const float tmp = g; g = b; b = tmp;
1373 K = -1.f;
1374 }
1375 if (r < g) {
1376 const float tmp = r; r = g; g = tmp;
1377 K = -2.f / 6.f - K;
1378 }
1379
1380 const float chroma = r - (g < b ? g : b);
1381 out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f));
1382 out_s = chroma / (r + 1e-20f);
1383 out_v = r;
1384}
1385
1386// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
1387// also http://en.wikipedia.org/wiki/HSL_and_HSV
1388void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) {
1389 if (s == 0.0f) {
1390 // gray
1391 out_r = out_g = out_b = v;
1392 return;
1393 }
1394
1395 h = fmodf(h, 1.0f) / (60.0f / 360.0f);
1396 int i = (int)h;
1397 float f = h - (float)i;
1398 float p = v * (1.0f - s);
1399 float q = v * (1.0f - s * f);
1400 float t = v * (1.0f - s * (1.0f - f));
1401
1402 switch (i) {
1403 case 0: out_r = v; out_g = t; out_b = p; break;
1404 case 1: out_r = q; out_g = v; out_b = p; break;
1405 case 2: out_r = p; out_g = v; out_b = t; break;
1406 case 3: out_r = p; out_g = q; out_b = v; break;
1407 case 4: out_r = t; out_g = p; out_b = v; break;
1408 case 5: default: out_r = v; out_g = p; out_b = q; break;
1409 }
1410}
1411
1412FILE* ImFileOpen(const char* filename, const char* mode) {
1413#if defined(_WIN32) && !defined(__CYGWIN__)
1414 // 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)
1415 const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
1416 const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
1417 ImVector<ImWchar> buf;
1418 buf.resize(filename_wsize + mode_wsize);
1419 ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
1420 ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
1421 return _wfopen((wchar_t*)& buf[0], (wchar_t*)& buf[filename_wsize]);
1422#else
1423 return fopen(filename, mode);
1424#endif
1425}
1426
1427// Load file content into memory
1428// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree()
1429void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) {
1430 IM_ASSERT(filename && file_open_mode);
1431 if (out_file_size)
1432 * out_file_size = 0;
1433
1434 FILE* f;
1435 if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
1436 return NULL;
1437
1438 long file_size_signed;
1439 if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) {
1440 fclose(f);
1441 return NULL;
1442 }
1443
1444 int file_size = (int)file_size_signed;
1445 void* file_data = ImGui::MemAlloc(file_size + padding_bytes);
1446 if (file_data == NULL) {
1447 fclose(f);
1448 return NULL;
1449 }
1450 if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) {
1451 fclose(f);
1452 ImGui::MemFree(file_data);
1453 return NULL;
1454 }
1455 if (padding_bytes > 0)
1456 memset((void*)(((char*)file_data) + file_size), 0, padding_bytes);
1457
1458 fclose(f);
1459 if (out_file_size)
1460 * out_file_size = file_size;
1461
1462 return file_data;
1463}
1464
1465//-----------------------------------------------------------------------------
1466// ImGuiStorage
1467//-----------------------------------------------------------------------------
1468
1469// Helper: Key->value storage
1470void ImGuiStorage::Clear() {
1471 Data.clear();
1472}
1473
1474// std::lower_bound but without the bullshit
1475static ImVector<ImGuiStorage::Pair>::iterator LowerBound(ImVector<ImGuiStorage::Pair> & data, ImGuiID key) {
1476 ImVector<ImGuiStorage::Pair>::iterator first = data.begin();
1477 ImVector<ImGuiStorage::Pair>::iterator last = data.end();
1478 int count = (int)(last - first);
1479 while (count > 0) {
1480 int count2 = count / 2;
1481 ImVector<ImGuiStorage::Pair>::iterator mid = first + count2;
1482 if (mid->key < key) {
1483 first = ++mid;
1484 count -= count2 + 1;
1485 }
1486 else {
1487 count = count2;
1488 }
1489 }
1490 return first;
1491}
1492
1493int ImGuiStorage::GetInt(ImGuiID key, int default_val) const {
1494 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1495 if (it == Data.end() || it->key != key)
1496 return default_val;
1497 return it->val_i;
1498}
1499
1500bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const {
1501 return GetInt(key, default_val ? 1 : 0) != 0;
1502}
1503
1504float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const {
1505 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1506 if (it == Data.end() || it->key != key)
1507 return default_val;
1508 return it->val_f;
1509}
1510
1511void* ImGuiStorage::GetVoidPtr(ImGuiID key) const {
1512 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1513 if (it == Data.end() || it->key != key)
1514 return NULL;
1515 return it->val_p;
1516}
1517
1518// 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.
1519int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) {
1520 ImVector<Pair>::iterator it = LowerBound(Data, key);
1521 if (it == Data.end() || it->key != key)
1522 it = Data.insert(it, Pair(key, default_val));
1523 return &it->val_i;
1524}
1525
1526bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) {
1527 return (bool*)GetIntRef(key, default_val ? 1 : 0);
1528}
1529
1530float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) {
1531 ImVector<Pair>::iterator it = LowerBound(Data, key);
1532 if (it == Data.end() || it->key != key)
1533 it = Data.insert(it, Pair(key, default_val));
1534 return &it->val_f;
1535}
1536
1537void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) {
1538 ImVector<Pair>::iterator it = LowerBound(Data, key);
1539 if (it == Data.end() || it->key != key)
1540 it = Data.insert(it, Pair(key, default_val));
1541 return &it->val_p;
1542}
1543
1544// 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)
1545void ImGuiStorage::SetInt(ImGuiID key, int val) {
1546 ImVector<Pair>::iterator it = LowerBound(Data, key);
1547 if (it == Data.end() || it->key != key) {
1548 Data.insert(it, Pair(key, val));
1549 return;
1550 }
1551 it->val_i = val;
1552}
1553
1554void ImGuiStorage::SetBool(ImGuiID key, bool val) {
1555 SetInt(key, val ? 1 : 0);
1556}
1557
1558void ImGuiStorage::SetFloat(ImGuiID key, float val) {
1559 ImVector<Pair>::iterator it = LowerBound(Data, key);
1560 if (it == Data.end() || it->key != key) {
1561 Data.insert(it, Pair(key, val));
1562 return;
1563 }
1564 it->val_f = val;
1565}
1566
1567void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) {
1568 ImVector<Pair>::iterator it = LowerBound(Data, key);
1569 if (it == Data.end() || it->key != key) {
1570 Data.insert(it, Pair(key, val));
1571 return;
1572 }
1573 it->val_p = val;
1574}
1575
1576void ImGuiStorage::SetAllInt(int v) {
1577 for (int i = 0; i < Data.Size; i++)
1578 Data[i].val_i = v;
1579}
1580
1581//-----------------------------------------------------------------------------
1582// ImGuiTextFilter
1583//-----------------------------------------------------------------------------
1584
1585// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
1586ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) {
1587 if (default_filter) {
1588 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
1589 Build();
1590 }
1591 else {
1592 InputBuf[0] = 0;
1593 CountGrep = 0;
1594 }
1595}
1596
1597bool ImGuiTextFilter::Draw(const char* label, float width) {
1598 if (width != 0.0f)
1599 ImGui::PushItemWidth(width);
1600 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
1601 if (width != 0.0f)
1602 ImGui::PopItemWidth();
1603 if (value_changed)
1604 Build();
1605 return value_changed;
1606}
1607
1608void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange> & out) {
1609 out.resize(0);
1610 const char* wb = b;
1611 const char* we = wb;
1612 while (we < e) {
1613 if (*we == separator) {
1614 out.push_back(TextRange(wb, we));
1615 wb = we + 1;
1616 }
1617 we++;
1618 }
1619 if (wb != we)
1620 out.push_back(TextRange(wb, we));
1621}
1622
1623void ImGuiTextFilter::Build() {
1624 Filters.resize(0);
1625 TextRange input_range(InputBuf, InputBuf + strlen(InputBuf));
1626 input_range.split(',', Filters);
1627
1628 CountGrep = 0;
1629 for (int i = 0; i != Filters.Size; i++) {
1630 Filters[i].trim_blanks();
1631 if (Filters[i].empty())
1632 continue;
1633 if (Filters[i].front() != '-')
1634 CountGrep += 1;
1635 }
1636}
1637
1638bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const {
1639 if (Filters.empty())
1640 return true;
1641
1642 if (text == NULL)
1643 text = "";
1644
1645 for (int i = 0; i != Filters.Size; i++) {
1646 const TextRange& f = Filters[i];
1647 if (f.empty())
1648 continue;
1649 if (f.front() == '-') {
1650 // Subtract
1651 if (ImStristr(text, text_end, f.begin() + 1, f.end()) != NULL)
1652 return false;
1653 }
1654 else {
1655 // Grep
1656 if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
1657 return true;
1658 }
1659 }
1660
1661 // Implicit * grep
1662 if (CountGrep == 0)
1663 return true;
1664
1665 return false;
1666}
1667
1668//-----------------------------------------------------------------------------
1669// ImGuiTextBuffer
1670//-----------------------------------------------------------------------------
1671
1672// On some platform vsnprintf() takes va_list by reference and modifies it.
1673// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
1674#ifndef va_copy
1675#define va_copy(dest, src) (dest = src)
1676#endif
1677
1678// Helper: Text buffer for logging/accumulating text
1679void ImGuiTextBuffer::appendv(const char* fmt, va_list args) {
1680 va_list args_copy;
1681 va_copy(args_copy, args);
1682
1683 int len = vsnprintf(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
1684 if (len <= 0)
1685 return;
1686
1687 const int write_off = Buf.Size;
1688 const int needed_sz = write_off + len;
1689 if (write_off + len >= Buf.Capacity) {
1690 int double_capacity = Buf.Capacity * 2;
1691 Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity);
1692 }
1693
1694 Buf.resize(needed_sz);
1695 ImFormatStringV(&Buf[write_off] - 1, len + 1, fmt, args_copy);
1696}
1697
1698void ImGuiTextBuffer::append(const char* fmt, ...) {
1699 va_list args;
1700 va_start(args, fmt);
1701 appendv(fmt, args);
1702 va_end(args);
1703}
1704
1705//-----------------------------------------------------------------------------
1706// ImGuiSimpleColumns
1707//-----------------------------------------------------------------------------
1708
1709ImGuiSimpleColumns::ImGuiSimpleColumns() {
1710 Count = 0;
1711 Spacing = Width = NextWidth = 0.0f;
1712 memset(Pos, 0, sizeof(Pos));
1713 memset(NextWidths, 0, sizeof(NextWidths));
1714}
1715
1716void ImGuiSimpleColumns::Update(int count, float spacing, bool clear) {
1717 IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
1718 Count = count;
1719 Width = NextWidth = 0.0f;
1720 Spacing = spacing;
1721 if (clear) memset(NextWidths, 0, sizeof(NextWidths));
1722 for (int i = 0; i < Count; i++) {
1723 if (i > 0 && NextWidths[i] > 0.0f)
1724 Width += Spacing;
1725 Pos[i] = (float)(int)Width;
1726 Width += NextWidths[i];
1727 NextWidths[i] = 0.0f;
1728 }
1729}
1730
1731float ImGuiSimpleColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
1732{
1733 NextWidth = 0.0f;
1734 NextWidths[0] = ImMax(NextWidths[0], w0);
1735 NextWidths[1] = ImMax(NextWidths[1], w1);
1736 NextWidths[2] = ImMax(NextWidths[2], w2);
1737 for (int i = 0; i < 3; i++)
1738 NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
1739 return ImMax(Width, NextWidth);
1740}
1741
1742float ImGuiSimpleColumns::CalcExtraSpace(float avail_w) {
1743 return ImMax(0.0f, avail_w - Width);
1744}
1745
1746//-----------------------------------------------------------------------------
1747// ImGuiListClipper
1748//-----------------------------------------------------------------------------
1749
1750static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) {
1751 // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor.
1752 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. Consider moving within SetCursorXXX functions?
1753 ImGui::SetCursorPosY(pos_y);
1754 ImGuiWindow* window = ImGui::GetCurrentWindow();
1755 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.
1756 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.
1757 if (window->DC.ColumnsCount > 1)
1758 window->DC.ColumnsCellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
1759}
1760
1761// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
1762// Use case B: Begin() called from constructor with items_height>0
1763// 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.
1764void ImGuiListClipper::Begin(int count, float items_height) {
1765 StartPosY = ImGui::GetCursorPosY();
1766 ItemsHeight = items_height;
1767 ItemsCount = count;
1768 StepNo = 0;
1769 DisplayEnd = DisplayStart = -1;
1770 if (ItemsHeight > 0.0f) {
1771 ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
1772 if (DisplayStart > 0)
1773 SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
1774 StepNo = 2;
1775 }
1776}
1777
1778void ImGuiListClipper::End() {
1779 if (ItemsCount < 0)
1780 return;
1781 // 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.
1782 if (ItemsCount < INT_MAX)
1783 SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
1784 ItemsCount = -1;
1785 StepNo = 3;
1786}
1787
1788bool ImGuiListClipper::Step() {
1789 if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) {
1790 ItemsCount = -1;
1791 return false;
1792 }
1793 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.
1794 {
1795 DisplayStart = 0;
1796 DisplayEnd = 1;
1797 StartPosY = ImGui::GetCursorPosY();
1798 StepNo = 1;
1799 return true;
1800 }
1801 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.
1802 {
1803 if (ItemsCount == 1) {
1804 ItemsCount = -1; return false;
1805 }
1806 float items_height = ImGui::GetCursorPosY() - StartPosY;
1807 IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically
1808 Begin(ItemsCount - 1, items_height);
1809 DisplayStart++;
1810 DisplayEnd++;
1811 StepNo = 3;
1812 return true;
1813 }
1814 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.
1815 {
1816 IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
1817 StepNo = 3;
1818 return true;
1819 }
1820 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.
1821 End();
1822 return false;
1823}
1824
1825//-----------------------------------------------------------------------------
1826// ImGuiWindow
1827//-----------------------------------------------------------------------------
1828
1829ImGuiWindow::ImGuiWindow(const char* name) {
1830 Name = ImStrdup(name);
1831 ID = ImHash(name, 0);
1832 IDStack.push_back(ID);
1833 MoveId = GetID("#MOVE");
1834
1835 Flags = 0;
1836 OrderWithinParent = 0;
1837 PosFloat = Pos = ImVec2(0.0f, 0.0f);
1838 Size = SizeFull = ImVec2(0.0f, 0.0f);
1839 SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f);
1840 WindowPadding = ImVec2(0.0f, 0.0f);
1841 Scroll = ImVec2(0.0f, 0.0f);
1842 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
1843 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
1844 ScrollbarX = ScrollbarY = false;
1845 ScrollbarSizes = ImVec2(0.0f, 0.0f);
1846 BorderSize = 0.0f;
1847 Active = WasActive = false;
1848 Accessed = false;
1849 Collapsed = false;
1850 SkipItems = false;
1851 BeginCount = 0;
1852 PopupId = 0;
1853 AutoFitFramesX = AutoFitFramesY = -1;
1854 AutoFitOnlyGrows = false;
1855 AutoFitChildAxises = 0x00;
1856 AutoPosLastDirection = -1;
1857 HiddenFrames = 0;
1858 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
1859 SetWindowPosCenterWanted = false;
1860
1861 LastFrameActive = -1;
1862 ItemWidthDefault = 0.0f;
1863 FontWindowScale = 1.0f;
1864
1865 DrawList = (ImDrawList*)ImGui::MemAlloc(sizeof(ImDrawList));
1866 IM_PLACEMENT_NEW(DrawList) ImDrawList();
1867 DrawList->_OwnerName = Name;
1868 RootWindow = NULL;
1869 RootNonPopupWindow = NULL;
1870 ParentWindow = NULL;
1871
1872 FocusIdxAllCounter = FocusIdxTabCounter = -1;
1873 FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
1874 FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX;
1875}
1876
1877ImGuiWindow::~ImGuiWindow() {
1878 DrawList->~ImDrawList();
1879 ImGui::MemFree(DrawList);
1880 DrawList = NULL;
1881 ImGui::MemFree(Name);
1882 Name = NULL;
1883}
1884
1885ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) {
1886 ImGuiID seed = IDStack.back();
1887 ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
1888 ImGui::KeepAliveID(id);
1889 return id;
1890}
1891
1892ImGuiID ImGuiWindow::GetID(const void* ptr) {
1893 ImGuiID seed = IDStack.back();
1894 ImGuiID id = ImHash(&ptr, sizeof(void*), seed);
1895 ImGui::KeepAliveID(id);
1896 return id;
1897}
1898
1899ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) {
1900 ImGuiID seed = IDStack.back();
1901 return ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
1902}
1903
1904//-----------------------------------------------------------------------------
1905// Internal API exposed in imgui_internal.h
1906//-----------------------------------------------------------------------------
1907
1908static void SetCurrentWindow(ImGuiWindow * window) {
1909 ImGuiContext& g = *GImGui;
1910 g.CurrentWindow = window;
1911 if (window)
1912 g.FontSize = window->CalcFontSize();
1913}
1914
1915ImGuiWindow* ImGui::GetParentWindow() {
1916 ImGuiContext& g = *GImGui;
1917 IM_ASSERT(g.CurrentWindowStack.Size >= 2);
1918 return g.CurrentWindowStack[(unsigned int)g.CurrentWindowStack.Size - 2];
1919}
1920
1921void ImGui::SetActiveID(ImGuiID id, ImGuiWindow * window) {
1922 ImGuiContext& g = *GImGui;
1923 g.ActiveId = id;
1924 g.ActiveIdAllowOverlap = false;
1925 g.ActiveIdIsJustActivated = true;
1926 if (id)
1927 g.ActiveIdIsAlive = true;
1928 g.ActiveIdWindow = window;
1929}
1930
1931void ImGui::ClearActiveID() {
1932 SetActiveID(0, NULL);
1933}
1934
1935void ImGui::SetHoveredID(ImGuiID id) {
1936 ImGuiContext& g = *GImGui;
1937 g.HoveredId = id;
1938 g.HoveredIdAllowOverlap = false;
1939}
1940
1941void ImGui::KeepAliveID(ImGuiID id) {
1942 ImGuiContext& g = *GImGui;
1943 if (g.ActiveId == id)
1944 g.ActiveIdIsAlive = true;
1945}
1946
1947// Advance cursor given item size for layout.
1948void ImGui::ItemSize(const ImVec2 & size, float text_offset_y) {
1949 ImGuiWindow* window = GetCurrentWindow();
1950 if (window->SkipItems)
1951 return;
1952
1953 // Always align ourselves on pixel boundaries
1954 ImGuiContext& g = *GImGui;
1955 const float line_height = ImMax(window->DC.CurrentLineHeight, size.y);
1956 const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y);
1957 window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y);
1958 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));
1959 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
1960 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
1961
1962 //window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // Debug
1963
1964 window->DC.PrevLineHeight = line_height;
1965 window->DC.PrevLineTextBaseOffset = text_base_offset;
1966 window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f;
1967}
1968
1969void ImGui::ItemSize(const ImRect & bb, float text_offset_y) {
1970 ItemSize(bb.GetSize(), text_offset_y);
1971}
1972
1973// Declare item bounding box for clipping and interaction.
1974// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
1975// declares their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd().
1976bool ImGui::ItemAdd(const ImRect & bb, const ImGuiID * id) {
1977 ImGuiWindow* window = GetCurrentWindow();
1978 window->DC.LastItemId = id ? *id : 0;
1979 window->DC.LastItemRect = bb;
1980 window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = false;
1981 if (IsClippedEx(bb, id, false))
1982 return false;
1983
1984 // This is a sensible default, but widgets are free to override it after calling ItemAdd()
1985 ImGuiContext& g = *GImGui;
1986 if (IsMouseHoveringRect(bb.Min, bb.Max)) {
1987 // Matching the behavior of IsHovered() but allow if ActiveId==window->MoveID (we clicked on the window background)
1988 // So that clicking on items with no active id such as Text() still returns true with IsItemHovered()
1989 window->DC.LastItemHoveredRect = true;
1990 if (g.HoveredRootWindow == window->RootWindow)
1991 if (g.ActiveId == 0 || (id && g.ActiveId == *id) || g.ActiveIdAllowOverlap || (g.ActiveId == window->MoveId))
1992 if (IsWindowContentHoverable(window))
1993 window->DC.LastItemHoveredAndUsable = true;
1994 }
1995
1996 return true;
1997}
1998
1999bool ImGui::IsClippedEx(const ImRect & bb, const ImGuiID * id, bool clip_even_when_logged) {
2000 ImGuiContext& g = *GImGui;
2001 ImGuiWindow* window = GetCurrentWindowRead();
2002 if (!bb.Overlaps(window->ClipRect))
2003 if (!id || *id != GImGui->ActiveId)
2004 if (clip_even_when_logged || !g.LogEnabled)
2005 return true;
2006 return false;
2007}
2008
2009// NB: This is an internal helper. The user-facing IsItemHovered() is using data emitted from ItemAdd(), with a slightly different logic.
2010bool ImGui::IsHovered(const ImRect & bb, ImGuiID id, bool flatten_childs) {
2011 ImGuiContext& g = *GImGui;
2012 if (g.HoveredId == 0 || g.HoveredId == id || g.HoveredIdAllowOverlap) {
2013 ImGuiWindow* window = GetCurrentWindowRead();
2014 if (g.HoveredWindow == window || (flatten_childs && g.HoveredRootWindow == window->RootWindow))
2015 if ((g.ActiveId == 0 || g.ActiveId == id || g.ActiveIdAllowOverlap) && IsMouseHoveringRect(bb.Min, bb.Max))
2016 if (IsWindowContentHoverable(g.HoveredRootWindow))
2017 return true;
2018 }
2019 return false;
2020}
2021
2022bool ImGui::FocusableItemRegister(ImGuiWindow * window, bool is_active, bool tab_stop) {
2023 ImGuiContext& g = *GImGui;
2024
2025 const bool allow_keyboard_focus = window->DC.AllowKeyboardFocus;
2026 window->FocusIdxAllCounter++;
2027 if (allow_keyboard_focus)
2028 window->FocusIdxTabCounter++;
2029
2030 // Process keyboard input at this point: TAB, Shift-TAB switch focus
2031 // We can always TAB out of a widget that doesn't allow tabbing in.
2032 if (tab_stop && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && is_active && IsKeyPressedMap(ImGuiKey_Tab)) {
2033 // Modulo on index will be applied at the end of frame once we've got the total counter of items.
2034 window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1);
2035 }
2036
2037 if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent)
2038 return true;
2039
2040 if (allow_keyboard_focus)
2041 if (window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
2042 return true;
2043
2044 return false;
2045}
2046
2047void ImGui::FocusableItemUnregister(ImGuiWindow * window) {
2048 window->FocusIdxAllCounter--;
2049 window->FocusIdxTabCounter--;
2050}
2051
2052ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) {
2053 ImGuiContext& g = *GImGui;
2054 ImVec2 content_max;
2055 if (size.x < 0.0f || size.y < 0.0f)
2056 content_max = g.CurrentWindow->Pos + GetContentRegionMax();
2057 if (size.x <= 0.0f)
2058 size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x;
2059 if (size.y <= 0.0f)
2060 size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y;
2061 return size;
2062}
2063
2064float ImGui::CalcWrapWidthForPos(const ImVec2 & pos, float wrap_pos_x) {
2065 if (wrap_pos_x < 0.0f)
2066 return 0.0f;
2067
2068 ImGuiWindow* window = GetCurrentWindowRead();
2069 if (wrap_pos_x == 0.0f)
2070 wrap_pos_x = GetContentRegionMax().x + window->Pos.x;
2071 else if (wrap_pos_x > 0.0f)
2072 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
2073
2074 return ImMax(wrap_pos_x - pos.x, 1.0f);
2075}
2076
2077//-----------------------------------------------------------------------------
2078
2079void* ImGui::MemAlloc(size_t sz) {
2080 GImGui->IO.MetricsAllocs++;
2081 return GImGui->IO.MemAllocFn(sz);
2082}
2083
2084void ImGui::MemFree(void* ptr) {
2085 if (ptr) GImGui->IO.MetricsAllocs--;
2086 return GImGui->IO.MemFreeFn(ptr);
2087}
2088
2089const char* ImGui::GetClipboardText() {
2090 return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : "";
2091}
2092
2093void ImGui::SetClipboardText(const char* text) {
2094 if (GImGui->IO.SetClipboardTextFn)
2095 GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text);
2096}
2097
2098const char* ImGui::GetVersion() {
2099 return IMGUI_VERSION;
2100}
2101
2102// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself
2103// 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
2104ImGuiContext* ImGui::GetCurrentContext() {
2105 return GImGui;
2106}
2107
2108void ImGui::SetCurrentContext(ImGuiContext* ctx) {
2109#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
2110 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
2111#else
2112 GImGui = ctx;
2113#endif
2114}
2115
2116ImGuiContext* ImGui::CreateContext(void* (*malloc_fn)(size_t), void(*free_fn)(void*)) {
2117 if (!malloc_fn) malloc_fn = malloc;
2118 ImGuiContext* ctx = (ImGuiContext*)malloc_fn(sizeof(ImGuiContext));
2119 IM_PLACEMENT_NEW(ctx) ImGuiContext();
2120 ctx->IO.MemAllocFn = malloc_fn;
2121 ctx->IO.MemFreeFn = free_fn ? free_fn : free;
2122 return ctx;
2123}
2124
2125void ImGui::DestroyContext(ImGuiContext* ctx) {
2126 void(*free_fn)(void*) = ctx->IO.MemFreeFn;
2127 ctx->~ImGuiContext();
2128 free_fn(ctx);
2129 if (GImGui == ctx)
2130 SetCurrentContext(NULL);
2131}
2132
2133ImGuiIO& ImGui::GetIO() {
2134 return GImGui->IO;
2135}
2136
2137ImGuiStyle& ImGui::GetStyle() {
2138 return GImGui->Style;
2139}
2140
2141// Same value as passed to your RenderDrawListsFn() function. valid after Render() and until the next call to NewFrame()
2142ImDrawData* ImGui::GetDrawData() {
2143 return GImGui->RenderDrawData.Valid ? &GImGui->RenderDrawData : NULL;
2144}
2145
2146float ImGui::GetTime() {
2147 return GImGui->Time;
2148}
2149
2150int ImGui::GetFrameCount() {
2151 return GImGui->FrameCount;
2152}
2153
2154void ImGui::NewFrame() {
2155 ImGuiContext& g = *GImGui;
2156
2157 // Check user data
2158 IM_ASSERT(g.IO.DeltaTime >= 0.0f); // Need a positive DeltaTime (zero is tolerated but will cause some timing issues)
2159 //IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f);
2160 IM_ASSERT(g.IO.Fonts->Fonts.Size > 0); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
2161 IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
2162 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f); // Invalid style setting
2163 //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)
2164
2165 if (!g.Initialized) {
2166 // Initialize on first frame
2167 g.LogClipboard = (ImGuiTextBuffer*)ImGui::MemAlloc(sizeof(ImGuiTextBuffer));
2168 IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer();
2169
2170 IM_ASSERT(g.Settings.empty());
2171 LoadIniSettingsFromDisk(g.IO.IniFilename);
2172 g.Initialized = true;
2173 }
2174
2175 SetCurrentFont(GetDefaultFont());
2176 IM_ASSERT(g.Font->IsLoaded());
2177
2178 g.Time += g.IO.DeltaTime;
2179 g.FrameCount += 1;
2180 g.TooltipOverrideCount = 0;
2181 g.OverlayDrawList.Clear();
2182 g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID);
2183 g.OverlayDrawList.PushClipRectFullScreen();
2184
2185 // Mark rendering data as invalid to prevent user who may have a handle on it to use it
2186 g.RenderDrawData.Valid = false;
2187 g.RenderDrawData.CmdLists = NULL;
2188 g.RenderDrawData.CmdListsCount = g.RenderDrawData.TotalVtxCount = g.RenderDrawData.TotalIdxCount = 0;
2189
2190 // Update mouse input state
2191 if (g.IO.MousePos.x < 0 && g.IO.MousePos.y < 0)
2192 g.IO.MousePos = ImVec2(-9999.0f, -9999.0f);
2193 if ((g.IO.MousePos.x < 0 && g.IO.MousePos.y < 0) || (g.IO.MousePosPrev.x < 0 && g.IO.MousePosPrev.y < 0)) // if mouse just appeared or disappeared (negative coordinate) we cancel out movement in MouseDelta
2194 g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
2195 else
2196 g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
2197 g.IO.MousePosPrev = g.IO.MousePos;
2198 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) {
2199 g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
2200 g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
2201 g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
2202 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;
2203 g.IO.MouseDoubleClicked[i] = false;
2204 if (g.IO.MouseClicked[i]) {
2205 if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime) {
2206 if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
2207 g.IO.MouseDoubleClicked[i] = true;
2208 g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click
2209 }
2210 else {
2211 g.IO.MouseClickedTime[i] = g.Time;
2212 }
2213 g.IO.MouseClickedPos[i] = g.IO.MousePos;
2214 g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
2215 }
2216 else if (g.IO.MouseDown[i]) {
2217 g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]));
2218 }
2219 }
2220 memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
2221 for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
2222 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;
2223
2224 // Calculate frame-rate for the user, as a purely luxurious feature
2225 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
2226 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
2227 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
2228 g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame));
2229
2230 // Clear reference to active widget if the widget isn't alive anymore
2231 g.HoveredIdPreviousFrame = g.HoveredId;
2232 g.HoveredId = 0;
2233 g.HoveredIdAllowOverlap = false;
2234 if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
2235 ClearActiveID();
2236 g.ActiveIdPreviousFrame = g.ActiveId;
2237 g.ActiveIdIsAlive = false;
2238 g.ActiveIdIsJustActivated = false;
2239
2240 // Handle user moving window (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows.
2241 if (g.MovedWindowMoveId && g.MovedWindowMoveId == g.ActiveId) {
2242 KeepAliveID(g.MovedWindowMoveId);
2243 IM_ASSERT(g.MovedWindow && g.MovedWindow->RootWindow);
2244 IM_ASSERT(g.MovedWindow->RootWindow->MoveId == g.MovedWindowMoveId);
2245 if (g.IO.MouseDown[0]) {
2246 if (!(g.MovedWindow->Flags & ImGuiWindowFlags_NoMove)) {
2247 g.MovedWindow->PosFloat += g.IO.MouseDelta;
2248 if (!(g.MovedWindow->Flags & ImGuiWindowFlags_NoSavedSettings) && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f))
2249 MarkIniSettingsDirty(g.MovedWindow);
2250 }
2251 FocusWindow(g.MovedWindow);
2252 }
2253 else {
2254 ClearActiveID();
2255 g.MovedWindow = NULL;
2256 g.MovedWindowMoveId = 0;
2257 }
2258 }
2259 else {
2260 g.MovedWindow = NULL;
2261 g.MovedWindowMoveId = 0;
2262 }
2263
2264 // Delay saving settings so we don't spam disk too much
2265 if (g.SettingsDirtyTimer > 0.0f) {
2266 g.SettingsDirtyTimer -= g.IO.DeltaTime;
2267 if (g.SettingsDirtyTimer <= 0.0f)
2268 SaveIniSettingsToDisk(g.IO.IniFilename);
2269 }
2270
2271 // Find the window we are hovering. Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow
2272 g.HoveredWindow = g.MovedWindow ? g.MovedWindow : FindHoveredWindow(g.IO.MousePos, false);
2273 if (g.HoveredWindow && (g.HoveredWindow->Flags & ImGuiWindowFlags_ChildWindow))
2274 g.HoveredRootWindow = g.HoveredWindow->RootWindow;
2275 else
2276 g.HoveredRootWindow = g.MovedWindow ? g.MovedWindow->RootWindow : FindHoveredWindow(g.IO.MousePos, true);
2277
2278 if (ImGuiWindow * modal_window = GetFrontMostModalRootWindow()) {
2279 g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f);
2280 ImGuiWindow* window = g.HoveredRootWindow;
2281 while (window && window != modal_window)
2282 window = window->ParentWindow;
2283 if (!window)
2284 g.HoveredRootWindow = g.HoveredWindow = NULL;
2285 }
2286 else {
2287 g.ModalWindowDarkeningRatio = 0.0f;
2288 }
2289
2290 // Are we using inputs? Tell user so they can capture/discard the inputs away from the rest of their application.
2291 // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership.
2292 int mouse_earliest_button_down = -1;
2293 bool mouse_any_down = false;
2294 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) {
2295 if (g.IO.MouseClicked[i])
2296 g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty());
2297 mouse_any_down |= g.IO.MouseDown[i];
2298 if (g.IO.MouseDown[i])
2299 if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[mouse_earliest_button_down] > g.IO.MouseClickedTime[i])
2300 mouse_earliest_button_down = i;
2301 }
2302 bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
2303 if (g.CaptureMouseNextFrame != -1)
2304 g.IO.WantCaptureMouse = (g.CaptureMouseNextFrame != 0);
2305 else
2306 g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.ActiveId != 0) || (!g.OpenPopupStack.empty());
2307 g.IO.WantCaptureKeyboard = (g.CaptureKeyboardNextFrame != -1) ? (g.CaptureKeyboardNextFrame != 0) : (g.ActiveId != 0);
2308 g.IO.WantTextInput = (g.ActiveId != 0 && g.InputTextState.Id == g.ActiveId);
2309 g.MouseCursor = ImGuiMouseCursor_Arrow;
2310 g.CaptureMouseNextFrame = g.CaptureKeyboardNextFrame = -1;
2311 g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default
2312
2313 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
2314 if (!mouse_avail_to_imgui)
2315 g.HoveredWindow = g.HoveredRootWindow = NULL;
2316
2317 // Scale & Scrolling
2318 if (g.HoveredWindow && g.IO.MouseWheel != 0.0f && !g.HoveredWindow->Collapsed) {
2319 ImGuiWindow* window = g.HoveredWindow;
2320
2321 if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) {
2322 // Zoom / Scale window
2323 const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
2324 const float scale = new_font_scale / window->FontWindowScale;
2325 window->FontWindowScale = new_font_scale;
2326
2327 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
2328 window->Pos += offset;
2329 window->PosFloat += offset;
2330 window->Size *= scale;
2331 window->SizeFull *= scale;
2332 }
2333 else if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) {
2334 // Scroll
2335 const int scroll_lines = (window->Flags & ImGuiWindowFlags_ComboBox) ? 3 : 5;
2336 SetWindowScrollY(window, window->Scroll.y - (g.IO.MouseWheel * 0.15f) * window->CalcFontSize() * scroll_lines);
2337 }
2338 }
2339
2340 // Pressing TAB activate widget focus
2341 // NB: Don't discard FocusedWindow if it isn't active, so that a window that go on/off programatically won't lose its keyboard focus.
2342 if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && IsKeyPressedMap(ImGuiKey_Tab, false))
2343 g.NavWindow->FocusIdxTabRequestNext = 0;
2344
2345 // Mark all windows as not visible
2346 for (int i = 0; i != g.Windows.Size; i++) {
2347 ImGuiWindow* window = g.Windows[i];
2348 window->WasActive = window->Active;
2349 window->Active = false;
2350 window->Accessed = false;
2351 }
2352
2353 // Closing the focused window restore focus to the first active root window in descending z-order
2354 if (g.NavWindow && !g.NavWindow->WasActive)
2355 for (int i = g.Windows.Size - 1; i >= 0; i--)
2356 if (g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) {
2357 FocusWindow(g.Windows[i]);
2358 break;
2359 }
2360
2361 // No window should be open at the beginning of the frame.
2362 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
2363 g.CurrentWindowStack.resize(0);
2364 g.CurrentPopupStack.resize(0);
2365 CloseInactivePopups();
2366
2367 // Create implicit window - we will only render it if the user has added something to it.
2368 ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
2369 ImGui::Begin("Debug");
2370}
2371
2372// NB: behavior of ImGui after Shutdown() is not tested/guaranteed at the moment. This function is merely here to free heap allocations.
2373void ImGui::Shutdown() {
2374 ImGuiContext& g = *GImGui;
2375
2376 // 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)
2377 if (g.IO.Fonts) // Testing for NULL to allow user to NULLify in case of running Shutdown() on multiple contexts. Bit hacky.
2378 g.IO.Fonts->Clear();
2379
2380 // Cleanup of other data are conditional on actually having used ImGui.
2381 if (!g.Initialized)
2382 return;
2383
2384 SaveIniSettingsToDisk(g.IO.IniFilename);
2385
2386 for (int i = 0; i < g.Windows.Size; i++) {
2387 g.Windows[i]->~ImGuiWindow();
2388 ImGui::MemFree(g.Windows[i]);
2389 }
2390 g.Windows.clear();
2391 g.WindowsSortBuffer.clear();
2392 g.CurrentWindow = NULL;
2393 g.CurrentWindowStack.clear();
2394 g.NavWindow = NULL;
2395 g.HoveredWindow = NULL;
2396 g.HoveredRootWindow = NULL;
2397 g.ActiveIdWindow = NULL;
2398 g.MovedWindow = NULL;
2399 for (int i = 0; i < g.Settings.Size; i++)
2400 ImGui::MemFree(g.Settings[i].Name);
2401 g.Settings.clear();
2402 g.ColorModifiers.clear();
2403 g.StyleModifiers.clear();
2404 g.FontStack.clear();
2405 g.OpenPopupStack.clear();
2406 g.CurrentPopupStack.clear();
2407 g.SetNextWindowSizeConstraintCallback = NULL;
2408 g.SetNextWindowSizeConstraintCallbackUserData = NULL;
2409 for (int i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++)
2410 g.RenderDrawLists[i].clear();
2411 g.OverlayDrawList.ClearFreeMemory();
2412 g.PrivateClipboard.clear();
2413 g.InputTextState.Text.clear();
2414 g.InputTextState.InitialText.clear();
2415 g.InputTextState.TempTextBuffer.clear();
2416
2417 if (g.LogFile && g.LogFile != stdout) {
2418 fclose(g.LogFile);
2419 g.LogFile = NULL;
2420 }
2421 if (g.LogClipboard) {
2422 g.LogClipboard->~ImGuiTextBuffer();
2423 ImGui::MemFree(g.LogClipboard);
2424 }
2425
2426 g.Initialized = false;
2427}
2428
2429static ImGuiIniData* FindWindowSettings(const char* name) {
2430 ImGuiContext& g = *GImGui;
2431 ImGuiID id = ImHash(name, 0);
2432 for (int i = 0; i != g.Settings.Size; i++) {
2433 ImGuiIniData* ini = &g.Settings[i];
2434 if (ini->Id == id)
2435 return ini;
2436 }
2437 return NULL;
2438}
2439
2440static ImGuiIniData* AddWindowSettings(const char* name) {
2441 GImGui->Settings.resize(GImGui->Settings.Size + 1);
2442 ImGuiIniData * ini = &GImGui->Settings.back();
2443 ini->Name = ImStrdup(name);
2444 ini->Id = ImHash(name, 0);
2445 ini->Collapsed = false;
2446 ini->Pos = ImVec2(FLT_MAX, FLT_MAX);
2447 ini->Size = ImVec2(0, 0);
2448 return ini;
2449}
2450
2451// Zero-tolerance, poor-man .ini parsing
2452// FIXME: Write something less rubbish
2453static void LoadIniSettingsFromDisk(const char* ini_filename) {
2454 ImGuiContext& g = *GImGui;
2455 if (!ini_filename)
2456 return;
2457
2458 int file_size;
2459 char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_size, 1);
2460 if (!file_data)
2461 return;
2462
2463 ImGuiIniData* settings = NULL;
2464 const char* buf_end = file_data + file_size;
2465 for (const char* line_start = file_data; line_start < buf_end; ) {
2466 const char* line_end = line_start;
2467 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
2468 line_end++;
2469
2470 if (line_start[0] == '[' && line_end > line_start && line_end[-1] == ']') {
2471 char name[64];
2472 ImFormatString(name, IM_ARRAYSIZE(name), "%.*s", (int)(line_end - line_start - 2), line_start + 1);
2473 settings = FindWindowSettings(name);
2474 if (!settings)
2475 settings = AddWindowSettings(name);
2476 }
2477 else if (settings) {
2478 float x, y;
2479 int i;
2480 if (sscanf(line_start, "Pos=%f,%f", &x, &y) == 2)
2481 settings->Pos = ImVec2(x, y);
2482 else if (sscanf(line_start, "Size=%f,%f", &x, &y) == 2)
2483 settings->Size = ImMax(ImVec2(x, y), g.Style.WindowMinSize);
2484 else if (sscanf(line_start, "Collapsed=%d", &i) == 1)
2485 settings->Collapsed = (i != 0);
2486 }
2487
2488 line_start = line_end + 1;
2489 }
2490
2491 ImGui::MemFree(file_data);
2492}
2493
2494static void SaveIniSettingsToDisk(const char* ini_filename) {
2495 ImGuiContext& g = *GImGui;
2496 g.SettingsDirtyTimer = 0.0f;
2497 if (!ini_filename)
2498 return;
2499
2500 // Gather data from windows that were active during this session
2501 for (int i = 0; i != g.Windows.Size; i++) {
2502 ImGuiWindow* window = g.Windows[i];
2503 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
2504 continue;
2505 ImGuiIniData* settings = FindWindowSettings(window->Name);
2506 if (!settings) // This will only return NULL in the rare instance where the window was first created with ImGuiWindowFlags_NoSavedSettings then had the flag disabled later on. We don't bind settings in this case (bug #1000).
2507 continue;
2508 settings->Pos = window->Pos;
2509 settings->Size = window->SizeFull;
2510 settings->Collapsed = window->Collapsed;
2511 }
2512
2513 // Write .ini file
2514 // If a window wasn't opened in this session we preserve its settings
2515 FILE* f = ImFileOpen(ini_filename, "wt");
2516 if (!f)
2517 return;
2518 for (int i = 0; i != g.Settings.Size; i++) {
2519 const ImGuiIniData* settings = &g.Settings[i];
2520 if (settings->Pos.x == FLT_MAX)
2521 continue;
2522 const char* name = settings->Name;
2523 if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
2524 name = p;
2525 fprintf(f, "[%s]\n", name);
2526 fprintf(f, "Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
2527 fprintf(f, "Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
2528 fprintf(f, "Collapsed=%d\n", settings->Collapsed);
2529 fprintf(f, "\n");
2530 }
2531
2532 fclose(f);
2533}
2534
2535static void MarkIniSettingsDirty(ImGuiWindow * window) {
2536 ImGuiContext& g = *GImGui;
2537 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
2538 if (g.SettingsDirtyTimer <= 0.0f)
2539 g.SettingsDirtyTimer = g.IO.IniSavingRate;
2540}
2541
2542// FIXME: Add a more explicit sort order in the window structure.
2543static int ChildWindowComparer(const void* lhs, const void* rhs) {
2544 const ImGuiWindow* a = *(const ImGuiWindow * *)lhs;
2545 const ImGuiWindow* b = *(const ImGuiWindow * *)rhs;
2546 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
2547 return d;
2548 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
2549 return d;
2550 if (int d = (a->Flags & ImGuiWindowFlags_ComboBox) - (b->Flags & ImGuiWindowFlags_ComboBox))
2551 return d;
2552 return (a->OrderWithinParent - b->OrderWithinParent);
2553}
2554
2555static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*> & out_sorted_windows, ImGuiWindow * window) {
2556 out_sorted_windows.push_back(window);
2557 if (window->Active) {
2558 int count = window->DC.ChildWindows.Size;
2559 if (count > 1)
2560 qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
2561 for (int i = 0; i < count; i++) {
2562 ImGuiWindow* child = window->DC.ChildWindows[i];
2563 if (child->Active)
2564 AddWindowToSortedBuffer(out_sorted_windows, child);
2565 }
2566 }
2567}
2568
2569static void AddDrawListToRenderList(ImVector<ImDrawList*> & out_render_list, ImDrawList * draw_list) {
2570 if (draw_list->CmdBuffer.empty())
2571 return;
2572
2573 // Remove trailing command if unused
2574 ImDrawCmd& last_cmd = draw_list->CmdBuffer.back();
2575 if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) {
2576 draw_list->CmdBuffer.pop_back();
2577 if (draw_list->CmdBuffer.empty())
2578 return;
2579 }
2580
2581 // 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.
2582 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
2583 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
2584 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
2585
2586 // Check that draw_list doesn't use more vertices than indexable in a single draw call (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per window)
2587 // If this assert triggers because you are drawing lots of stuff manually, you can:
2588 // A) Add '#define ImDrawIdx unsigned int' in imconfig.h to set the index size to 4 bytes. You'll need to handle the 4-bytes indices to your renderer.
2589 // For example, the OpenGL example code detect index size at compile-time by doing:
2590 // 'glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);'
2591 // 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.
2592 // B) 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.
2593 IM_ASSERT(((ImU64)draw_list->_VtxCurrentIdx >> (sizeof(ImDrawIdx) * 8)) == 0); // Too many vertices in same ImDrawList. See comment above.
2594
2595 out_render_list.push_back(draw_list);
2596 GImGui->IO.MetricsRenderVertices += draw_list->VtxBuffer.Size;
2597 GImGui->IO.MetricsRenderIndices += draw_list->IdxBuffer.Size;
2598}
2599
2600static void AddWindowToRenderList(ImVector<ImDrawList*> & out_render_list, ImGuiWindow * window) {
2601 AddDrawListToRenderList(out_render_list, window->DrawList);
2602 for (int i = 0; i < window->DC.ChildWindows.Size; i++) {
2603 ImGuiWindow* child = window->DC.ChildWindows[i];
2604 if (!child->Active) // clipped children may have been marked not active
2605 continue;
2606 if ((child->Flags & ImGuiWindowFlags_Popup) && child->HiddenFrames > 0)
2607 continue;
2608 AddWindowToRenderList(out_render_list, child);
2609 }
2610}
2611
2612// 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.
2613void ImGui::PushClipRect(const ImVec2 & clip_rect_min, const ImVec2 & clip_rect_max, bool intersect_with_current_clip_rect) {
2614 ImGuiWindow* window = GetCurrentWindow();
2615 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
2616 window->ClipRect = window->DrawList->_ClipRectStack.back();
2617}
2618
2619void ImGui::PopClipRect() {
2620 ImGuiWindow* window = GetCurrentWindow();
2621 window->DrawList->PopClipRect();
2622 window->ClipRect = window->DrawList->_ClipRectStack.back();
2623}
2624
2625// 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.
2626void ImGui::EndFrame() {
2627 ImGuiContext& g = *GImGui;
2628 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
2629 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // ImGui::EndFrame() called multiple times, or forgot to call ImGui::NewFrame() again
2630
2631 // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
2632 if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f) {
2633 g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y);
2634 g.OsImePosSet = g.OsImePosRequest;
2635 }
2636
2637 // Hide implicit "Debug" window if it hasn't been used
2638 IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls
2639 if (g.CurrentWindow && !g.CurrentWindow->Accessed)
2640 g.CurrentWindow->Active = false;
2641 ImGui::End();
2642
2643 // Click to focus window and start moving (after we're done with all our widgets)
2644 if (g.ActiveId == 0 && g.HoveredId == 0 && g.IO.MouseClicked[0]) {
2645 if (!(g.NavWindow && !g.NavWindow->WasActive && g.NavWindow->Active)) // Unless we just made a popup appear
2646 {
2647 if (g.HoveredRootWindow != NULL) {
2648 FocusWindow(g.HoveredWindow);
2649 if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove)) {
2650 g.MovedWindow = g.HoveredWindow;
2651 g.MovedWindowMoveId = g.HoveredRootWindow->MoveId;
2652 SetActiveID(g.MovedWindowMoveId, g.HoveredRootWindow);
2653 }
2654 }
2655 else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) {
2656 // Clicking on void disable focus
2657 FocusWindow(NULL);
2658 }
2659 }
2660 }
2661
2662 // Sort the window list so that all child windows are after their parent
2663 // We cannot do that on FocusWindow() because childs may not exist yet
2664 g.WindowsSortBuffer.resize(0);
2665 g.WindowsSortBuffer.reserve(g.Windows.Size);
2666 for (int i = 0; i != g.Windows.Size; i++) {
2667 ImGuiWindow* window = g.Windows[i];
2668 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
2669 continue;
2670 AddWindowToSortedBuffer(g.WindowsSortBuffer, window);
2671 }
2672
2673 IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong
2674 g.Windows.swap(g.WindowsSortBuffer);
2675
2676 // Clear Input data for next frame
2677 g.IO.MouseWheel = 0.0f;
2678 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
2679
2680 g.FrameCountEnded = g.FrameCount;
2681}
2682
2683void ImGui::Render() {
2684 ImGuiContext& g = *GImGui;
2685 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
2686
2687 if (g.FrameCountEnded != g.FrameCount)
2688 ImGui::EndFrame();
2689 g.FrameCountRendered = g.FrameCount;
2690
2691 // Skip render altogether if alpha is 0.0
2692 // Note that vertex buffers have been created and are wasted, so it is best practice that you don't create windows in the first place, or consistently respond to Begin() returning false.
2693 if (g.Style.Alpha > 0.0f) {
2694 // Gather windows to render
2695 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0;
2696 for (int i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++)
2697 g.RenderDrawLists[i].resize(0);
2698 for (int i = 0; i != g.Windows.Size; i++) {
2699 ImGuiWindow* window = g.Windows[i];
2700 if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0) {
2701 // FIXME: Generalize this with a proper layering system so e.g. user can draw in specific layers, below text, ..
2702 g.IO.MetricsActiveWindows++;
2703 if (window->Flags & ImGuiWindowFlags_Popup)
2704 AddWindowToRenderList(g.RenderDrawLists[1], window);
2705 else if (window->Flags & ImGuiWindowFlags_Tooltip)
2706 AddWindowToRenderList(g.RenderDrawLists[2], window);
2707 else
2708 AddWindowToRenderList(g.RenderDrawLists[0], window);
2709 }
2710 }
2711
2712 // Flatten layers
2713 int n = g.RenderDrawLists[0].Size;
2714 int flattened_size = n;
2715 for (int i = 1; i < IM_ARRAYSIZE(g.RenderDrawLists); i++)
2716 flattened_size += g.RenderDrawLists[i].Size;
2717 g.RenderDrawLists[0].resize(flattened_size);
2718 for (int i = 1; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) {
2719 ImVector<ImDrawList*>& layer = g.RenderDrawLists[i];
2720 if (layer.empty())
2721 continue;
2722 memcpy(&g.RenderDrawLists[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
2723 n += layer.Size;
2724 }
2725
2726 // Draw software mouse cursor if requested
2727 if (g.IO.MouseDrawCursor) {
2728 const ImGuiMouseCursorData& cursor_data = g.MouseCursorData[g.MouseCursor];
2729 const ImVec2 pos = g.IO.MousePos - cursor_data.HotOffset;
2730 const ImVec2 size = cursor_data.Size;
2731 const ImTextureID tex_id = g.IO.Fonts->TexID;
2732 g.OverlayDrawList.PushTextureID(tex_id);
2733 g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(1, 0), pos + ImVec2(1, 0) + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0, 0, 0, 48)); // Shadow
2734 g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(2, 0), pos + ImVec2(2, 0) + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0, 0, 0, 48)); // Shadow
2735 g.OverlayDrawList.AddImage(tex_id, pos, pos + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0, 0, 0, 255)); // Black border
2736 g.OverlayDrawList.AddImage(tex_id, pos, pos + size, cursor_data.TexUvMin[0], cursor_data.TexUvMax[0], IM_COL32(255, 255, 255, 255)); // White fill
2737 g.OverlayDrawList.PopTextureID();
2738 }
2739 if (!g.OverlayDrawList.VtxBuffer.empty())
2740 AddDrawListToRenderList(g.RenderDrawLists[0], &g.OverlayDrawList);
2741
2742 // Setup draw data
2743 g.RenderDrawData.Valid = true;
2744 g.RenderDrawData.CmdLists = (g.RenderDrawLists[0].Size > 0) ? &g.RenderDrawLists[0][0] : NULL;
2745 g.RenderDrawData.CmdListsCount = g.RenderDrawLists[0].Size;
2746 g.RenderDrawData.TotalVtxCount = g.IO.MetricsRenderVertices;
2747 g.RenderDrawData.TotalIdxCount = g.IO.MetricsRenderIndices;
2748
2749 // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData()
2750 if (g.RenderDrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL)
2751 g.IO.RenderDrawListsFn(&g.RenderDrawData);
2752 }
2753}
2754
2755const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) {
2756 const char* text_display_end = text;
2757 if (!text_end)
2758 text_end = (const char*)-1;
2759
2760 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
2761 text_display_end++;
2762 return text_display_end;
2763}
2764
2765// Pass text data straight to log (without being displayed)
2766void ImGui::LogText(const char* fmt, ...) {
2767 ImGuiContext& g = *GImGui;
2768 if (!g.LogEnabled)
2769 return;
2770
2771 va_list args;
2772 va_start(args, fmt);
2773 if (g.LogFile) {
2774 vfprintf(g.LogFile, fmt, args);
2775 }
2776 else {
2777 g.LogClipboard->appendv(fmt, args);
2778 }
2779 va_end(args);
2780}
2781
2782// Internal version that takes a position to decide on newline placement and pad items according to their depth.
2783// We split text into individual lines to add current tree level padding
2784static void LogRenderedText(const ImVec2 & ref_pos, const char* text, const char* text_end) {
2785 ImGuiContext& g = *GImGui;
2786 ImGuiWindow* window = ImGui::GetCurrentWindowRead();
2787
2788 if (!text_end)
2789 text_end = ImGui::FindRenderedTextEnd(text, text_end);
2790
2791 const bool log_new_line = ref_pos.y > window->DC.LogLinePosY + 1;
2792 window->DC.LogLinePosY = ref_pos.y;
2793
2794 const char* text_remaining = text;
2795 if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
2796 g.LogStartDepth = window->DC.TreeDepth;
2797 const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
2798 for (;;) {
2799 // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
2800 const char* line_end = text_remaining;
2801 while (line_end < text_end)
2802 if (*line_end == '\n')
2803 break;
2804 else
2805 line_end++;
2806 if (line_end >= text_end)
2807 line_end = NULL;
2808
2809 const bool is_first_line = (text == text_remaining);
2810 bool is_last_line = false;
2811 if (line_end == NULL) {
2812 is_last_line = true;
2813 line_end = text_end;
2814 }
2815 if (line_end != NULL && !(is_last_line && (line_end - text_remaining) == 0)) {
2816 const int char_count = (int)(line_end - text_remaining);
2817 if (log_new_line || !is_first_line)
2818 ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth * 4, "", char_count, text_remaining);
2819 else
2820 ImGui::LogText(" %.*s", char_count, text_remaining);
2821 }
2822
2823 if (is_last_line)
2824 break;
2825 text_remaining = line_end + 1;
2826 }
2827}
2828
2829// Internal ImGui functions to render text
2830// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
2831void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) {
2832 ImGuiContext& g = *GImGui;
2833 ImGuiWindow* window = GetCurrentWindow();
2834
2835 // Hide anything after a '##' string
2836 const char* text_display_end;
2837 if (hide_text_after_hash) {
2838 text_display_end = FindRenderedTextEnd(text, text_end);
2839 }
2840 else {
2841 if (!text_end)
2842 text_end = text + strlen(text); // FIXME-OPT
2843 text_display_end = text_end;
2844 }
2845
2846 const int text_len = (int)(text_display_end - text);
2847 if (text_len > 0) {
2848 window->DrawList->AddText(g.Font, g.FontSize, ImVec2(pos.x + 1, pos.y + 1), GetColorU32(ImVec4(0, 0, 0, 1)), text, text_display_end);
2849 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
2850 if (g.LogEnabled)
2851 LogRenderedText(pos, text, text_display_end);
2852 }
2853}
2854
2855void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) {
2856 ImGuiContext& g = *GImGui;
2857 ImGuiWindow* window = GetCurrentWindow();
2858
2859 if (!text_end)
2860 text_end = text + strlen(text); // FIXME-OPT
2861
2862 const int text_len = (int)(text_end - text);
2863 if (text_len > 0) {
2864 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
2865 if (g.LogEnabled)
2866 LogRenderedText(pos, text, text_end);
2867 }
2868}
2869
2870// Default clip_rect uses (pos_min,pos_max)
2871// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)
2872void 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) {
2873 // Hide anything after a '##' string
2874 const char* text_display_end = FindRenderedTextEnd(text, text_end);
2875 const int text_len = (int)(text_display_end - text);
2876 if (text_len == 0)
2877 return;
2878
2879 ImGuiContext & g = *GImGui;
2880 ImGuiWindow * window = GetCurrentWindow();
2881
2882 // Perform CPU side clipping for single clipped element to avoid using scissor state
2883 ImVec2 pos = pos_min;
2884 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
2885
2886 const ImVec2 * clip_min = clip_rect ? &clip_rect->Min : &pos_min;
2887 const ImVec2 * clip_max = clip_rect ? &clip_rect->Max : &pos_max;
2888 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
2889 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
2890 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
2891
2892 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
2893 if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
2894 if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
2895
2896 // Render
2897 if (need_clipping) {
2898 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
2899 window->DrawList->AddText(g.Font, g.FontSize, ImVec2(pos.x + 1, pos.y + 1), GetColorU32(ImVec4(0, 0, 0, 1)), text, text_display_end, 0.0f, &fine_clip_rect);
2900 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
2901 }
2902 else {
2903 window->DrawList->AddText(g.Font, g.FontSize, ImVec2(pos.x + 1, pos.y + 1), GetColorU32(ImVec4(0, 0, 0, 1)), text, text_display_end, 0.0f, NULL);
2904 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
2905 }
2906 if (g.LogEnabled)
2907 LogRenderedText(pos, text, text_display_end);
2908}
2909
2910// Render a rectangle shaped with optional rounding and borders
2911void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) {
2912 ImGuiWindow* window = GetCurrentWindow();
2913
2914 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
2915 if (border && (window->Flags & ImGuiWindowFlags_ShowBorders)) {
2916 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding);
2917 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding);
2918 }
2919}
2920
2921void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) {
2922 ImGuiWindow* window = GetCurrentWindow();
2923 if (window->Flags & ImGuiWindowFlags_ShowBorders) {
2924 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding);
2925 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding);
2926 }
2927}
2928
2929// Render a triangle to denote expanded/collapsed state
2930void ImGui::RenderCollapseTriangle(ImVec2 p_min, bool is_open, float scale) {
2931 ImGuiContext& g = *GImGui;
2932 ImGuiWindow* window = GetCurrentWindow();
2933
2934 const float h = g.FontSize * 1.00f;
2935 const float r = h * 0.40f * scale;
2936 ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale);
2937
2938 ImVec2 a, b, c;
2939 if (is_open) {
2940 /*a = center + ImVec2( 1, 0 )*r;
2941 b = center + ImVec2( -0.500f, 0.866f )*r;
2942 c = center + ImVec2( -0.500f, -0.866f )*r;*/
2943 center.y += r * 0.25f;
2944 a = center - ImVec2(0, 1) * r;
2945 b = center - ImVec2(-0.866f, -0.5f) * r;
2946 c = center - ImVec2(0.866f, -0.5f) * r;
2947 }
2948 else {
2949 center.y -= r * 0.25f;
2950 a = center + ImVec2(0, 1) * r;
2951 b = center + ImVec2(-0.866f, -0.5f) * r;
2952 c = center + ImVec2(0.866f, -0.5f) * r;
2953 }
2954
2955 window->DrawList->AddTriangleFilled(a, b, c, GetColorU32(ImGuiCol_Text));
2956}
2957
2958void ImGui::RenderBullet(ImVec2 pos) {
2959 ImGuiWindow* window = GetCurrentWindow();
2960 window->DrawList->AddCircleFilled(pos, GImGui->FontSize * 0.20f, GetColorU32(ImGuiCol_Text), 8);
2961}
2962
2963void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col) {
2964 ImGuiContext& g = *GImGui;
2965 ImGuiWindow* window = GetCurrentWindow();
2966 float start_x = (float)(int)(g.FontSize * 0.307f + 0.5f);
2967 float rem_third = (float)(int)((g.FontSize - start_x) / 3.0f);
2968 float bx = pos.x + 0.5f + start_x + rem_third;
2969 float by = pos.y - 1.0f + (float)(int)(g.Font->Ascent * (g.FontSize / g.Font->FontSize) + 0.5f) + (float)(int)(g.Font->DisplayOffset.y);
2970 window->DrawList->PathLineTo(ImVec2(bx - rem_third, by - rem_third));
2971 window->DrawList->PathLineTo(ImVec2(bx, by));
2972 window->DrawList->PathLineTo(ImVec2(bx + rem_third * 2, by - rem_third * 2));
2973 window->DrawList->PathStroke(col, false);
2974}
2975
2976// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
2977// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize)
2978ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) {
2979 ImGuiContext& g = *GImGui;
2980
2981 const char* text_display_end;
2982 if (hide_text_after_double_hash)
2983 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
2984 else
2985 text_display_end = text_end;
2986
2987 ImFont* font = g.Font;
2988 const float font_size = g.FontSize;
2989 if (text == text_display_end)
2990 return ImVec2(0.0f, font_size);
2991 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
2992
2993 // Cancel out character spacing for the last character of a line (it is baked into glyph->XAdvance field)
2994 const float font_scale = font_size / font->FontSize;
2995 const float character_spacing_x = 1.0f * font_scale;
2996 if (text_size.x > 0.0f)
2997 text_size.x -= character_spacing_x;
2998 text_size.x = (float)(int)(text_size.x + 0.95f);
2999
3000 return text_size;
3001}
3002
3003// Helper to calculate coarse clipping of large list of evenly sized items.
3004// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
3005// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX
3006void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) {
3007 ImGuiContext& g = *GImGui;
3008 ImGuiWindow* window = GetCurrentWindowRead();
3009 if (g.LogEnabled) {
3010 // If logging is active, do not perform any clipping
3011 *out_items_display_start = 0;
3012 *out_items_display_end = items_count;
3013 return;
3014 }
3015 if (window->SkipItems) {
3016 *out_items_display_start = *out_items_display_end = 0;
3017 return;
3018 }
3019
3020 const ImVec2 pos = window->DC.CursorPos;
3021 int start = (int)((window->ClipRect.Min.y - pos.y) / items_height);
3022 int end = (int)((window->ClipRect.Max.y - pos.y) / items_height);
3023 start = ImClamp(start, 0, items_count);
3024 end = ImClamp(end + 1, start, items_count);
3025 *out_items_display_start = start;
3026 *out_items_display_end = end;
3027}
3028
3029// Find window given position, search front-to-back
3030// 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.
3031static ImGuiWindow * FindHoveredWindow(ImVec2 pos, bool excluding_childs) {
3032 ImGuiContext& g = *GImGui;
3033 for (int i = g.Windows.Size - 1; i >= 0; i--) {
3034 ImGuiWindow* window = g.Windows[i];
3035 if (!window->Active)
3036 continue;
3037 if (window->Flags & ImGuiWindowFlags_NoInputs)
3038 continue;
3039 if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0)
3040 continue;
3041
3042 // Using the clipped AABB so a child window will typically be clipped by its parent.
3043 ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding);
3044 if (bb.Contains(pos))
3045 return window;
3046 }
3047 return NULL;
3048}
3049
3050// Test if mouse cursor is hovering given rectangle
3051// NB- Rectangle is clipped by our current clip setting
3052// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
3053bool ImGui::IsMouseHoveringRect(const ImVec2 & r_min, const ImVec2 & r_max, bool clip) {
3054 ImGuiContext& g = *GImGui;
3055 ImGuiWindow* window = GetCurrentWindowRead();
3056
3057 // Clip
3058 ImRect rect_clipped(r_min, r_max);
3059 if (clip)
3060 rect_clipped.ClipWith(window->ClipRect);
3061
3062 // Expand for touch input
3063 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
3064 return rect_for_touch.Contains(g.IO.MousePos);
3065}
3066
3067bool ImGui::IsAnyWindowHovered() {
3068 ImGuiContext& g = *GImGui;
3069 return g.HoveredWindow != NULL;
3070}
3071
3072static bool IsKeyPressedMap(ImGuiKey key, bool repeat) {
3073 const int key_index = GImGui->IO.KeyMap[key];
3074 return ImGui::IsKeyPressed(key_index, repeat);
3075}
3076
3077int ImGui::GetKeyIndex(ImGuiKey imgui_key) {
3078 IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
3079 return GImGui->IO.KeyMap[imgui_key];
3080}
3081
3082// Note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]!
3083bool ImGui::IsKeyDown(int user_key_index) {
3084 if (user_key_index < 0) return false;
3085 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown));
3086 return GImGui->IO.KeysDown[user_key_index];
3087}
3088
3089bool ImGui::IsKeyPressed(int user_key_index, bool repeat) {
3090 ImGuiContext& g = *GImGui;
3091 if (user_key_index < 0) return false;
3092 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
3093 const float t = g.IO.KeysDownDuration[user_key_index];
3094 if (t == 0.0f)
3095 return true;
3096
3097 if (repeat && t > g.IO.KeyRepeatDelay) {
3098 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate;
3099 if ((fmodf(t - delay, rate) > rate * 0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate * 0.5f))
3100 return true;
3101 }
3102 return false;
3103}
3104
3105bool ImGui::IsKeyReleased(int user_key_index) {
3106 ImGuiContext& g = *GImGui;
3107 if (user_key_index < 0) return false;
3108 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
3109 if (g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index])
3110 return true;
3111 return false;
3112}
3113
3114bool ImGui::IsMouseDown(int button) {
3115 ImGuiContext& g = *GImGui;
3116 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3117 return g.IO.MouseDown[button];
3118}
3119
3120bool ImGui::IsMouseClicked(int button, bool repeat) {
3121 ImGuiContext& g = *GImGui;
3122 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3123 const float t = g.IO.MouseDownDuration[button];
3124 if (t == 0.0f)
3125 return true;
3126
3127 if (repeat && t > g.IO.KeyRepeatDelay) {
3128 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate;
3129 if ((fmodf(t - delay, rate) > rate * 0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate * 0.5f))
3130 return true;
3131 }
3132
3133 return false;
3134}
3135
3136bool ImGui::IsMouseReleased(int button) {
3137 ImGuiContext& g = *GImGui;
3138 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3139 return g.IO.MouseReleased[button];
3140}
3141
3142bool ImGui::IsMouseDoubleClicked(int button) {
3143 ImGuiContext& g = *GImGui;
3144 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3145 return g.IO.MouseDoubleClicked[button];
3146}
3147
3148bool ImGui::IsMouseDragging(int button, float lock_threshold) {
3149 ImGuiContext& g = *GImGui;
3150 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3151 if (!g.IO.MouseDown[button])
3152 return false;
3153 if (lock_threshold < 0.0f)
3154 lock_threshold = g.IO.MouseDragThreshold;
3155 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
3156}
3157
3158ImVec2 ImGui::GetMousePos() {
3159 return GImGui->IO.MousePos;
3160}
3161
3162// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
3163ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() {
3164 ImGuiContext& g = *GImGui;
3165 if (g.CurrentPopupStack.Size > 0)
3166 return g.OpenPopupStack[g.CurrentPopupStack.Size - 1].MousePosOnOpen;
3167 return g.IO.MousePos;
3168}
3169
3170ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) {
3171 ImGuiContext& g = *GImGui;
3172 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3173 if (lock_threshold < 0.0f)
3174 lock_threshold = g.IO.MouseDragThreshold;
3175 if (g.IO.MouseDown[button])
3176 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
3177 return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment).
3178 return ImVec2(0.0f, 0.0f);
3179}
3180
3181void ImGui::ResetMouseDragDelta(int button) {
3182 ImGuiContext& g = *GImGui;
3183 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
3184 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
3185 g.IO.MouseClickedPos[button] = g.IO.MousePos;
3186}
3187
3188ImGuiMouseCursor ImGui::GetMouseCursor() {
3189 return GImGui->MouseCursor;
3190}
3191
3192void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) {
3193 GImGui->MouseCursor = cursor_type;
3194}
3195
3196void ImGui::CaptureKeyboardFromApp(bool capture) {
3197 GImGui->CaptureKeyboardNextFrame = capture ? 1 : 0;
3198}
3199
3200void ImGui::CaptureMouseFromApp(bool capture) {
3201 GImGui->CaptureMouseNextFrame = capture ? 1 : 0;
3202}
3203
3204bool ImGui::IsItemHovered() {
3205 ImGuiWindow* window = GetCurrentWindowRead();
3206 return window->DC.LastItemHoveredAndUsable;
3207}
3208
3209bool ImGui::IsItemRectHovered() {
3210 ImGuiWindow* window = GetCurrentWindowRead();
3211 return window->DC.LastItemHoveredRect;
3212}
3213
3214bool ImGui::IsItemActive() {
3215 ImGuiContext& g = *GImGui;
3216 if (g.ActiveId) {
3217 ImGuiWindow* window = GetCurrentWindowRead();
3218 return g.ActiveId == window->DC.LastItemId;
3219 }
3220 return false;
3221}
3222
3223bool ImGui::IsItemClicked(int mouse_button) {
3224 return IsMouseClicked(mouse_button) && IsItemHovered();
3225}
3226
3227bool ImGui::IsAnyItemHovered() {
3228 return GImGui->HoveredId != 0 || GImGui->HoveredIdPreviousFrame != 0;
3229}
3230
3231bool ImGui::IsAnyItemActive() {
3232 return GImGui->ActiveId != 0;
3233}
3234
3235bool ImGui::IsItemVisible() {
3236 ImGuiWindow* window = GetCurrentWindowRead();
3237 return window->ClipRect.Overlaps(window->DC.LastItemRect);
3238}
3239
3240// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
3241void ImGui::SetItemAllowOverlap() {
3242 ImGuiContext& g = *GImGui;
3243 if (g.HoveredId == g.CurrentWindow->DC.LastItemId)
3244 g.HoveredIdAllowOverlap = true;
3245 if (g.ActiveId == g.CurrentWindow->DC.LastItemId)
3246 g.ActiveIdAllowOverlap = true;
3247}
3248
3249ImVec2 ImGui::GetItemRectMin() {
3250 ImGuiWindow* window = GetCurrentWindowRead();
3251 return window->DC.LastItemRect.Min;
3252}
3253
3254ImVec2 ImGui::GetItemRectMax() {
3255 ImGuiWindow* window = GetCurrentWindowRead();
3256 return window->DC.LastItemRect.Max;
3257}
3258
3259ImVec2 ImGui::GetItemRectSize() {
3260 ImGuiWindow* window = GetCurrentWindowRead();
3261 return window->DC.LastItemRect.GetSize();
3262}
3263
3264ImVec2 ImGui::CalcItemRectClosestPoint(const ImVec2 & pos, bool on_edge, float outward) {
3265 ImGuiWindow* window = GetCurrentWindowRead();
3266 ImRect rect = window->DC.LastItemRect;
3267 rect.Expand(outward);
3268 return rect.GetClosestPoint(pos, on_edge);
3269}
3270
3271static ImRect GetVisibleRect() {
3272 ImGuiContext& g = *GImGui;
3273 if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y)
3274 return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax);
3275 return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
3276}
3277
3278// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first.
3279static void BeginTooltipEx(bool override_previous_tooltip) {
3280 ImGuiContext& g = *GImGui;
3281 char window_name[16];
3282 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip%02d", g.TooltipOverrideCount);
3283 if (override_previous_tooltip)
3284 if (ImGuiWindow * window = ImGui::FindWindowByName(window_name))
3285 if (window->Active) {
3286 // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one.
3287 window->HiddenFrames = 1;
3288 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip%02d", ++g.TooltipOverrideCount);
3289 }
3290 ImGui::Begin(window_name, NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize);
3291}
3292
3293void ImGui::SetTooltipV(const char* fmt, va_list args) {
3294 BeginTooltipEx(true);
3295 TextV(fmt, args);
3296 EndTooltip();
3297}
3298
3299void ImGui::SetTooltip(const char* fmt, ...) {
3300 va_list args;
3301 va_start(args, fmt);
3302 SetTooltipV(fmt, args);
3303 va_end(args);
3304}
3305
3306void ImGui::BeginTooltip() {
3307 BeginTooltipEx(false);
3308}
3309
3310void ImGui::EndTooltip() {
3311 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
3312 ImGui::End();
3313}
3314
3315// Mark popup as open (toggle toward open state).
3316// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
3317// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
3318// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
3319void ImGui::OpenPopupEx(ImGuiID id, bool reopen_existing) {
3320 ImGuiContext& g = *GImGui;
3321 ImGuiWindow* window = g.CurrentWindow;
3322 int current_stack_size = g.CurrentPopupStack.Size;
3323 ImGuiPopupRef popup_ref = ImGuiPopupRef(id, window, window->GetID("##menus"), g.IO.MousePos); // Tagged as new ref because constructor sets Window to NULL (we are passing the ParentWindow info here)
3324 if (g.OpenPopupStack.Size < current_stack_size + 1)
3325 g.OpenPopupStack.push_back(popup_ref);
3326 else if (reopen_existing || g.OpenPopupStack[current_stack_size].PopupId != id) {
3327 g.OpenPopupStack.resize(current_stack_size + 1);
3328 g.OpenPopupStack[current_stack_size] = popup_ref;
3329 }
3330}
3331
3332void ImGui::OpenPopup(const char* str_id) {
3333 ImGuiContext& g = *GImGui;
3334 OpenPopupEx(g.CurrentWindow->GetID(str_id), false);
3335}
3336
3337static void CloseInactivePopups() {
3338 ImGuiContext& g = *GImGui;
3339 if (g.OpenPopupStack.empty())
3340 return;
3341
3342 // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
3343 // Don't close our own child popup windows
3344 int n = 0;
3345 if (g.NavWindow) {
3346 for (n = 0; n < g.OpenPopupStack.Size; n++) {
3347 ImGuiPopupRef& popup = g.OpenPopupStack[n];
3348 if (!popup.Window)
3349 continue;
3350 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
3351 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
3352 continue;
3353
3354 bool has_focus = false;
3355 for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
3356 has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == g.NavWindow->RootWindow);
3357 if (!has_focus)
3358 break;
3359 }
3360 }
3361 if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a useful breakpoint on the line below
3362 g.OpenPopupStack.resize(n);
3363}
3364
3365static ImGuiWindow * GetFrontMostModalRootWindow() {
3366 ImGuiContext& g = *GImGui;
3367 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
3368 if (ImGuiWindow * front_most_popup = g.OpenPopupStack.Data[n].Window)
3369 if (front_most_popup->Flags & ImGuiWindowFlags_Modal)
3370 return front_most_popup;
3371 return NULL;
3372}
3373
3374static void ClosePopupToLevel(int remaining) {
3375 ImGuiContext& g = *GImGui;
3376 if (remaining > 0)
3377 ImGui::FocusWindow(g.OpenPopupStack[remaining - 1].Window);
3378 else
3379 ImGui::FocusWindow(g.OpenPopupStack[0].ParentWindow);
3380 g.OpenPopupStack.resize(remaining);
3381}
3382
3383static void ClosePopup(ImGuiID id) {
3384 if (!ImGui::IsPopupOpen(id))
3385 return;
3386 ImGuiContext& g = *GImGui;
3387 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
3388}
3389
3390// Close the popup we have begin-ed into.
3391void ImGui::CloseCurrentPopup() {
3392 ImGuiContext& g = *GImGui;
3393 int popup_idx = g.CurrentPopupStack.Size - 1;
3394 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
3395 return;
3396 while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
3397 popup_idx--;
3398 ClosePopupToLevel(popup_idx);
3399}
3400
3401static inline void ClearSetNextWindowData() {
3402 // FIXME-OPT
3403 ImGuiContext& g = *GImGui;
3404 g.SetNextWindowPosCond = g.SetNextWindowSizeCond = g.SetNextWindowContentSizeCond = g.SetNextWindowCollapsedCond = 0;
3405 g.SetNextWindowSizeConstraint = g.SetNextWindowFocus = false;
3406}
3407
3408static bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) {
3409 ImGuiContext& g = *GImGui;
3410 ImGuiWindow* window = g.CurrentWindow;
3411 if (!ImGui::IsPopupOpen(id)) {
3412 ClearSetNextWindowData(); // We behave like Begin() and need to consume those values
3413 return false;
3414 }
3415
3416 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
3417 ImGuiWindowFlags flags = extra_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize;
3418
3419 char name[20];
3420 if (flags & ImGuiWindowFlags_ChildMenu)
3421 ImFormatString(name, IM_ARRAYSIZE(name), "##menu_%d", g.CurrentPopupStack.Size); // Recycle windows based on depth
3422 else
3423 ImFormatString(name, IM_ARRAYSIZE(name), "##popup_%08x", id); // Not recycling, so we can close/open during the same frame
3424
3425 bool is_open = ImGui::Begin(name, NULL, flags);
3426 if (!(window->Flags & ImGuiWindowFlags_ShowBorders))
3427 g.CurrentWindow->Flags &= ~ImGuiWindowFlags_ShowBorders;
3428 if (!is_open) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
3429 ImGui::EndPopup();
3430
3431 return is_open;
3432}
3433
3434bool ImGui::BeginPopup(const char* str_id) {
3435 ImGuiContext& g = *GImGui;
3436 if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
3437 {
3438 ClearSetNextWindowData(); // We behave like Begin() and need to consume those values
3439 return false;
3440 }
3441 return BeginPopupEx(g.CurrentWindow->GetID(str_id), ImGuiWindowFlags_ShowBorders);
3442}
3443
3444bool ImGui::IsPopupOpen(ImGuiID id) {
3445 ImGuiContext& g = *GImGui;
3446 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size&& g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
3447}
3448
3449bool ImGui::IsPopupOpen(const char* str_id) {
3450 ImGuiContext& g = *GImGui;
3451 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size&& g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
3452}
3453
3454bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags extra_flags) {
3455 ImGuiContext& g = *GImGui;
3456 ImGuiWindow* window = g.CurrentWindow;
3457 const ImGuiID id = window->GetID(name);
3458 if (!IsPopupOpen(id)) {
3459 ClearSetNextWindowData(); // We behave like Begin() and need to consume those values
3460 return false;
3461 }
3462
3463 ImGuiWindowFlags flags = extra_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings;
3464 bool is_open = ImGui::Begin(name, p_open, flags);
3465 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
3466 {
3467 ImGui::EndPopup();
3468 if (is_open)
3469 ClosePopup(id);
3470 return false;
3471 }
3472
3473 return is_open;
3474}
3475
3476void ImGui::EndPopup() {
3477 ImGuiWindow* window = GetCurrentWindow();
3478 IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
3479 IM_ASSERT(GImGui->CurrentPopupStack.Size > 0);
3480 ImGui::End();
3481 if (!(window->Flags & ImGuiWindowFlags_Modal))
3482 ImGui::PopStyleVar();
3483}
3484
3485// This is a helper to handle the most simple case of associating one named popup to one given widget.
3486// 1. If you have many possible popups (for different "instances" of a same widget, or for wholly different widgets), you may be better off handling
3487// this yourself so you can store data relative to the widget that opened the popup instead of choosing different popup identifiers.
3488// 2. If you want right-clicking on the same item to reopen the popup at new location, use the same code replacing IsItemHovered() with IsItemRectHovered()
3489// and passing true to the OpenPopupEx().
3490// Because: hovering an item in a window below the popup won't normally trigger is hovering behavior/coloring. The pattern of ignoring the fact that
3491// the item can be interacted with (because it is blocked by the active popup) may useful in some situation when e.g. large canvas as one item, content of menu
3492// driven by click position.
3493bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) {
3494 if (IsItemHovered() && IsMouseClicked(mouse_button))
3495 OpenPopupEx(GImGui->CurrentWindow->GetID(str_id), false);
3496 return BeginPopup(str_id);
3497}
3498
3499bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) {
3500 if (!str_id)
3501 str_id = "window_context";
3502 if (IsMouseHoveringWindow() && IsMouseClicked(mouse_button))
3503 if (also_over_items || !IsAnyItemHovered())
3504 OpenPopupEx(GImGui->CurrentWindow->GetID(str_id), true);
3505 return BeginPopup(str_id);
3506}
3507
3508bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) {
3509 if (!str_id)
3510 str_id = "void_context";
3511 if (!IsMouseHoveringAnyWindow() && IsMouseClicked(mouse_button))
3512 OpenPopupEx(GImGui->CurrentWindow->GetID(str_id), true);
3513 return BeginPopup(str_id);
3514}
3515
3516static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2 & size_arg, bool border, ImGuiWindowFlags extra_flags) {
3517 ImGuiWindow* parent_window = ImGui::GetCurrentWindow();
3518 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow;
3519
3520 const ImVec2 content_avail = ImGui::GetContentRegionAvail();
3521 ImVec2 size = ImFloor(size_arg);
3522 const int auto_fit_axises = ((size.x == 0.0f) ? 0x01 : 0x00) | ((size.y == 0.0f) ? 0x02 : 0x00);
3523 if (size.x <= 0.0f)
3524 size.x = ImMax(content_avail.x, 4.0f) - fabsf(size.x); // Arbitrary minimum zero-ish child size of 4.0f (0.0f causing too much issues)
3525 if (size.y <= 0.0f)
3526 size.y = ImMax(content_avail.y, 4.0f) - fabsf(size.y);
3527
3528 if (border)
3529 flags |= ImGuiWindowFlags_ShowBorders;
3530 flags |= extra_flags;
3531
3532 char title[256];
3533 if (name)
3534 ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s.%08X", parent_window->Name, name, id);
3535 else
3536 ImFormatString(title, IM_ARRAYSIZE(title), "%s.%08X", parent_window->Name, id);
3537
3538 bool ret = ImGui::Begin(title, NULL, size, -1.0f, flags);
3539 ImGuiWindow * child_window = ImGui::GetCurrentWindow();
3540 child_window->AutoFitChildAxises = auto_fit_axises;
3541 if (!(parent_window->Flags & ImGuiWindowFlags_ShowBorders))
3542 child_window->Flags &= ~ImGuiWindowFlags_ShowBorders;
3543
3544 return ret;
3545}
3546
3547bool ImGui::BeginChild(const char* str_id, const ImVec2 & size_arg, bool border, ImGuiWindowFlags extra_flags) {
3548 ImGuiWindow* window = GetCurrentWindow();
3549 return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
3550}
3551
3552bool ImGui::BeginChild(ImGuiID id, const ImVec2 & size_arg, bool border, ImGuiWindowFlags extra_flags) {
3553 return BeginChildEx(NULL, id, size_arg, border, extra_flags);
3554}
3555
3556void ImGui::EndChild(bool shadow) {
3557 ImGuiWindow* window = GetCurrentWindow();
3558 ImVec2 min_top, max_top, min_bot, max_bot;
3559
3560 if (window->ScrollbarY) {
3561 auto min_top = ImGui::GetWindowPos();
3562 auto max_top = min_top + ImVec2(GetWindowSize().x, 15);
3563
3564 auto min_bot = min_top + ImVec2(0, GetWindowSize().y - 15);
3565 auto max_bot = min_bot + ImVec2(GetWindowSize().x, 15);
3566
3567 auto arrow = [window](int x, int y, bool normal, float clr = 0.59f) {
3568 if (normal) {
3569 for (auto i = 5; i >= 3; --i) {
3570 auto offset = 5 - i;
3571
3572 window->DrawList->AddLine(ImVec2(x + offset, y + offset), ImVec2(x + offset + nigga(i - offset, 0, 5), y + offset), ImColor(clr, clr, clr));
3573 }
3574 }
3575 else {
3576 window->DrawList->AddLine(ImVec2(x + 2, y), ImVec2(x + 3, y), ImColor(clr, clr, clr));
3577 window->DrawList->AddLine(ImVec2(x + 1, y + 1), ImVec2(x + 4, y + 1), ImColor(clr, clr, clr));
3578 window->DrawList->AddLine(ImVec2(x, y + 2), ImVec2(x + 5, y + 2), ImColor(clr, clr, clr));
3579 }
3580 };
3581
3582 if (window->Scroll.y > 1.0f) {
3583 window->DrawList->AddRectFilledMultiColor(min_top - ImVec2(7, 0), max_top - ImVec2(1, 0), ImColor(0, 0, 0, 100), ImColor(0, 0, 0, 100), ImColor(0, 0, 0, 0), ImColor(0, 0, 0, 0));
3584 arrow(max_top.x - 20, max_top.y - 8, false);
3585 }
3586 if ((GetScrollMaxY() - window->Scroll.y) > 1.f) {
3587 window->DrawList->AddRectFilledMultiColor(min_bot - ImVec2(7, 0), max_bot - ImVec2(1, 0), ImColor(0, 0, 0, 0), ImColor(0, 0, 0, 0), ImColor(0, 0, 0, 100), ImColor(0, 0, 0, 100));
3588 arrow(max_bot.x - 20, max_bot.y - 10, true);
3589 }
3590 }
3591
3592 if (shadow) {
3593 std::string window_title(window->Name);
3594 window_title = std::string(&window_title[0] + window_title.find(".") + 1);
3595 window_title[window_title.find(".")] = '\0';
3596
3597 ImVec2 text_position(window->Pos.x + 12.0f, window->Pos.y + CalcTextSize(window_title.c_str()).y - 18.0f);
3598
3599 ImVec2 line_min(text_position.x - 3.0f, window->Pos.y);
3600 ImVec2 line_max(text_position.x + CalcTextSize(window_title.c_str()).x + 3.0f, GetWindowPos().y);
3601
3602 ImRect title_bar_rect = window->TitleBarRect();
3603 ImRect clip_rect;
3604
3605 clip_rect.Min.x = ImFloor(0.5f + title_bar_rect.Min.x + ImMax(window->BorderSize, ImFloor(window->WindowPadding.x * 0.1f)));
3606 clip_rect.Min.y = ImFloor(0.5f + title_bar_rect.Max.y + window->MenuBarHeight() + window->BorderSize) - 16;
3607 clip_rect.Max.x = ImFloor(0.5f + window->Pos.x + window->Size.x - window->ScrollbarSizes.x - ImMax(window->BorderSize, ImFloor(window->WindowPadding.x * 0.1f)));
3608 clip_rect.Max.y = ImFloor(0.5f + window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->BorderSize);
3609
3610 PushClipRect(clip_rect.Min, clip_rect.Max, false);
3611
3612 ImGui::GetWindowDrawList()->AddLine(line_min, line_max, GetColorU32(ImGuiCol_ChildWindowBg));
3613
3614 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x - 1, text_position.y - 1), IM_COL32(0, 0, 0, 255), window_title.c_str());
3615 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x - 1, text_position.y), IM_COL32(0, 0, 0, 255), window_title.c_str());
3616 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x - 1, text_position.y + 1), IM_COL32(0, 0, 0, 255), window_title.c_str());
3617 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x, text_position.y - 1), IM_COL32(0, 0, 0, 255), window_title.c_str());
3618 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x, text_position.y + 1), IM_COL32(0, 0, 0, 255), window_title.c_str());
3619 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x + 1, text_position.y - 1), IM_COL32(0, 0, 0, 255), window_title.c_str());
3620 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x + 1, text_position.y), IM_COL32(0, 0, 0, 255), window_title.c_str());
3621 ImGui::GetWindowDrawList()->AddText(ImVec2(text_position.x + 1, text_position.y + 1), IM_COL32(0, 0, 0, 255), window_title.c_str());
3622 ImGui::GetWindowDrawList()->AddText(text_position, IM_COL32(255, 255, 255, 255), window_title.c_str());
3623
3624 PopClipRect();
3625 }
3626
3627 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss
3628 if ((window->Flags & ImGuiWindowFlags_ComboBox) || window->BeginCount > 1) {
3629 ImGui::End();
3630 }
3631 else {
3632 // 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.
3633 ImVec2 sz = GetWindowSize();
3634 if (window->AutoFitChildAxises & 0x01) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
3635 sz.x = ImMax(4.0f, sz.x);
3636 if (window->AutoFitChildAxises & 0x02)
3637 sz.y = ImMax(4.0f, sz.y);
3638
3639 ImGui::End();
3640
3641 ImGuiWindow* parent_window = GetCurrentWindow();
3642 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
3643 ItemSize(sz);
3644 ItemAdd(bb, NULL);
3645 }
3646}
3647
3648// Helper to create a child window / scrolling region that looks like a normal widget frame.
3649bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2 & size, ImGuiWindowFlags extra_flags) {
3650 ImGuiContext& g = *GImGui;
3651 const ImGuiStyle& style = g.Style;
3652 ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, style.Colors[ImGuiCol_FrameBg]);
3653 ImGui::PushStyleVar(ImGuiStyleVar_ChildWindowRounding, style.FrameRounding);
3654 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
3655 return ImGui::BeginChild(id, size, (g.CurrentWindow->Flags & ImGuiWindowFlags_ShowBorders) ? true : false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
3656}
3657
3658void ImGui::EndChildFrame() {
3659 ImGui::EndChild();
3660 ImGui::PopStyleVar(2);
3661 ImGui::PopStyleColor();
3662}
3663
3664// Save and compare stack sizes on Begin()/End() to detect usage errors
3665static void CheckStacksSize(ImGuiWindow * window, bool write) {
3666 // 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)
3667 ImGuiContext& g = *GImGui;
3668 int* p_backup = &window->DC.StackSizesBackup[0];
3669 { 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()
3670 { 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()
3671 { 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()
3672 { 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()
3673 { 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()
3674 { 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()
3675 IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
3676}
3677
3678static ImVec2 FindBestPopupWindowPos(const ImVec2 & base_pos, const ImVec2 & size, int* last_dir, const ImRect & r_inner) {
3679 const ImGuiStyle& style = GImGui->Style;
3680
3681 // Clamp into visible area while not overlapping the cursor. Safety padding is optional if our popup size won't fit without it.
3682 ImVec2 safe_padding = style.DisplaySafeAreaPadding;
3683 ImRect r_outer(GetVisibleRect());
3684 r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x * 2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y * 2) ? -safe_padding.y : 0.0f));
3685 ImVec2 base_pos_clamped = ImClamp(base_pos, r_outer.Min, r_outer.Max - size);
3686
3687 for (int n = (*last_dir != -1) ? -1 : 0; n < 4; n++) // Last, Right, down, up, left. (Favor last used direction).
3688 {
3689 const int dir = (n == -1) ? *last_dir : n;
3690 ImRect rect(dir == 0 ? r_inner.Max.x : r_outer.Min.x, dir == 1 ? r_inner.Max.y : r_outer.Min.y, dir == 3 ? r_inner.Min.x : r_outer.Max.x, dir == 2 ? r_inner.Min.y : r_outer.Max.y);
3691 if (rect.GetWidth() < size.x || rect.GetHeight() < size.y)
3692 continue;
3693 *last_dir = dir;
3694 return ImVec2(dir == 0 ? r_inner.Max.x : dir == 3 ? r_inner.Min.x - size.x : base_pos_clamped.x, dir == 1 ? r_inner.Max.y : dir == 2 ? r_inner.Min.y - size.y : base_pos_clamped.y);
3695 }
3696
3697 // Fallback, try to keep within display
3698 *last_dir = -1;
3699 ImVec2 pos = base_pos;
3700 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
3701 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
3702 return pos;
3703}
3704
3705ImGuiWindow * ImGui::FindWindowByName(const char* name) {
3706 // FIXME-OPT: Store sorted hashes -> pointers so we can do a bissection in a contiguous block
3707 ImGuiContext& g = *GImGui;
3708 ImGuiID id = ImHash(name, 0);
3709 for (int i = 0; i < g.Windows.Size; i++)
3710 if (g.Windows[i]->ID == id)
3711 return g.Windows[i];
3712 return NULL;
3713}
3714
3715static ImGuiWindow * CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) {
3716 ImGuiContext& g = *GImGui;
3717
3718 // Create window the first time
3719 ImGuiWindow* window = (ImGuiWindow*)ImGui::MemAlloc(sizeof(ImGuiWindow));
3720 IM_PLACEMENT_NEW(window) ImGuiWindow(name);
3721 window->Flags = flags;
3722
3723 if (flags & ImGuiWindowFlags_NoSavedSettings) {
3724 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
3725 window->Size = window->SizeFull = size;
3726 }
3727 else {
3728 // Retrieve settings from .ini file
3729 // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
3730 window->PosFloat = ImVec2(60, 60);
3731 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y);
3732
3733 ImGuiIniData* settings = FindWindowSettings(name);
3734 if (!settings) {
3735 settings = AddWindowSettings(name);
3736 }
3737 else {
3738 window->SetWindowPosAllowFlags &= ~ImGuiCond_FirstUseEver;
3739 window->SetWindowSizeAllowFlags &= ~ImGuiCond_FirstUseEver;
3740 window->SetWindowCollapsedAllowFlags &= ~ImGuiCond_FirstUseEver;
3741 }
3742
3743 if (settings->Pos.x != FLT_MAX) {
3744 window->PosFloat = settings->Pos;
3745 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y);
3746 window->Collapsed = settings->Collapsed;
3747 }
3748
3749 if (ImLengthSqr(settings->Size) > 0.00001f)
3750 size = settings->Size;
3751 window->Size = window->SizeFull = size;
3752 }
3753
3754 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) {
3755 window->AutoFitFramesX = window->AutoFitFramesY = 2;
3756 window->AutoFitOnlyGrows = false;
3757 }
3758 else {
3759 if (window->Size.x <= 0.0f)
3760 window->AutoFitFramesX = 2;
3761 if (window->Size.y <= 0.0f)
3762 window->AutoFitFramesY = 2;
3763 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
3764 }
3765
3766 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
3767 g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once
3768 else
3769 g.Windows.push_back(window);
3770 return window;
3771}
3772
3773static void ApplySizeFullWithConstraint(ImGuiWindow * window, ImVec2 new_size) {
3774 ImGuiContext& g = *GImGui;
3775 if (g.SetNextWindowSizeConstraint) {
3776 // Using -1,-1 on either X/Y axis to preserve the current size.
3777 ImRect cr = g.SetNextWindowSizeConstraintRect;
3778 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
3779 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
3780 if (g.SetNextWindowSizeConstraintCallback) {
3781 ImGuiSizeConstraintCallbackData data;
3782 data.UserData = g.SetNextWindowSizeConstraintCallbackUserData;
3783 data.Pos = window->Pos;
3784 data.CurrentSize = window->SizeFull;
3785 data.DesiredSize = new_size;
3786 g.SetNextWindowSizeConstraintCallback(&data);
3787 new_size = data.DesiredSize;
3788 }
3789 }
3790 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
3791 new_size = ImMax(new_size, g.Style.WindowMinSize);
3792 window->SizeFull = new_size;
3793}
3794
3795// Push a new ImGui window to add widgets to.
3796// - 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.
3797// - Begin/End can be called multiple times during the frame with the same window name to append content.
3798// - 'size_on_first_use' for a regular window denote the initial size for first-time creation (no saved data) and isn't that useful. Use SetNextWindowSize() prior to calling Begin() for more flexible window manipulation.
3799// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
3800// 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.
3801// - 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.
3802// - 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.
3803// - Passing non-zero 'size' is roughly equivalent to calling SetNextWindowSize(size, ImGuiCond_FirstUseEver) prior to calling Begin().
3804bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
3805 return ImGui::Begin(name, p_open, ImVec2(0.f, 0.f), -1.0f, flags);
3806}
3807
3808bool ImGui::Begin(const char* name, bool* p_open, const ImVec2 & size_on_first_use, float bg_alpha, ImGuiWindowFlags flags) {
3809 ImGuiContext& g = *GImGui;
3810 const ImGuiStyle& style = g.Style;
3811 IM_ASSERT(name != NULL); // Window name required
3812 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
3813 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
3814
3815 if (flags & ImGuiWindowFlags_NoInputs)
3816 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
3817
3818 // Find or create
3819 bool window_is_new = false;
3820 ImGuiWindow * window = FindWindowByName(name);
3821 if (!window) {
3822 window = CreateNewWindow(name, size_on_first_use, flags);
3823 window_is_new = true;
3824 }
3825
3826 const int current_frame = ImGui::GetFrameCount();
3827 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
3828 if (first_begin_of_the_frame)
3829 window->Flags = (ImGuiWindowFlags)flags;
3830 else
3831 flags = window->Flags;
3832
3833 // Add to stack
3834 ImGuiWindow * parent_window = !g.CurrentWindowStack.empty() ? g.CurrentWindowStack.back() : NULL;
3835 g.CurrentWindowStack.push_back(window);
3836 SetCurrentWindow(window);
3837 CheckStacksSize(window, true);
3838 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
3839
3840 bool window_was_active = (window->LastFrameActive == current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
3841 if (flags & ImGuiWindowFlags_Popup) {
3842 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
3843 window_was_active &= (window->PopupId == popup_ref.PopupId);
3844 window_was_active &= (window == popup_ref.Window);
3845 popup_ref.Window = window;
3846 g.CurrentPopupStack.push_back(popup_ref);
3847 window->PopupId = popup_ref.PopupId;
3848 }
3849
3850 const bool window_appearing_after_being_hidden = (window->HiddenFrames == 1);
3851
3852 // Process SetNextWindow***() calls
3853 bool window_pos_set_by_api = false, window_size_set_by_api = false;
3854 if (g.SetNextWindowPosCond) {
3855 const ImVec2 backup_cursor_pos = window->DC.CursorPos; // FIXME: not sure of the exact reason of this saving/restore anymore :( need to look into that.
3856 if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowPosAllowFlags |= ImGuiCond_Appearing;
3857 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) != 0;
3858 if (window_pos_set_by_api && ImLengthSqr(g.SetNextWindowPosVal - ImVec2(-FLT_MAX, -FLT_MAX)) < 0.001f) {
3859 window->SetWindowPosCenterWanted = true; // May be processed on the next frame if this is our first frame and we are measuring size
3860 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
3861 }
3862 else {
3863 SetWindowPos(window, g.SetNextWindowPosVal, g.SetNextWindowPosCond);
3864 }
3865 window->DC.CursorPos = backup_cursor_pos;
3866 g.SetNextWindowPosCond = 0;
3867 }
3868 if (g.SetNextWindowSizeCond) {
3869 if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowSizeAllowFlags |= ImGuiCond_Appearing;
3870 window_size_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0;
3871 SetWindowSize(window, g.SetNextWindowSizeVal, g.SetNextWindowSizeCond);
3872 g.SetNextWindowSizeCond = 0;
3873 }
3874 if (g.SetNextWindowContentSizeCond) {
3875 window->SizeContentsExplicit = g.SetNextWindowContentSizeVal;
3876 g.SetNextWindowContentSizeCond = 0;
3877 }
3878 else if (first_begin_of_the_frame) {
3879 window->SizeContentsExplicit = ImVec2(0.0f, 0.0f);
3880 }
3881 if (g.SetNextWindowCollapsedCond) {
3882 if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowCollapsedAllowFlags |= ImGuiCond_Appearing;
3883 SetWindowCollapsed(window, g.SetNextWindowCollapsedVal, g.SetNextWindowCollapsedCond);
3884 g.SetNextWindowCollapsedCond = 0;
3885 }
3886 if (g.SetNextWindowFocus) {
3887 ImGui::SetWindowFocus();
3888 g.SetNextWindowFocus = false;
3889 }
3890
3891 // Update known root window (if we are a child window, otherwise window == window->RootWindow)
3892 int root_idx, root_non_popup_idx;
3893 for (root_idx = g.CurrentWindowStack.Size - 1; root_idx > 0; root_idx--)
3894 if (!(g.CurrentWindowStack[root_idx]->Flags & ImGuiWindowFlags_ChildWindow))
3895 break;
3896 for (root_non_popup_idx = root_idx; root_non_popup_idx > 0; root_non_popup_idx--)
3897 if (!(g.CurrentWindowStack[root_non_popup_idx]->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
3898 break;
3899 window->ParentWindow = parent_window;
3900 window->RootWindow = g.CurrentWindowStack[root_idx];
3901 window->RootNonPopupWindow = g.CurrentWindowStack[root_non_popup_idx]; // This is merely for displaying the TitleBgActive color.
3902
3903 // When reusing window again multiple times a frame, just append content (don't need to setup again)
3904 if (first_begin_of_the_frame) {
3905 window->Active = true;
3906 window->OrderWithinParent = 0;
3907 window->BeginCount = 0;
3908 window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);
3909 window->LastFrameActive = current_frame;
3910 window->IDStack.resize(1);
3911
3912 // Clear draw list, setup texture, outer clipping rectangle
3913 window->DrawList->Clear();
3914 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
3915 ImRect fullscreen_rect(GetVisibleRect());
3916 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_ComboBox | ImGuiWindowFlags_Popup)))
3917 PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true);
3918 else
3919 PushClipRect(fullscreen_rect.Min, fullscreen_rect.Max, true);
3920
3921 if (!window_was_active) {
3922 // Popup first latch mouse position, will position itself when it appears next frame
3923 window->AutoPosLastDirection = -1;
3924 if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api)
3925 window->PosFloat = g.IO.MousePos;
3926 }
3927
3928 // Collapse window by double-clicking on title bar
3929 // 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
3930 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) {
3931 ImRect title_bar_rect = window->TitleBarRect();
3932 if (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) {
3933 window->Collapsed = !window->Collapsed;
3934 MarkIniSettingsDirty(window);
3935 FocusWindow(window);
3936 }
3937 }
3938 else {
3939 window->Collapsed = false;
3940 }
3941
3942 // SIZE
3943
3944 // Save contents size from last frame for auto-fitting (unless explicitly specified)
3945 window->SizeContents.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : ((window_is_new ? 0.0f : window->DC.CursorMaxPos.x - window->Pos.x) + window->Scroll.x));
3946 window->SizeContents.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : ((window_is_new ? 0.0f : window->DC.CursorMaxPos.y - window->Pos.y) + window->Scroll.y));
3947
3948 // Hide popup/tooltip window when first appearing while we measure size (because we recycle them)
3949 if (window->HiddenFrames > 0)
3950 window->HiddenFrames--;
3951 if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && !window_was_active) {
3952 window->HiddenFrames = 1;
3953 if (flags & ImGuiWindowFlags_AlwaysAutoResize) {
3954 if (!window_size_set_by_api)
3955 window->Size = window->SizeFull = ImVec2(0.f, 0.f);
3956 window->SizeContents = ImVec2(0.f, 0.f);
3957 }
3958 }
3959
3960 // Lock window padding so that altering the ShowBorders flag for children doesn't have side-effects.
3961 window->WindowPadding = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_ComboBox | ImGuiWindowFlags_Popup))) ? style.WindowPadding : style.WindowPadding;
3962
3963 // Calculate auto-fit size
3964 ImVec2 size_auto_fit;
3965 if ((flags & ImGuiWindowFlags_Tooltip) != 0) {
3966 // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose.
3967 size_auto_fit = window->SizeContents + window->WindowPadding - ImVec2(0.0f, style.ItemSpacing.y);
3968 }
3969 else {
3970 size_auto_fit = ImClamp(window->SizeContents + window->WindowPadding, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding));
3971
3972 // Handling case of auto fit window not fitting in screen on one axis, we are growing auto fit size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding.
3973 if (size_auto_fit.x < window->SizeContents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar))
3974 size_auto_fit.y += style.ScrollbarSize;
3975 if (size_auto_fit.y < window->SizeContents.y && !(flags & ImGuiWindowFlags_NoScrollbar))
3976 size_auto_fit.x += style.ScrollbarSize;
3977 size_auto_fit.y = ImMax(size_auto_fit.y - style.ItemSpacing.y, 0.0f);
3978 }
3979
3980 // Handle automatic resize
3981 if (window->Collapsed) {
3982 // We still process initial auto-fit on collapsed windows to get a window width,
3983 // But otherwise we don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
3984 if (window->AutoFitFramesX > 0)
3985 window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
3986 if (window->AutoFitFramesY > 0)
3987 window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
3988 }
3989 else {
3990 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window_size_set_by_api) {
3991 window->SizeFull = size_auto_fit;
3992 }
3993 else if ((window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) && !window_size_set_by_api) {
3994 // Auto-fit only grows during the first few frames
3995 if (window->AutoFitFramesX > 0)
3996 window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
3997 if (window->AutoFitFramesY > 0)
3998 window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
3999 MarkIniSettingsDirty(window);
4000 }
4001 }
4002
4003 // Apply minimum/maximum window size constraints and final size
4004 ApplySizeFullWithConstraint(window, window->SizeFull);
4005 window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull;
4006
4007 // POSITION
4008
4009 // Position child window
4010 if (flags & ImGuiWindowFlags_ChildWindow) {
4011 window->OrderWithinParent = parent_window->DC.ChildWindows.Size;
4012 parent_window->DC.ChildWindows.push_back(window);
4013 }
4014 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) {
4015 window->Pos = window->PosFloat = parent_window->DC.CursorPos;
4016 window->Size = window->SizeFull = size_on_first_use; // NB: argument name 'size_on_first_use' misleading here, it's really just 'size' as provided by user passed via BeginChild()->Begin().
4017 }
4018
4019 bool window_pos_center = false;
4020 window_pos_center |= (window->SetWindowPosCenterWanted && window->HiddenFrames == 0);
4021 window_pos_center |= ((flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api && window_appearing_after_being_hidden);
4022 if (window_pos_center) {
4023 // Center (any sort of window)
4024 SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, fullscreen_rect.GetCenter() - window->SizeFull * 0.5f), 0);
4025 }
4026 else if (flags & ImGuiWindowFlags_ChildMenu) {
4027 // 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.
4028 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
4029 IM_ASSERT(window_pos_set_by_api);
4030 float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value).
4031 ImRect rect_to_avoid;
4032 if (parent_window->DC.MenuBarAppending)
4033 rect_to_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight());
4034 else
4035 rect_to_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX);
4036 window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
4037 }
4038 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_appearing_after_being_hidden) {
4039 ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1);
4040 window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
4041 }
4042
4043 // Position tooltip (always follows mouse)
4044 if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api) {
4045 ImRect rect_to_avoid(g.IO.MousePos.x - 16, g.IO.MousePos.y - 8, g.IO.MousePos.x + 24, g.IO.MousePos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead?
4046 window->PosFloat = FindBestPopupWindowPos(g.IO.MousePos, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
4047 if (window->AutoPosLastDirection == -1)
4048 window->PosFloat = g.IO.MousePos + 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.
4049 }
4050
4051 // Clamp position so it stays visible
4052 if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) {
4053 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.
4054 {
4055 ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
4056 window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size;
4057 window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding);
4058 }
4059 }
4060 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y);
4061
4062 // Default item width. Make it proportional to window size if window manually resizes
4063 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
4064 window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f);
4065 else
4066 window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f);
4067
4068 // Prepare for focus requests
4069 window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter + 1)) % (window->FocusIdxAllCounter + 1);
4070 window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter + 1)) % (window->FocusIdxTabCounter + 1);
4071 window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1;
4072 window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX;
4073
4074 // Apply scrolling
4075 if (window->ScrollTarget.x < FLT_MAX) {
4076 window->Scroll.x = window->ScrollTarget.x;
4077 window->ScrollTarget.x = FLT_MAX;
4078 }
4079 if (window->ScrollTarget.y < FLT_MAX) {
4080 float center_ratio = window->ScrollTargetCenterRatio.y;
4081 window->Scroll.y = window->ScrollTarget.y - ((1.0f - center_ratio) * (window->TitleBarHeight() + window->MenuBarHeight())) - (center_ratio * (window->SizeFull.y - window->ScrollbarSizes.y));
4082 window->ScrollTarget.y = FLT_MAX;
4083 }
4084 window->Scroll = ImMax(window->Scroll, ImVec2(0.0f, 0.0f));
4085 if (!window->Collapsed && !window->SkipItems) {
4086 window->Scroll.x = ImMin(window->Scroll.x, GetScrollMaxX());
4087 window->Scroll.y = ImMin(window->Scroll.y, GetScrollMaxY());
4088 }
4089
4090 // Modal window darkens what is behind them
4091 if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow())
4092 window->DrawList->AddRectFilled(fullscreen_rect.Min, fullscreen_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio));
4093
4094 // Draw window + handle manual resize
4095 ImRect title_bar_rect = window->TitleBarRect();
4096 const float window_rounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding;
4097 if (window->Collapsed) {
4098 // Draw title bar only
4099 RenderFrame(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding);
4100 }
4101 else {
4102 ImU32 resize_col = 0;
4103 const float resize_corner_size = ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f);
4104 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && !(flags & ImGuiWindowFlags_NoResize)) {
4105 // Manual resize
4106 const ImVec2 br = window->Rect().GetBR();
4107 const ImRect resize_rect(br - ImVec2(resize_corner_size * 0.75f, resize_corner_size * 0.75f), br);
4108 const ImGuiID resize_id = window->GetID("#RESIZE");
4109 bool hovered, held;
4110 ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds);
4111 resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
4112
4113 if (hovered || held)
4114 g.MouseCursor = ImGuiMouseCursor_ResizeNWSE;
4115
4116 if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0]) {
4117 // Manual auto-fit when double-clicking
4118 ApplySizeFullWithConstraint(window, size_auto_fit);
4119 MarkIniSettingsDirty(window);
4120 ClearActiveID();
4121 }
4122 else if (held) {
4123 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
4124 ApplySizeFullWithConstraint(window, (g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize()) - window->Pos);
4125 MarkIniSettingsDirty(window);
4126 }
4127
4128 window->Size = window->SizeFull;
4129 title_bar_rect = window->TitleBarRect();
4130 }
4131
4132 // Scrollbars
4133 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > window->Size.y) && !(flags & ImGuiWindowFlags_NoScrollbar));
4134 window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > window->Size.x - (window->ScrollbarY ? style.ScrollbarSize : 0.0f) - window->WindowPadding.x) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
4135 if (window->ScrollbarX && !window->ScrollbarY)
4136 window->ScrollbarY = (window->SizeContents.y > window->Size.y + style.ItemSpacing.y - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
4137
4138 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
4139 window->BorderSize = (flags & ImGuiWindowFlags_ShowBorders) ? 1.0f : 0.0f;
4140
4141 // Window background, Default Alpha
4142 ImGuiCol bg_color_idx = ImGuiCol_WindowBg;
4143 if ((flags & ImGuiWindowFlags_ComboBox) != 0)
4144 bg_color_idx = ImGuiCol_FrameBg;
4145 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 || (flags & ImGuiWindowFlags_Popup) != 0)
4146 bg_color_idx = ImGuiCol_PopupBg;
4147 else if ((flags & ImGuiWindowFlags_ChildWindow) != 0)
4148 bg_color_idx = ImGuiCol_ChildWindowBg;
4149 ImVec4 bg_color = style.Colors[bg_color_idx]; // We don't use GetColorU32() because bg_alpha is assigned (not multiplied) below
4150 if (bg_alpha >= 0.0f)
4151 bg_color.w = bg_alpha;
4152 bg_color.w *= style.Alpha;
4153
4154 if (bg_color.w > 0.0f)
4155 window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, ColorConvertFloat4ToU32(bg_color), window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImGuiCorner_All : ImGuiCorner_BottomLeft | ImGuiCorner_BottomRight);
4156
4157 // Title bar
4158 if (!(flags & ImGuiWindowFlags_NoTitleBar))
4159 window->DrawList->AddRectFilled(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32((g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImGuiCorner_TopLeft | ImGuiCorner_TopRight);
4160
4161 // Menu bar
4162 if (flags & ImGuiWindowFlags_MenuBar) {
4163 ImRect menu_bar_rect = window->MenuBarRect();
4164 if (flags & ImGuiWindowFlags_ShowBorders)
4165 window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImVec4(0, 0, 0, 1)));
4166 window->DrawList->AddRectFilled(menu_bar_rect.GetTL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImGuiCorner_TopLeft | ImGuiCorner_TopRight);
4167 }
4168
4169 // Render resize grip
4170 // (after the input handling so we don't have a frame of latency)
4171 if (!(flags & ImGuiWindowFlags_NoResize)) {
4172 const ImVec2 br = window->Rect().GetBR();
4173 window->DrawList->PathLineTo(br + ImVec2(-resize_corner_size, -window->BorderSize));
4174 window->DrawList->PathLineTo(br + ImVec2(-window->BorderSize, -resize_corner_size));
4175 window->DrawList->PathArcToFast(ImVec2(br.x - window_rounding - window->BorderSize, br.y - window_rounding - window->BorderSize), window_rounding, 0, 3);
4176 window->DrawList->PathFillConvex(resize_col);
4177 }
4178
4179 // Borders
4180 if (flags & ImGuiWindowFlags_ShowBorders) {
4181 window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding);
4182
4183 if (flags & ImGuiWindowFlags_Popup) {
4184 window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding);
4185 }
4186 if (!(flags & ImGuiWindowFlags_NoTitleBar))
4187 window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(1, 0), title_bar_rect.GetBR() - ImVec2(1, 0), GetColorU32(ImGuiCol_Border));
4188 }
4189 }
4190
4191 // Update ContentsRegionMax. All the variable it depends on are set above in this function.
4192 window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x;
4193 window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight();
4194 window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x));
4195 window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y));
4196
4197 // Setup drawing context
4198 window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x;
4199 window->DC.GroupOffsetX = 0.0f;
4200 window->DC.ColumnsOffsetX = 0.0f;
4201 window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y);
4202 window->DC.CursorPos = window->DC.CursorStartPos;
4203 window->DC.CursorPosPrevLine = window->DC.CursorPos;
4204 window->DC.CursorMaxPos = window->DC.CursorStartPos;
4205 window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f;
4206 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
4207 window->DC.MenuBarAppending = false;
4208 window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x);
4209 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
4210 window->DC.ChildWindows.resize(0);
4211 window->DC.LayoutType = ImGuiLayoutType_Vertical;
4212 window->DC.ItemWidth = window->ItemWidthDefault;
4213 window->DC.TextWrapPos = -1.0f; // disabled
4214 window->DC.AllowKeyboardFocus = true;
4215 window->DC.ButtonRepeat = false;
4216 window->DC.ItemWidthStack.resize(0);
4217 window->DC.AllowKeyboardFocusStack.resize(0);
4218 window->DC.ButtonRepeatStack.resize(0);
4219 window->DC.TextWrapPosStack.resize(0);
4220 window->DC.ColumnsCurrent = 0;
4221 window->DC.ColumnsCount = 1;
4222 window->DC.ColumnsStartPosY = window->DC.CursorPos.y;
4223 window->DC.ColumnsStartMaxPosX = window->DC.CursorMaxPos.x;
4224 window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY = window->DC.ColumnsStartPosY;
4225 window->DC.TreeDepth = 0;
4226 window->DC.StateStorage = &window->StateStorage;
4227 window->DC.GroupStack.resize(0);
4228 window->MenuColumns.Update(3, style.ItemSpacing.x, !window_was_active);
4229
4230 if (window->AutoFitFramesX > 0)
4231 window->AutoFitFramesX--;
4232 if (window->AutoFitFramesY > 0)
4233 window->AutoFitFramesY--;
4234
4235 // New windows appears in front (we need to do that AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
4236 if (!window_was_active && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
4237 if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
4238 FocusWindow(window);
4239
4240 // Title bar
4241 if (!(flags & ImGuiWindowFlags_NoTitleBar)) {
4242 if (p_open != NULL) {
4243 const float pad = 2.0f;
4244 const float rad = (window->TitleBarHeight() - pad * 2.0f) * 0.5f;
4245 if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad))
4246 * p_open = false;
4247 }
4248
4249 const ImVec2 text_size = CalcTextSize(name, NULL, true);
4250 if (!(flags & ImGuiWindowFlags_NoCollapse))
4251 RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f);
4252
4253 ImVec2 text_min = window->Pos;
4254 ImVec2 text_max = window->Pos + ImVec2(window->Size.x, style.FramePadding.y * 2 + text_size.y);
4255 ImRect clip_rect;
4256 clip_rect.Max = ImVec2(window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x), text_max.y); // Match the size of CloseWindowButton()
4257 float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x;
4258 float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x;
4259 if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x);
4260 text_min.x += pad_left;
4261 text_max.x -= pad_right;
4262 clip_rect.Min = ImVec2(text_min.x, window->Pos.y);
4263 RenderTextClipped(text_min, text_max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect);
4264 }
4265
4266 // Save clipped aabb so we can access it in constant-time in FindHoveredWindow()
4267 window->WindowRectClipped = window->Rect();
4268 window->WindowRectClipped.ClipWith(window->ClipRect);
4269
4270 // Pressing CTRL+C while holding on a window copy its content to the clipboard
4271 // 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.
4272 // Maybe we can support CTRL+C on every element?
4273 /*
4274 if (g.ActiveId == move_id)
4275 if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
4276 ImGui::LogToClipboard();
4277 */
4278 }
4279
4280 // Scrollbars
4281 if (window->ScrollbarX)
4282 Scrollbar(window, true);
4283 if (window->ScrollbarY)
4284 Scrollbar(window, false);
4285
4286 // Inner clipping rectangle
4287 // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame
4288 // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior.
4289 const ImRect title_bar_rect = window->TitleBarRect();
4290 const float border_size = window->BorderSize;
4291 // Force round to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
4292 ImRect clip_rect;
4293 clip_rect.Min.x = ImFloor(0.5f + title_bar_rect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x * 0.1f)));
4294 clip_rect.Min.y = ImFloor(0.5f + title_bar_rect.Max.y + window->MenuBarHeight() + border_size);
4295 clip_rect.Max.x = ImFloor(0.5f + window->Pos.x + window->Size.x - window->ScrollbarSizes.x - ImMax(border_size, ImFloor(window->WindowPadding.x * 0.1f)));
4296 clip_rect.Max.y = ImFloor(0.5f + window->Pos.y + window->Size.y - window->ScrollbarSizes.y - border_size);
4297 PushClipRect(clip_rect.Min, clip_rect.Max, false);
4298
4299 // Clear 'accessed' flag last thing
4300 if (first_begin_of_the_frame)
4301 window->Accessed = false;
4302 window->BeginCount++;
4303 g.SetNextWindowSizeConstraint = false;
4304
4305 // Child window can be out of sight and have "negative" clip windows.
4306 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar).
4307 if (flags & ImGuiWindowFlags_ChildWindow) {
4308 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);
4309 window->Collapsed = parent_window && parent_window->Collapsed;
4310
4311 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
4312 window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y);
4313
4314 // We also hide the window from rendering because we've already added its border to the command list.
4315 // (we could perform the check earlier in the function but it is simpler at this point)
4316 if (window->Collapsed)
4317 window->Active = false;
4318 }
4319 if (style.Alpha <= 0.0f)
4320 window->Active = false;
4321
4322 // Return false if we don't intend to display anything to allow user to perform an early out optimization
4323 window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0;
4324 return !window->SkipItems;
4325}
4326
4327void ImGui::End() {
4328 ImGuiContext& g = *GImGui;
4329 ImGuiWindow* window = g.CurrentWindow;
4330
4331 if (window->DC.ColumnsCount != 1) // close columns set if any is open
4332 EndColumns();
4333 PopClipRect(); // inner window clip rectangle
4334
4335 // Stop logging
4336 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
4337 LogFinish();
4338
4339 // Pop
4340 // NB: we don't clear 'window->RootWindow'. The pointer is allowed to live until the next call to Begin().
4341 g.CurrentWindowStack.pop_back();
4342 if (window->Flags & ImGuiWindowFlags_Popup)
4343 g.CurrentPopupStack.pop_back();
4344 CheckStacksSize(window, false);
4345 SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
4346}
4347
4348// Vertical scrollbar
4349// The entire piece of code below is rather confusing because:
4350// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
4351// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
4352// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
4353static void Scrollbar(ImGuiWindow * window, bool horizontal) {
4354 ImGuiContext& g = *GImGui;
4355 const ImGuiStyle& style = g.Style;
4356 const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
4357
4358 // Render background
4359 bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
4360 float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
4361 const ImRect window_rect = window->Rect();
4362 const float border_size = window->BorderSize;
4363 ImRect bb = horizontal
4364 ? 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)
4365 : 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);
4366 if (!horizontal)
4367 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
4368 if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
4369 return;
4370
4371 float window_rounding = (window->Flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding;
4372 int window_rounding_corners;
4373 if (horizontal)
4374 window_rounding_corners = ImGuiCorner_BottomLeft | (other_scrollbar ? 0 : ImGuiCorner_BottomRight);
4375 else
4376 window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImGuiCorner_TopRight : 0) | (other_scrollbar ? 0 : ImGuiCorner_BottomRight);
4377 window->DrawList->AddRectFilled(bb.Min - ImVec2(1, 0), bb.Max, ImGui::GetColorU32(ImGuiCol_ScrollbarBg), window_rounding, window_rounding_corners);
4378 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)));
4379
4380 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
4381 float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
4382 float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
4383 float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
4384 float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
4385
4386 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
4387 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
4388 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.
4389 const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
4390 const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
4391 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
4392
4393 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
4394 bool held = false;
4395 bool hovered = false;
4396 const bool previously_held = (g.ActiveId == id);
4397 ImGui::ButtonBehavior(bb, id, &hovered, &held);
4398
4399 float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
4400 float scroll_ratio = ImSaturate(scroll_v / scroll_max);
4401 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
4402 if (held && grab_h_norm < 1.0f) {
4403 float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
4404 float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
4405 float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
4406
4407 // Click position in scrollbar normalized space (0.0f->1.0f)
4408 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
4409 ImGui::SetHoveredID(id);
4410
4411 bool seek_absolute = false;
4412 if (!previously_held) {
4413 // On initial click calculate the distance between mouse and the center of the grab
4414 if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) {
4415 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
4416 }
4417 else {
4418 seek_absolute = true;
4419 *click_delta_to_grab_center_v = 0.0f;
4420 }
4421 }
4422
4423 // Apply scroll
4424 // 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
4425 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));
4426 scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
4427 if (horizontal)
4428 window->Scroll.x = scroll_v;
4429 else
4430 window->Scroll.y = scroll_v;
4431
4432 // Update values for rendering
4433 scroll_ratio = ImSaturate(scroll_v / scroll_max);
4434 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
4435
4436 // Update distance to grab now that we have seeked and saturated
4437 if (seek_absolute)
4438 * click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
4439 }
4440
4441 // Render
4442 const ImU32 grab_col = ImGui::GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
4443 if (horizontal)
4444 window->DrawList->AddRectFilled(ImVec2(ImLerp(bb.Min.x - 1, bb.Max.x, grab_v_norm), bb.Min.y - 2), ImVec2(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y + 2), grab_col, style.ScrollbarRounding);
4445 else
4446 window->DrawList->AddRectFilled(ImVec2(bb.Min.x - 1, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) - 2), ImVec2(bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels + 2), grab_col, style.ScrollbarRounding);
4447}
4448
4449// Moving window to front of display (which happens to be back of our sorted list)
4450void ImGui::FocusWindow(ImGuiWindow * window) {
4451 ImGuiContext& g = *GImGui;
4452
4453 // Always mark the window we passed as focused. This is used for keyboard interactions such as tabbing.
4454 g.NavWindow = window;
4455
4456 // Passing NULL allow to disable keyboard focus
4457 if (!window)
4458 return;
4459
4460 // And move its root window to the top of the pile
4461 if (window->RootWindow)
4462 window = window->RootWindow;
4463
4464 // Steal focus on active widgets
4465 if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it..
4466 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window)
4467 ClearActiveID();
4468
4469 // Bring to front
4470 if ((window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) || g.Windows.back() == window)
4471 return;
4472 for (int i = 0; i < g.Windows.Size; i++)
4473 if (g.Windows[i] == window) {
4474 g.Windows.erase(g.Windows.begin() + i);
4475 break;
4476 }
4477 g.Windows.push_back(window);
4478}
4479
4480void ImGui::PushItemWidth(float item_width) {
4481 ImGuiWindow* window = GetCurrentWindow();
4482 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
4483 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth);
4484}
4485
4486static void PushMultiItemsWidths(int components, float w_full) {
4487 ImGuiWindow* window = ImGui::GetCurrentWindow();
4488 const ImGuiStyle& style = GImGui->Style;
4489 if (w_full <= 0.0f)
4490 w_full = ImGui::CalcItemWidth();
4491 const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
4492 const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
4493 window->DC.ItemWidthStack.push_back(w_item_last);
4494 for (int i = 0; i < components - 1; i++)
4495 window->DC.ItemWidthStack.push_back(w_item_one);
4496 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
4497}
4498
4499void ImGui::PopItemWidth() {
4500 ImGuiWindow* window = GetCurrentWindow();
4501 window->DC.ItemWidthStack.pop_back();
4502 window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back();
4503}
4504
4505float ImGui::CalcItemWidth() {
4506 ImGuiWindow* window = GetCurrentWindowRead();
4507 float w = window->DC.ItemWidth;
4508 if (w < 0.0f) {
4509 // 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.
4510 float width_to_right_edge = GetContentRegionAvail().x;
4511 w = ImMax(1.0f, width_to_right_edge + w);
4512 }
4513 w = (float)(int)w;
4514 return w;
4515}
4516
4517static ImFont* GetDefaultFont() {
4518 ImGuiContext& g = *GImGui;
4519 return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0];
4520}
4521
4522static void SetCurrentFont(ImFont * font) {
4523 ImGuiContext& g = *GImGui;
4524 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
4525 IM_ASSERT(font->Scale > 0.0f);
4526 g.Font = font;
4527 g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale;
4528 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
4529 g.FontTexUvWhitePixel = g.Font->ContainerAtlas->TexUvWhitePixel;
4530}
4531
4532void ImGui::PushFont(ImFont * font) {
4533 ImGuiContext& g = *GImGui;
4534 if (!font)
4535 font = GetDefaultFont();
4536 SetCurrentFont(font);
4537 g.FontStack.push_back(font);
4538 g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
4539}
4540
4541void ImGui::PopFont() {
4542 ImGuiContext& g = *GImGui;
4543 g.CurrentWindow->DrawList->PopTextureID();
4544 g.FontStack.pop_back();
4545 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
4546}
4547
4548void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) {
4549 ImGuiWindow* window = GetCurrentWindow();
4550 window->DC.AllowKeyboardFocus = allow_keyboard_focus;
4551 window->DC.AllowKeyboardFocusStack.push_back(allow_keyboard_focus);
4552}
4553
4554void ImGui::PopAllowKeyboardFocus() {
4555 ImGuiWindow* window = GetCurrentWindow();
4556 window->DC.AllowKeyboardFocusStack.pop_back();
4557 window->DC.AllowKeyboardFocus = window->DC.AllowKeyboardFocusStack.empty() ? true : window->DC.AllowKeyboardFocusStack.back();
4558}
4559
4560void ImGui::PushButtonRepeat(bool repeat) {
4561 ImGuiWindow* window = GetCurrentWindow();
4562 window->DC.ButtonRepeat = repeat;
4563 window->DC.ButtonRepeatStack.push_back(repeat);
4564}
4565
4566void ImGui::PopButtonRepeat() {
4567 ImGuiWindow* window = GetCurrentWindow();
4568 window->DC.ButtonRepeatStack.pop_back();
4569 window->DC.ButtonRepeat = window->DC.ButtonRepeatStack.empty() ? false : window->DC.ButtonRepeatStack.back();
4570}
4571
4572void ImGui::PushTextWrapPos(float wrap_pos_x) {
4573 ImGuiWindow* window = GetCurrentWindow();
4574 window->DC.TextWrapPos = wrap_pos_x;
4575 window->DC.TextWrapPosStack.push_back(wrap_pos_x);
4576}
4577
4578void ImGui::PopTextWrapPos() {
4579 ImGuiWindow* window = GetCurrentWindow();
4580 window->DC.TextWrapPosStack.pop_back();
4581 window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back();
4582}
4583
4584// 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
4585void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) {
4586 ImGuiContext& g = *GImGui;
4587 ImGuiColMod backup;
4588 backup.Col = idx;
4589 backup.BackupValue = g.Style.Colors[idx];
4590 g.ColorModifiers.push_back(backup);
4591 g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
4592}
4593
4594void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4 & col) {
4595 ImGuiContext& g = *GImGui;
4596 ImGuiColMod backup;
4597 backup.Col = idx;
4598 backup.BackupValue = g.Style.Colors[idx];
4599 g.ColorModifiers.push_back(backup);
4600 g.Style.Colors[idx] = col;
4601}
4602
4603void ImGui::PopStyleColor(int count) {
4604 ImGuiContext& g = *GImGui;
4605 while (count > 0) {
4606 ImGuiColMod& backup = g.ColorModifiers.back();
4607 g.Style.Colors[backup.Col] = backup.BackupValue;
4608 g.ColorModifiers.pop_back();
4609 count--;
4610 }
4611}
4612
4613struct ImGuiStyleVarInfo {
4614 ImGuiDataType Type;
4615 ImU32 Offset;
4616 void* GetVarPtr() const {
4617 return (void*)((unsigned char*)& GImGui->Style + Offset);
4618 }
4619};
4620
4621static const ImGuiStyleVarInfo GStyleVarInfo[ImGuiStyleVar_Count_] =
4622{
4623 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha
4624 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding
4625 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding
4626 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize
4627 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildWindowRounding) }, // ImGuiStyleVar_ChildWindowRounding
4628 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding
4629 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding
4630 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing
4631 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing
4632 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing
4633 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize
4634 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
4635};
4636
4637static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) {
4638 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_);
4639 return &GStyleVarInfo[idx];
4640}
4641
4642void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) {
4643 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
4644 if (var_info->Type == ImGuiDataType_Float) {
4645 float* pvar = (float*)var_info->GetVarPtr();
4646 GImGui->StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
4647 *pvar = val;
4648 return;
4649 }
4650 IM_ASSERT(0); // Called function with wrong-type? Variable is not a float.
4651}
4652
4653void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2 & val) {
4654 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
4655 if (var_info->Type == ImGuiDataType_Float2) {
4656 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr();
4657 GImGui->StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
4658 *pvar = val;
4659 return;
4660 }
4661 IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2.
4662}
4663
4664void ImGui::PopStyleVar(int count) {
4665 ImGuiContext& g = *GImGui;
4666 while (count > 0) {
4667 ImGuiStyleMod& backup = g.StyleModifiers.back();
4668 const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
4669 if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr()) = backup.BackupFloat[0];
4670 else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr()) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]);
4671 else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr()) = backup.BackupInt[0];
4672 g.StyleModifiers.pop_back();
4673 count--;
4674 }
4675}
4676
4677const char* ImGui::GetStyleColorName(ImGuiCol idx) {
4678 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
4679 switch (idx) {
4680 case ImGuiCol_Text: return "Text";
4681 case ImGuiCol_TextDisabled: return "TextDisabled";
4682 case ImGuiCol_WindowBg: return "WindowBg";
4683 case ImGuiCol_ChildWindowBg: return "ChildWindowBg";
4684 case ImGuiCol_PopupBg: return "PopupBg";
4685 case ImGuiCol_Border: return "Border";
4686 case ImGuiCol_BorderShadow: return "BorderShadow";
4687 case ImGuiCol_FrameBg: return "FrameBg";
4688 case ImGuiCol_FrameBgHovered: return "FrameBgHovered";
4689 case ImGuiCol_FrameBgActive: return "FrameBgActive";
4690 case ImGuiCol_TitleBg: return "TitleBg";
4691 case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed";
4692 case ImGuiCol_TitleBgActive: return "TitleBgActive";
4693 case ImGuiCol_MenuBarBg: return "MenuBarBg";
4694 case ImGuiCol_ScrollbarBg: return "ScrollbarBg";
4695 case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab";
4696 case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
4697 case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
4698 case ImGuiCol_ComboBg: return "ComboBg";
4699 case ImGuiCol_CheckMark: return "CheckMark";
4700 case ImGuiCol_SliderGrab: return "SliderGrab";
4701 case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
4702 case ImGuiCol_Button: return "Button";
4703 case ImGuiCol_ButtonHovered: return "ButtonHovered";
4704 case ImGuiCol_ButtonActive: return "ButtonActive";
4705 case ImGuiCol_Header: return "Header";
4706 case ImGuiCol_HeaderHovered: return "HeaderHovered";
4707 case ImGuiCol_HeaderActive: return "HeaderActive";
4708 case ImGuiCol_Separator: return "Separator";
4709 case ImGuiCol_SeparatorHovered: return "SeparatorHovered";
4710 case ImGuiCol_SeparatorActive: return "SeparatorActive";
4711 case ImGuiCol_ResizeGrip: return "ResizeGrip";
4712 case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered";
4713 case ImGuiCol_ResizeGripActive: return "ResizeGripActive";
4714 case ImGuiCol_CloseButton: return "CloseButton";
4715 case ImGuiCol_CloseButtonHovered: return "CloseButtonHovered";
4716 case ImGuiCol_CloseButtonActive: return "CloseButtonActive";
4717 case ImGuiCol_PlotLines: return "PlotLines";
4718 case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
4719 case ImGuiCol_PlotHistogram: return "PlotHistogram";
4720 case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
4721 case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
4722 case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening";
4723 }
4724 IM_ASSERT(0);
4725 return "Unknown";
4726}
4727
4728bool ImGui::IsWindowHovered() {
4729 ImGuiContext& g = *GImGui;
4730 return g.HoveredWindow == g.CurrentWindow && IsWindowContentHoverable(g.HoveredRootWindow);
4731}
4732
4733bool ImGui::IsWindowRectHovered() {
4734 ImGuiContext& g = *GImGui;
4735 return g.HoveredWindow == g.CurrentWindow;
4736}
4737
4738bool ImGui::IsWindowFocused() {
4739 ImGuiContext& g = *GImGui;
4740 return g.NavWindow == g.CurrentWindow;
4741}
4742
4743bool ImGui::IsRootWindowFocused() {
4744 ImGuiContext& g = *GImGui;
4745 return g.NavWindow == g.CurrentWindow->RootWindow;
4746}
4747
4748bool ImGui::IsRootWindowOrAnyChildFocused() {
4749 ImGuiContext& g = *GImGui;
4750 return g.NavWindow&& g.NavWindow->RootWindow == g.CurrentWindow->RootWindow;
4751}
4752
4753bool ImGui::IsRootWindowOrAnyChildHovered() {
4754 ImGuiContext& g = *GImGui;
4755 return g.HoveredRootWindow && (g.HoveredRootWindow == g.CurrentWindow->RootWindow) && IsWindowContentHoverable(g.HoveredRootWindow);
4756}
4757
4758float ImGui::GetWindowWidth() {
4759 ImGuiWindow* window = GImGui->CurrentWindow;
4760 return window->Size.x;
4761}
4762
4763float ImGui::GetWindowHeight() {
4764 ImGuiWindow* window = GImGui->CurrentWindow;
4765 return window->Size.y;
4766}
4767
4768ImVec2 ImGui::GetWindowPos() {
4769 ImGuiContext& g = *GImGui;
4770 ImGuiWindow* window = g.CurrentWindow;
4771 return window->Pos;
4772}
4773
4774static void SetWindowScrollY(ImGuiWindow * window, float new_scroll_y) {
4775 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.
4776 window->Scroll.y = new_scroll_y;
4777 window->DC.CursorMaxPos.y -= window->Scroll.y;
4778}
4779
4780static void SetWindowPos(ImGuiWindow * window, const ImVec2 & pos, ImGuiCond cond) {
4781 // Test condition (NB: bit 0 is always true) and clear flags for next time
4782 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
4783 return;
4784 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
4785 window->SetWindowPosCenterWanted = false;
4786
4787 // Set
4788 const ImVec2 old_pos = window->Pos;
4789 window->PosFloat = pos;
4790 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y);
4791 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
4792 window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected.
4793}
4794
4795void ImGui::SetWindowPos(const ImVec2 & pos, ImGuiCond cond) {
4796 ImGuiWindow* window = GetCurrentWindowRead();
4797 SetWindowPos(window, pos, cond);
4798}
4799
4800void ImGui::SetWindowPos(const char* name, const ImVec2 & pos, ImGuiCond cond) {
4801 if (ImGuiWindow * window = FindWindowByName(name))
4802 SetWindowPos(window, pos, cond);
4803}
4804
4805ImVec2 ImGui::GetWindowSize() {
4806 ImGuiWindow* window = GetCurrentWindowRead();
4807 return window->Size;
4808}
4809
4810static void SetWindowSize(ImGuiWindow * window, const ImVec2 & size, ImGuiCond cond) {
4811 // Test condition (NB: bit 0 is always true) and clear flags for next time
4812 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
4813 return;
4814 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
4815
4816 // Set
4817 if (size.x > 0.0f) {
4818 window->AutoFitFramesX = 0;
4819 window->SizeFull.x = size.x;
4820 }
4821 else {
4822 window->AutoFitFramesX = 2;
4823 window->AutoFitOnlyGrows = false;
4824 }
4825 if (size.y > 0.0f) {
4826 window->AutoFitFramesY = 0;
4827 window->SizeFull.y = size.y;
4828 }
4829 else {
4830 window->AutoFitFramesY = 2;
4831 window->AutoFitOnlyGrows = false;
4832 }
4833}
4834
4835void ImGui::SetWindowSize(const ImVec2 & size, ImGuiCond cond) {
4836 SetWindowSize(GImGui->CurrentWindow, size, cond);
4837}
4838
4839void ImGui::SetWindowSize(const char* name, const ImVec2 & size, ImGuiCond cond) {
4840 ImGuiWindow* window = FindWindowByName(name);
4841 if (window)
4842 SetWindowSize(window, size, cond);
4843}
4844
4845static void SetWindowCollapsed(ImGuiWindow * window, bool collapsed, ImGuiCond cond) {
4846 // Test condition (NB: bit 0 is always true) and clear flags for next time
4847 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
4848 return;
4849 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
4850
4851 // Set
4852 window->Collapsed = collapsed;
4853}
4854
4855void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) {
4856 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
4857}
4858
4859bool ImGui::IsWindowCollapsed() {
4860 return GImGui->CurrentWindow->Collapsed;
4861}
4862
4863void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) {
4864 ImGuiWindow* window = FindWindowByName(name);
4865 if (window)
4866 SetWindowCollapsed(window, collapsed, cond);
4867}
4868
4869void ImGui::SetWindowFocus() {
4870 FocusWindow(GImGui->CurrentWindow);
4871}
4872
4873void ImGui::SetWindowFocus(const char* name) {
4874 if (name) {
4875 if (ImGuiWindow * window = FindWindowByName(name))
4876 FocusWindow(window);
4877 }
4878 else {
4879 FocusWindow(NULL);
4880 }
4881}
4882
4883void ImGui::SetNextWindowPos(const ImVec2 & pos, ImGuiCond cond) {
4884 ImGuiContext& g = *GImGui;
4885 g.SetNextWindowPosVal = pos;
4886 g.SetNextWindowPosCond = cond ? cond : ImGuiCond_Always;
4887}
4888
4889void ImGui::SetNextWindowPosCenter(ImGuiCond cond) {
4890 ImGuiContext& g = *GImGui;
4891 g.SetNextWindowPosVal = ImVec2(-FLT_MAX, -FLT_MAX);
4892 g.SetNextWindowPosCond = cond ? cond : ImGuiCond_Always;
4893}
4894
4895void ImGui::SetNextWindowSize(const ImVec2 & size, ImGuiCond cond) {
4896 ImGuiContext& g = *GImGui;
4897 g.SetNextWindowSizeVal = size;
4898 g.SetNextWindowSizeCond = cond ? cond : ImGuiCond_Always;
4899}
4900
4901void ImGui::SetNextWindowSizeConstraints(const ImVec2 & size_min, const ImVec2 & size_max, ImGuiSizeConstraintCallback custom_callback, void* custom_callback_user_data) {
4902 ImGuiContext& g = *GImGui;
4903 g.SetNextWindowSizeConstraint = true;
4904 g.SetNextWindowSizeConstraintRect = ImRect(size_min, size_max);
4905 g.SetNextWindowSizeConstraintCallback = custom_callback;
4906 g.SetNextWindowSizeConstraintCallbackUserData = custom_callback_user_data;
4907}
4908
4909void ImGui::SetNextWindowContentSize(const ImVec2 & size) {
4910 ImGuiContext& g = *GImGui;
4911 g.SetNextWindowContentSizeVal = size;
4912 g.SetNextWindowContentSizeCond = ImGuiCond_Always;
4913}
4914
4915void ImGui::SetNextWindowContentWidth(float width) {
4916 ImGuiContext& g = *GImGui;
4917 g.SetNextWindowContentSizeVal = ImVec2(width, g.SetNextWindowContentSizeCond ? g.SetNextWindowContentSizeVal.y : 0.0f);
4918 g.SetNextWindowContentSizeCond = ImGuiCond_Always;
4919}
4920
4921void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) {
4922 ImGuiContext& g = *GImGui;
4923 g.SetNextWindowCollapsedVal = collapsed;
4924 g.SetNextWindowCollapsedCond = cond ? cond : ImGuiCond_Always;
4925}
4926
4927void ImGui::SetNextWindowFocus() {
4928 ImGuiContext& g = *GImGui;
4929 g.SetNextWindowFocus = true;
4930}
4931
4932// In window space (not screen space!)
4933ImVec2 ImGui::GetContentRegionMax() {
4934 ImGuiWindow* window = GetCurrentWindowRead();
4935 ImVec2 mx = window->ContentsRegionRect.Max;
4936 if (window->DC.ColumnsCount != 1)
4937 mx.x = GetColumnOffset(window->DC.ColumnsCurrent + 1) - window->WindowPadding.x;
4938 return mx;
4939}
4940
4941ImVec2 ImGui::GetContentRegionAvail() {
4942 ImGuiWindow* window = GetCurrentWindowRead();
4943 return GetContentRegionMax() - (window->DC.CursorPos - window->Pos);
4944}
4945
4946float ImGui::GetContentRegionAvailWidth() {
4947 return GetContentRegionAvail().x;
4948}
4949
4950// In window space (not screen space!)
4951ImVec2 ImGui::GetWindowContentRegionMin() {
4952 ImGuiWindow* window = GetCurrentWindowRead();
4953 return window->ContentsRegionRect.Min;
4954}
4955
4956ImVec2 ImGui::GetWindowContentRegionMax() {
4957 ImGuiWindow* window = GetCurrentWindowRead();
4958 return window->ContentsRegionRect.Max;
4959}
4960
4961float ImGui::GetWindowContentRegionWidth() {
4962 ImGuiWindow* window = GetCurrentWindowRead();
4963 return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x;
4964}
4965
4966float ImGui::GetTextLineHeight() {
4967 ImGuiContext& g = *GImGui;
4968 return g.FontSize;
4969}
4970
4971float ImGui::GetTextLineHeightWithSpacing() {
4972 ImGuiContext& g = *GImGui;
4973 return g.FontSize + g.Style.ItemSpacing.y;
4974}
4975
4976float ImGui::GetItemsLineHeightWithSpacing() {
4977 ImGuiContext& g = *GImGui;
4978 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
4979}
4980
4981ImDrawList* ImGui::GetWindowDrawList() {
4982 ImGuiWindow* window = GetCurrentWindow();
4983 return window->DrawList;
4984}
4985
4986ImFont* ImGui::GetFont() {
4987 return GImGui->Font;
4988}
4989
4990float ImGui::GetFontSize() {
4991 return GImGui->FontSize;
4992}
4993
4994ImVec2 ImGui::GetFontTexUvWhitePixel() {
4995 return GImGui->FontTexUvWhitePixel;
4996}
4997
4998void ImGui::SetWindowFontScale(float scale) {
4999 ImGuiContext& g = *GImGui;
5000 ImGuiWindow* window = GetCurrentWindow();
5001 window->FontWindowScale = scale;
5002 g.FontSize = window->CalcFontSize();
5003}
5004
5005// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
5006// 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'.
5007ImVec2 ImGui::GetCursorPos() {
5008 ImGuiWindow* window = GetCurrentWindowRead();
5009 return window->DC.CursorPos - window->Pos + window->Scroll;
5010}
5011
5012float ImGui::GetCursorPosX() {
5013 ImGuiWindow* window = GetCurrentWindowRead();
5014 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
5015}
5016
5017float ImGui::GetCursorPosY() {
5018 ImGuiWindow* window = GetCurrentWindowRead();
5019 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
5020}
5021
5022void ImGui::SetCursorPos(const ImVec2 & local_pos) {
5023 ImGuiWindow* window = GetCurrentWindow();
5024 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
5025 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
5026}
5027
5028void ImGui::SetCursorPosX(float x) {
5029 ImGuiWindow* window = GetCurrentWindow();
5030 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
5031 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
5032}
5033
5034void ImGui::SetCursorPosY(float y) {
5035 ImGuiWindow* window = GetCurrentWindow();
5036 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
5037 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
5038}
5039
5040ImVec2 ImGui::GetCursorStartPos() {
5041 ImGuiWindow* window = GetCurrentWindowRead();
5042 return window->DC.CursorStartPos - window->Pos;
5043}
5044
5045ImVec2 ImGui::GetCursorScreenPos() {
5046 ImGuiWindow* window = GetCurrentWindowRead();
5047 return window->DC.CursorPos;
5048}
5049
5050void ImGui::SetCursorScreenPos(const ImVec2 & screen_pos) {
5051 ImGuiWindow* window = GetCurrentWindow();
5052 window->DC.CursorPos = screen_pos;
5053 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
5054}
5055
5056float ImGui::GetScrollX() {
5057 return GImGui->CurrentWindow->Scroll.x;
5058}
5059
5060float ImGui::GetScrollY() {
5061 return GImGui->CurrentWindow->Scroll.y;
5062}
5063
5064float ImGui::GetScrollMaxX() {
5065 ImGuiWindow* window = GetCurrentWindowRead();
5066 return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x));
5067}
5068
5069float ImGui::GetScrollMaxY() {
5070 ImGuiWindow* window = GetCurrentWindowRead();
5071 return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y));
5072}
5073
5074void ImGui::SetScrollX(float scroll_x) {
5075 ImGuiWindow* window = GetCurrentWindow();
5076 window->ScrollTarget.x = scroll_x;
5077 window->ScrollTargetCenterRatio.x = 0.0f;
5078}
5079
5080void ImGui::SetScrollY(float scroll_y) {
5081 ImGuiWindow* window = GetCurrentWindow();
5082 window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY
5083 window->ScrollTargetCenterRatio.y = 0.0f;
5084}
5085
5086void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) {
5087 // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
5088 ImGuiWindow* window = GetCurrentWindow();
5089 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
5090 window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y);
5091 if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y) // Minor hack to make "scroll to top" take account of WindowPadding, else it would scroll to (WindowPadding.y - ItemSpacing.y)
5092 window->ScrollTarget.y = 0.0f;
5093 window->ScrollTargetCenterRatio.y = center_y_ratio;
5094}
5095
5096// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
5097void ImGui::SetScrollHere(float center_y_ratio) {
5098 ImGuiWindow* window = GetCurrentWindow();
5099 float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space
5100 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.
5101 SetScrollFromPosY(target_y, center_y_ratio);
5102}
5103
5104void ImGui::SetKeyboardFocusHere(int offset) {
5105 ImGuiWindow* window = GetCurrentWindow();
5106 window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset;
5107 window->FocusIdxTabRequestNext = INT_MAX;
5108}
5109
5110void ImGui::SetStateStorage(ImGuiStorage * tree) {
5111 ImGuiWindow* window = GetCurrentWindow();
5112 window->DC.StateStorage = tree ? tree : &window->StateStorage;
5113}
5114
5115ImGuiStorage* ImGui::GetStateStorage() {
5116 ImGuiWindow* window = GetCurrentWindowRead();
5117 return window->DC.StateStorage;
5118}
5119
5120void ImGui::TextV(const char* fmt, va_list args) {
5121 ImGuiWindow* window = GetCurrentWindow();
5122 if (window->SkipItems)
5123 return;
5124
5125 ImGuiContext& g = *GImGui;
5126 const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5127 TextUnformatted(g.TempBuffer, text_end);
5128}
5129
5130void ImGui::Text(const char* fmt, ...) {
5131 va_list args;
5132 va_start(args, fmt);
5133 TextV(fmt, args);
5134 va_end(args);
5135}
5136
5137void ImGui::TextColoredV(const ImVec4 & col, const char* fmt, va_list args) {
5138 PushStyleColor(ImGuiCol_Text, col);
5139 TextV(fmt, args);
5140 PopStyleColor();
5141}
5142
5143void ImGui::TextColored(const ImVec4 & col, const char* fmt, ...) {
5144 va_list args;
5145 va_start(args, fmt);
5146 TextColoredV(col, fmt, args);
5147 va_end(args);
5148}
5149
5150void ImGui::TextDisabledV(const char* fmt, va_list args) {
5151 PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
5152 TextV(fmt, args);
5153 PopStyleColor();
5154}
5155
5156void ImGui::TextDisabled(const char* fmt, ...) {
5157 va_list args;
5158 va_start(args, fmt);
5159 TextDisabledV(fmt, args);
5160 va_end(args);
5161}
5162
5163void ImGui::TextWrappedV(const char* fmt, va_list args) {
5164 bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
5165 if (need_wrap) PushTextWrapPos(0.0f);
5166 TextV(fmt, args);
5167 if (need_wrap) PopTextWrapPos();
5168}
5169
5170void ImGui::TextWrapped(const char* fmt, ...) {
5171 va_list args;
5172 va_start(args, fmt);
5173 TextWrappedV(fmt, args);
5174 va_end(args);
5175}
5176
5177void ImGui::TextUnformatted(const char* text, const char* text_end) {
5178 ImGuiWindow* window = GetCurrentWindow();
5179 if (window->SkipItems)
5180 return;
5181
5182 ImGuiContext& g = *GImGui;
5183 IM_ASSERT(text != NULL);
5184 const char* text_begin = text;
5185 if (text_end == NULL)
5186 text_end = text + strlen(text); // FIXME-OPT
5187
5188 const float wrap_pos_x = window->DC.TextWrapPos;
5189 const bool wrap_enabled = wrap_pos_x >= 0.0f;
5190 if (text_end - text > 2000 && !wrap_enabled) {
5191 // Long text!
5192 // Perform manual coarse clipping to optimize for long multi-line text
5193 // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
5194 // 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.
5195 const char* line = text;
5196 const float line_height = GetTextLineHeight();
5197 const ImVec2 text_pos = window->DC.CursorPos + ImVec2(0.0f, window->DC.CurrentLineTextBaseOffset);
5198 const ImRect clip_rect = window->ClipRect;
5199 ImVec2 text_size(0, 0);
5200
5201 if (text_pos.y <= clip_rect.Max.y) {
5202 ImVec2 pos = text_pos;
5203
5204 // Lines to skip (can't skip when logging text)
5205 if (!g.LogEnabled) {
5206 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
5207 if (lines_skippable > 0) {
5208 int lines_skipped = 0;
5209 while (line < text_end && lines_skipped < lines_skippable) {
5210 const char* line_end = strchr(line, '\n');
5211 if (!line_end)
5212 line_end = text_end;
5213 line = line_end + 1;
5214 lines_skipped++;
5215 }
5216 pos.y += lines_skipped * line_height;
5217 }
5218 }
5219
5220 // Lines to render
5221 if (line < text_end) {
5222 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
5223 while (line < text_end) {
5224 const char* line_end = strchr(line, '\n');
5225 if (IsClippedEx(line_rect, NULL, false))
5226 break;
5227
5228 const ImVec2 line_size = CalcTextSize(line, line_end, false);
5229 text_size.x = ImMax(text_size.x, line_size.x);
5230
5231 RenderText(pos, line, line_end, false);
5232
5233 if (!line_end)
5234 line_end = text_end;
5235 line = line_end + 1;
5236 line_rect.Min.y += line_height;
5237 line_rect.Max.y += line_height;
5238 pos.y += line_height;
5239 }
5240
5241 // Count remaining lines
5242 int lines_skipped = 0;
5243 while (line < text_end) {
5244 const char* line_end = strchr(line, '\n');
5245 if (!line_end)
5246 line_end = text_end;
5247 line = line_end + 1;
5248 lines_skipped++;
5249 }
5250 pos.y += lines_skipped * line_height;
5251 }
5252
5253 text_size.y += (pos - text_pos).y;
5254 }
5255
5256 ImRect bb(text_pos, text_pos + text_size);
5257 ItemSize(bb);
5258 ItemAdd(bb, NULL);
5259 }
5260 else {
5261 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
5262 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
5263
5264 // Account of baseline offset
5265 ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
5266 ImRect bb(text_pos, text_pos + text_size);
5267 ItemSize(text_size);
5268 if (!ItemAdd(bb, NULL))
5269 return;
5270
5271 // Render (we don't hide text after ## in this end-user function)
5272 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
5273 }
5274}
5275
5276void ImGui::AlignFirstTextHeightToWidgets() {
5277 ImGuiWindow* window = GetCurrentWindow();
5278 if (window->SkipItems)
5279 return;
5280
5281 // Declare a dummy item size to that upcoming items that are smaller will center-align on the newly expanded line height.
5282 ImGuiContext& g = *GImGui;
5283 ItemSize(ImVec2(0, g.FontSize + g.Style.FramePadding.y * 2), g.Style.FramePadding.y);
5284 SameLine(0, 0);
5285}
5286
5287// Add a label+text combo aligned to other label+value widgets
5288void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) {
5289 ImGuiWindow* window = GetCurrentWindow();
5290 if (window->SkipItems)
5291 return;
5292
5293 ImGuiContext& g = *GImGui;
5294 const ImGuiStyle& style = g.Style;
5295 const float w = CalcItemWidth();
5296
5297 const ImVec2 label_size = CalcTextSize(label, NULL, true);
5298 const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2));
5299 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);
5300 ItemSize(total_bb, style.FramePadding.y);
5301 if (!ItemAdd(total_bb, NULL))
5302 return;
5303
5304 // Render
5305 const char* value_text_begin = &g.TempBuffer[0];
5306 const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5307 RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f, 0.5f));
5308 if (label_size.x > 0.0f)
5309 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
5310}
5311
5312void ImGui::LabelText(const char* label, const char* fmt, ...) {
5313 va_list args;
5314 va_start(args, fmt);
5315 LabelTextV(label, fmt, args);
5316 va_end(args);
5317}
5318
5319static inline bool IsWindowContentHoverable(ImGuiWindow * window) {
5320 // An active popup disable hovering on other windows (apart from its own children)
5321 // FIXME-OPT: This could be cached/stored within the window.
5322 ImGuiContext& g = *GImGui;
5323 if (g.NavWindow)
5324 if (ImGuiWindow * focused_root_window = g.NavWindow->RootWindow)
5325 if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) != 0 && focused_root_window->WasActive && focused_root_window != window->RootWindow)
5326 return false;
5327
5328 return true;
5329}
5330
5331bool ImGui::ButtonBehavior(const ImRect & bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) {
5332 ImGuiContext& g = *GImGui;
5333 ImGuiWindow* window = GetCurrentWindow();
5334
5335 if (flags & ImGuiButtonFlags_Disabled) {
5336 if (out_hovered)* out_hovered = false;
5337 if (out_held)* out_held = false;
5338 if (g.ActiveId == id) ClearActiveID();
5339 return false;
5340 }
5341
5342 // Default behavior requires click+release on same spot
5343 if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
5344 flags |= ImGuiButtonFlags_PressedOnClickRelease;
5345
5346 bool pressed = false;
5347 bool hovered = IsHovered(bb, id, (flags & ImGuiButtonFlags_FlattenChilds) != 0);
5348 if (hovered) {
5349 SetHoveredID(id);
5350 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) {
5351 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
5352 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
5353 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
5354 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
5355 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
5356 if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) {
5357 SetActiveID(id, window); // Hold on ID
5358 FocusWindow(window);
5359 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
5360 }
5361 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) {
5362 pressed = true;
5363 ClearActiveID();
5364 FocusWindow(window);
5365 }
5366 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) {
5367 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
5368 pressed = true;
5369 ClearActiveID();
5370 }
5371
5372 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
5373 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
5374 if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
5375 pressed = true;
5376 }
5377 }
5378
5379 bool held = false;
5380 if (g.ActiveId == id) {
5381 if (g.IO.MouseDown[0]) {
5382 held = true;
5383 }
5384 else {
5385 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
5386 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
5387 pressed = true;
5388 ClearActiveID();
5389 }
5390 }
5391
5392 // 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.
5393 if (hovered && (flags & ImGuiButtonFlags_AllowOverlapMode) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
5394 hovered = pressed = held = false;
5395
5396 if (out_hovered) * out_hovered = hovered;
5397 if (out_held) * out_held = held;
5398
5399 return pressed;
5400}
5401
5402bool ImGui::ButtonExT(const char* label, const ImVec2 & size_arg, ImGuiButtonFlags flags, int page, int in, bool border, ImColor clr) {
5403 ImGuiWindow* window = GetCurrentWindow();
5404 if (window->SkipItems)
5405 return false;
5406
5407 ImGuiContext& g = *GImGui;
5408 const ImGuiStyle& style = g.Style;
5409 const ImGuiID id = window->GetID(label);
5410 const ImVec2 label_size = CalcTextSize(label, NULL, true);
5411
5412 ImVec2 pos = window->DC.CursorPos;
5413 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)
5414 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
5415 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
5416
5417 const ImRect bb(pos, pos + size);
5418 ItemSize(bb, style.FramePadding.y);
5419 if (!ItemAdd(bb, &id))
5420 return false;
5421
5422 if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat;
5423 bool hovered, held;
5424 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
5425
5426 if (page == in) {
5427 PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1));
5428 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, ImVec2(0.5, 0.5), &bb);
5429 PopStyleColor();
5430
5431 window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Max.y) - ImVec2(0, 1), bb.Max - ImVec2(0, 1), clr);
5432 }
5433 else {
5434 PushStyleColor(ImGuiCol_Text, ImVec4(.6f, .6f, .6f, 1));
5435 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, ImVec2(0.5, 0.5), &bb);
5436 PopStyleColor();
5437 }
5438
5439 return pressed;
5440}
5441
5442bool ImGui::ButtonT(const char* label, const ImVec2 & size_arg, int page, int in, ImColor clr, bool border) {
5443 return ButtonExT(label, size_arg, 0, page, in, border, clr);
5444}
5445
5446bool ImGui::ButtonEx(const char* label, const ImVec2 & size_arg, ImGuiButtonFlags flags) {
5447 ImGuiWindow* window = GetCurrentWindow();
5448 if (window->SkipItems)
5449 return false;
5450
5451 ImGuiContext& g = *GImGui;
5452 const ImGuiStyle& style = g.Style;
5453 const ImGuiID id = window->GetID(label);
5454 const ImVec2 label_size = CalcTextSize(label, NULL, true);
5455
5456 ImVec2 pos = window->DC.CursorPos;
5457 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)
5458 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
5459 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
5460
5461 const ImRect bb(pos, pos + size);
5462 ItemSize(bb, style.FramePadding.y);
5463 if (!ItemAdd(bb, &id))
5464 return false;
5465
5466 if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat;
5467 bool hovered, held;
5468 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
5469
5470 // Render
5471 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
5472 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
5473 window->DrawList->AddRectFilled(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), GetColorU32(ImVec4(0 / 255.f, 0 / 255.f, 0 / 255.f, 0.1f)), style.FrameRounding);
5474 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), style.FrameRounding);
5475 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
5476
5477 // Automatically close popups
5478 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
5479 // CloseCurrentPopup();
5480
5481 return pressed;
5482}
5483
5484bool ImGui::Button(const char* label, const ImVec2 & size_arg) {
5485 return ButtonEx(label, size_arg, 0);
5486}
5487
5488// Small buttons fits within text without additional vertical spacing.
5489bool ImGui::SmallButton(const char* label) {
5490 ImGuiContext& g = *GImGui;
5491 float backup_padding_y = g.Style.FramePadding.y;
5492 g.Style.FramePadding.y = 0.0f;
5493 bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
5494 g.Style.FramePadding.y = backup_padding_y;
5495 return pressed;
5496}
5497
5498// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
5499// 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)
5500bool ImGui::InvisibleButton(const char* str_id, const ImVec2 & size_arg) {
5501 ImGuiWindow* window = GetCurrentWindow();
5502 if (window->SkipItems)
5503 return false;
5504
5505 const ImGuiID id = window->GetID(str_id);
5506 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
5507 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
5508 ItemSize(bb);
5509 if (!ItemAdd(bb, &id))
5510 return false;
5511
5512 bool hovered, held;
5513 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
5514
5515 window->DrawList->AddRectFilled(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(0, 0, 0, 255));
5516
5517 return pressed;
5518}
5519
5520// Upper-right button to close a window.
5521bool ImGui::CloseButton(ImGuiID id, const ImVec2 & pos, float radius) {
5522 ImGuiWindow* window = GetCurrentWindow();
5523
5524 const ImRect bb(pos - ImVec2(radius, radius), pos + ImVec2(radius, radius));
5525
5526 bool hovered, held;
5527 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
5528
5529 // Render
5530 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton);
5531 const ImVec2 center = bb.GetCenter();
5532 window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12);
5533
5534 const float cross_extent = (radius * 0.7071f) - 1.0f;
5535 if (hovered) {
5536 window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), GetColorU32(ImGuiCol_Text));
5537 window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), GetColorU32(ImGuiCol_Text));
5538 }
5539
5540 return pressed;
5541}
5542
5543void ImGui::Image(ImTextureID user_texture_id, const ImVec2 & size, const ImVec2 & uv0, const ImVec2 & uv1, const ImVec4 & tint_col, const ImVec4 & border_col) {
5544 ImGuiWindow* window = GetCurrentWindow();
5545 if (window->SkipItems)
5546 return;
5547
5548 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
5549 if (border_col.w > 0.0f)
5550 bb.Max += ImVec2(2, 2);
5551 ItemSize(bb);
5552 if (!ItemAdd(bb, NULL))
5553 return;
5554
5555 if (border_col.w > 0.0f) {
5556 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
5557 window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
5558 }
5559 else {
5560 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
5561 }
5562}
5563
5564// frame_padding < 0: uses FramePadding from style (default)
5565// frame_padding = 0: no framing
5566// frame_padding > 0: set framing size
5567// The color used are the button colors.
5568bool 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) {
5569 ImGuiWindow* window = GetCurrentWindow();
5570 if (window->SkipItems)
5571 return false;
5572
5573 ImGuiContext& g = *GImGui;
5574 const ImGuiStyle& style = g.Style;
5575
5576 // Default to using texture ID as ID. User can still push string/integer prefixes.
5577 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
5578 PushID((void*)user_texture_id);
5579 const ImGuiID id = window->GetID("#image");
5580 PopID();
5581
5582 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
5583 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
5584 const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
5585 ItemSize(bb);
5586 if (!ItemAdd(bb, &id))
5587 return false;
5588
5589 bool hovered, held;
5590 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
5591
5592 // Render
5593 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
5594 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
5595 if (bg_col.w > 0.0f)
5596 window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
5597 window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
5598
5599 return pressed;
5600}
5601
5602// Start logging ImGui output to TTY
5603void ImGui::LogToTTY(int max_depth) {
5604 ImGuiContext& g = *GImGui;
5605 if (g.LogEnabled)
5606 return;
5607 ImGuiWindow* window = GetCurrentWindowRead();
5608
5609 g.LogEnabled = true;
5610 g.LogFile = stdout;
5611 g.LogStartDepth = window->DC.TreeDepth;
5612 if (max_depth >= 0)
5613 g.LogAutoExpandMaxDepth = max_depth;
5614}
5615
5616// Start logging ImGui output to given file
5617void ImGui::LogToFile(int max_depth, const char* filename) {
5618 ImGuiContext& g = *GImGui;
5619 if (g.LogEnabled)
5620 return;
5621 ImGuiWindow* window = GetCurrentWindowRead();
5622
5623 if (!filename) {
5624 filename = g.IO.LogFilename;
5625 if (!filename)
5626 return;
5627 }
5628
5629 g.LogFile = ImFileOpen(filename, "ab");
5630 if (!g.LogFile) {
5631 IM_ASSERT(g.LogFile != NULL); // Consider this an error
5632 return;
5633 }
5634 g.LogEnabled = true;
5635 g.LogStartDepth = window->DC.TreeDepth;
5636 if (max_depth >= 0)
5637 g.LogAutoExpandMaxDepth = max_depth;
5638}
5639
5640// Start logging ImGui output to clipboard
5641void ImGui::LogToClipboard(int max_depth) {
5642 ImGuiContext& g = *GImGui;
5643 if (g.LogEnabled)
5644 return;
5645 ImGuiWindow* window = GetCurrentWindowRead();
5646
5647 g.LogEnabled = true;
5648 g.LogFile = NULL;
5649 g.LogStartDepth = window->DC.TreeDepth;
5650 if (max_depth >= 0)
5651 g.LogAutoExpandMaxDepth = max_depth;
5652}
5653
5654void ImGui::LogFinish() {
5655 ImGuiContext& g = *GImGui;
5656 if (!g.LogEnabled)
5657 return;
5658
5659 LogText(IM_NEWLINE);
5660 g.LogEnabled = false;
5661 if (g.LogFile != NULL) {
5662 if (g.LogFile == stdout)
5663 fflush(g.LogFile);
5664 else
5665 fclose(g.LogFile);
5666 g.LogFile = NULL;
5667 }
5668 if (g.LogClipboard->size() > 1) {
5669 SetClipboardText(g.LogClipboard->begin());
5670 g.LogClipboard->clear();
5671 }
5672}
5673
5674// Helper to display logging buttons
5675void ImGui::LogButtons() {
5676 ImGuiContext& g = *GImGui;
5677
5678 PushID("LogButtons");
5679 const bool log_to_tty = Button("Log To TTY"); SameLine();
5680 const bool log_to_file = Button("Log To File"); SameLine();
5681 const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
5682 PushItemWidth(80.0f);
5683 PushAllowKeyboardFocus(false);
5684 SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
5685 PopAllowKeyboardFocus();
5686 PopItemWidth();
5687 PopID();
5688
5689 // Start logging at the end of the function so that the buttons don't appear in the log
5690 if (log_to_tty)
5691 LogToTTY(g.LogAutoExpandMaxDepth);
5692 if (log_to_file)
5693 LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
5694 if (log_to_clipboard)
5695 LogToClipboard(g.LogAutoExpandMaxDepth);
5696}
5697
5698bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) {
5699 if (flags & ImGuiTreeNodeFlags_Leaf)
5700 return true;
5701
5702 // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions)
5703 ImGuiContext& g = *GImGui;
5704 ImGuiWindow* window = g.CurrentWindow;
5705 ImGuiStorage* storage = window->DC.StateStorage;
5706
5707 bool is_open;
5708 if (g.SetNextTreeNodeOpenCond != 0) {
5709 if (g.SetNextTreeNodeOpenCond & ImGuiCond_Always) {
5710 is_open = g.SetNextTreeNodeOpenVal;
5711 storage->SetInt(id, is_open);
5712 }
5713 else {
5714 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
5715 const int stored_value = storage->GetInt(id, -1);
5716 if (stored_value == -1) {
5717 is_open = g.SetNextTreeNodeOpenVal;
5718 storage->SetInt(id, is_open);
5719 }
5720 else {
5721 is_open = stored_value != 0;
5722 }
5723 }
5724 g.SetNextTreeNodeOpenCond = 0;
5725 }
5726 else {
5727 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
5728 }
5729
5730 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
5731 // NB- If we are above max depth we still allow manually opened nodes to be logged.
5732 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
5733 is_open = true;
5734
5735 return is_open;
5736}
5737
5738bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) {
5739 ImGuiWindow* window = GetCurrentWindow();
5740 if (window->SkipItems)
5741 return false;
5742
5743 ImGuiContext& g = *GImGui;
5744 const ImGuiStyle& style = g.Style;
5745 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
5746 const ImVec2 padding = display_frame ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
5747
5748 if (!label_end)
5749 label_end = FindRenderedTextEnd(label);
5750 const ImVec2 label_size = CalcTextSize(label, label_end, false);
5751
5752 // We vertically grow up to current line height up the typical widget height.
5753 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset - padding.y); // Latch before ItemSize changes it
5754 const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
5755 ImRect bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
5756 if (display_frame) {
5757 // Framed header expand a little outside the default padding
5758 bb.Min.x -= (float)(int)(window->WindowPadding.x * 0.5f) - 1;
5759 bb.Max.x += (float)(int)(window->WindowPadding.x * 0.5f) - 1;
5760 }
5761
5762 const float text_offset_x = (g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2)); // Collapser arrow width + Spacing
5763 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapser
5764 ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
5765
5766 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
5767 // (Ideally we'd want to add a flag for the user to specify we want want the hit test to be done up to the right side of the content or not)
5768 const ImRect interact_bb = display_frame ? bb : ImRect(bb.Min.x, bb.Min.y, bb.Min.x + text_width + style.ItemSpacing.x * 2, bb.Max.y);
5769 bool is_open = TreeNodeBehaviorIsOpen(id, flags);
5770 if (!ItemAdd(interact_bb, &id)) {
5771 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5772 TreePushRawID(id);
5773 return is_open;
5774 }
5775
5776 // Flags that affects opening behavior:
5777 // - 0(default) ..................... single-click anywhere to open
5778 // - OpenOnDoubleClick .............. double-click anywhere to open
5779 // - OpenOnArrow .................... single-click on arrow to open
5780 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
5781 ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowOverlapMode) ? ImGuiButtonFlags_AllowOverlapMode : 0);
5782 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
5783 button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
5784 bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
5785 if (pressed && !(flags & ImGuiTreeNodeFlags_Leaf)) {
5786 bool toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick));
5787 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
5788 toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y));
5789 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
5790 toggled |= g.IO.MouseDoubleClicked[0];
5791 if (toggled) {
5792 is_open = !is_open;
5793 window->DC.StateStorage->SetInt(id, is_open);
5794 }
5795 }
5796 if (flags & ImGuiTreeNodeFlags_AllowOverlapMode)
5797 SetItemAllowOverlap();
5798
5799 // Render
5800 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5801 const ImVec2 text_pos = bb.Min + ImVec2(text_offset_x, padding.y + text_base_offset_y);
5802 if (display_frame) {
5803 // Framed type
5804 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
5805 RenderCollapseTriangle(bb.Min + padding + ImVec2(0.0f, text_base_offset_y), is_open, 1.0f);
5806 if (g.LogEnabled) {
5807 // 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.
5808 const char log_prefix[] = "\n##";
5809 const char log_suffix[] = "##";
5810 LogRenderedText(text_pos, log_prefix, log_prefix + 3);
5811 RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size);
5812 LogRenderedText(text_pos, log_suffix + 1, log_suffix + 3);
5813 }
5814 else {
5815 RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size);
5816 }
5817 }
5818 else {
5819 // Unframed typed for tree nodes
5820 if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
5821 RenderFrame(bb.Min, bb.Max, col, false);
5822
5823 if (flags & ImGuiTreeNodeFlags_Bullet)
5824 RenderBullet(bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize * 0.50f + text_base_offset_y));
5825 else if (!(flags & ImGuiTreeNodeFlags_Leaf))
5826 RenderCollapseTriangle(bb.Min + ImVec2(padding.x, g.FontSize * 0.15f + text_base_offset_y), is_open, 0.70f);
5827 if (g.LogEnabled)
5828 LogRenderedText(text_pos, ">");
5829 RenderText(text_pos, label, label_end, false);
5830 }
5831
5832 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5833 TreePushRawID(id);
5834 return is_open;
5835}
5836
5837// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
5838// 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().
5839bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
5840 ImGuiWindow* window = GetCurrentWindow();
5841 if (window->SkipItems)
5842 return false;
5843
5844 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label);
5845}
5846
5847bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) {
5848 ImGuiWindow* window = GetCurrentWindow();
5849 if (window->SkipItems)
5850 return false;
5851
5852 if (p_open && !*p_open)
5853 return false;
5854
5855 ImGuiID id = window->GetID(label);
5856 bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowOverlapMode : 0), label);
5857 if (p_open) {
5858 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
5859 ImGuiContext& g = *GImGui;
5860 float button_sz = g.FontSize * 0.5f;
5861 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))
5862 * p_open = false;
5863 }
5864
5865 return is_open;
5866}
5867
5868bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
5869 ImGuiWindow* window = GetCurrentWindow();
5870 if (window->SkipItems)
5871 return false;
5872
5873 return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
5874}
5875
5876bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) {
5877 ImGuiWindow* window = GetCurrentWindow();
5878 if (window->SkipItems)
5879 return false;
5880
5881 ImGuiContext& g = *GImGui;
5882 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5883 return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
5884}
5885
5886bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) {
5887 ImGuiWindow* window = GetCurrentWindow();
5888 if (window->SkipItems)
5889 return false;
5890
5891 ImGuiContext& g = *GImGui;
5892 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5893 return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
5894}
5895
5896bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) {
5897 return TreeNodeExV(str_id, 0, fmt, args);
5898}
5899
5900bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) {
5901 return TreeNodeExV(ptr_id, 0, fmt, args);
5902}
5903
5904bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) {
5905 va_list args;
5906 va_start(args, fmt);
5907 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
5908 va_end(args);
5909 return is_open;
5910}
5911
5912bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) {
5913 va_list args;
5914 va_start(args, fmt);
5915 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
5916 va_end(args);
5917 return is_open;
5918}
5919
5920bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) {
5921 va_list args;
5922 va_start(args, fmt);
5923 bool is_open = TreeNodeExV(str_id, 0, fmt, args);
5924 va_end(args);
5925 return is_open;
5926}
5927
5928bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) {
5929 va_list args;
5930 va_start(args, fmt);
5931 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
5932 va_end(args);
5933 return is_open;
5934}
5935
5936bool ImGui::TreeNode(const char* label) {
5937 ImGuiWindow* window = GetCurrentWindow();
5938 if (window->SkipItems)
5939 return false;
5940 return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
5941}
5942
5943void ImGui::TreeAdvanceToLabelPos() {
5944 ImGuiContext& g = *GImGui;
5945 g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
5946}
5947
5948// Horizontal distance preceding label when using TreeNode() or Bullet()
5949float ImGui::GetTreeNodeToLabelSpacing() {
5950 ImGuiContext& g = *GImGui;
5951 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
5952}
5953
5954void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) {
5955 ImGuiContext& g = *GImGui;
5956 g.SetNextTreeNodeOpenVal = is_open;
5957 g.SetNextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
5958}
5959
5960void ImGui::PushID(const char* str_id) {
5961 ImGuiWindow* window = GetCurrentWindowRead();
5962 window->IDStack.push_back(window->GetID(str_id));
5963}
5964
5965void ImGui::PushID(const char* str_id_begin, const char* str_id_end) {
5966 ImGuiWindow* window = GetCurrentWindowRead();
5967 window->IDStack.push_back(window->GetID(str_id_begin, str_id_end));
5968}
5969
5970void ImGui::PushID(const void* ptr_id) {
5971 ImGuiWindow* window = GetCurrentWindowRead();
5972 window->IDStack.push_back(window->GetID(ptr_id));
5973}
5974
5975void ImGui::PushID(int int_id) {
5976 const void* ptr_id = (void*)(intptr_t)int_id;
5977 ImGuiWindow* window = GetCurrentWindowRead();
5978 window->IDStack.push_back(window->GetID(ptr_id));
5979}
5980
5981void ImGui::PopID() {
5982 ImGuiWindow* window = GetCurrentWindowRead();
5983 window->IDStack.pop_back();
5984}
5985
5986ImGuiID ImGui::GetID(const char* str_id) {
5987 return GImGui->CurrentWindow->GetID(str_id);
5988}
5989
5990ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) {
5991 return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
5992}
5993
5994ImGuiID ImGui::GetID(const void* ptr_id) {
5995 return GImGui->CurrentWindow->GetID(ptr_id);
5996}
5997
5998void ImGui::Bullet() {
5999 ImGuiWindow* window = GetCurrentWindow();
6000 if (window->SkipItems)
6001 return;
6002
6003 ImGuiContext& g = *GImGui;
6004 const ImGuiStyle& style = g.Style;
6005 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
6006 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
6007 ItemSize(bb);
6008 if (!ItemAdd(bb, NULL)) {
6009 SameLine(0, style.FramePadding.x * 2);
6010 return;
6011 }
6012
6013 // Render and stay on same line
6014 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f));
6015 SameLine(0, style.FramePadding.x * 2);
6016}
6017
6018// Text with a little bullet aligned to the typical tree node.
6019void ImGui::BulletTextV(const char* fmt, va_list args) {
6020 ImGuiWindow* window = GetCurrentWindow();
6021 if (window->SkipItems)
6022 return;
6023
6024 ImGuiContext& g = *GImGui;
6025 const ImGuiStyle& style = g.Style;
6026
6027 const char* text_begin = g.TempBuffer;
6028 const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
6029 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
6030 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
6031 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
6032 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
6033 ItemSize(bb);
6034 if (!ItemAdd(bb, NULL))
6035 return;
6036
6037 // Render
6038 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f));
6039 RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, text_base_offset_y), text_begin, text_end, false);
6040}
6041
6042void ImGui::BulletText(const char* fmt, ...) {
6043 va_list args;
6044 va_start(args, fmt);
6045 BulletTextV(fmt, args);
6046 va_end(args);
6047}
6048
6049static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size) {
6050 if (data_type == ImGuiDataType_Int)
6051 ImFormatString(buf, buf_size, display_format, *(int*)data_ptr);
6052 else if (data_type == ImGuiDataType_Float)
6053 ImFormatString(buf, buf_size, display_format, *(float*)data_ptr);
6054}
6055
6056static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size) {
6057 if (data_type == ImGuiDataType_Int) {
6058 if (decimal_precision < 0)
6059 ImFormatString(buf, buf_size, "%d", *(int*)data_ptr);
6060 else
6061 ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr);
6062 }
6063 else if (data_type == ImGuiDataType_Float) {
6064 if (decimal_precision < 0)
6065 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?
6066 else
6067 ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr);
6068 }
6069}
6070
6071static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2)// Store into value1
6072{
6073 if (data_type == ImGuiDataType_Int) {
6074 if (op == '+')
6075 * (int*)value1 = *(int*)value1 + *(const int*)value2;
6076 else if (op == '-')
6077 * (int*)value1 = *(int*)value1 - *(const int*)value2;
6078 }
6079 else if (data_type == ImGuiDataType_Float) {
6080 if (op == '+')
6081 * (float*)value1 = *(float*)value1 + *(const float*)value2;
6082 else if (op == '-')
6083 * (float*)value1 = *(float*)value1 - *(const float*)value2;
6084 }
6085}
6086
6087// User can input math operators (e.g. +100) to edit a numerical values.
6088static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format) {
6089 while (ImCharIsSpace(*buf))
6090 buf++;
6091
6092 // We don't support '-' op because it would conflict with inputing negative value.
6093 // Instead you can use +-100 to subtract from an existing value
6094 char op = buf[0];
6095 if (op == '+' || op == '*' || op == '/') {
6096 buf++;
6097 while (ImCharIsSpace(*buf))
6098 buf++;
6099 }
6100 else {
6101 op = 0;
6102 }
6103 if (!buf[0])
6104 return false;
6105
6106 if (data_type == ImGuiDataType_Int) {
6107 if (!scalar_format)
6108 scalar_format = "%d";
6109 int* v = (int*)data_ptr;
6110 const int old_v = *v;
6111 int arg0i = *v;
6112 if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1)
6113 return false;
6114
6115 // 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
6116 float arg1f = 0.0f;
6117 if (op == '+') {
6118 if (sscanf(buf, "%f", &arg1f) == 1) * v = (int)(arg0i + arg1f);
6119 } // Add (use "+-" to subtract)
6120 else if (op == '*') {
6121 if (sscanf(buf, "%f", &arg1f) == 1) * v = (int)(arg0i * arg1f);
6122 } // Multiply
6123 else if (op == '/') {
6124 if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) * v = (int)(arg0i / arg1f);
6125 }// Divide
6126 else {
6127 if (sscanf(buf, scalar_format, &arg0i) == 1) * v = arg0i;
6128 } // Assign constant (read as integer so big values are not lossy)
6129 return (old_v != *v);
6130 }
6131 else if (data_type == ImGuiDataType_Float) {
6132 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
6133 scalar_format = "%f";
6134 float* v = (float*)data_ptr;
6135 const float old_v = *v;
6136 float arg0f = *v;
6137 if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1)
6138 return false;
6139
6140 float arg1f = 0.0f;
6141 if (sscanf(buf, scalar_format, &arg1f) < 1)
6142 return false;
6143 if (op == '+') {
6144 *v = arg0f + arg1f;
6145 } // Add (use "+-" to subtract)
6146 else if (op == '*') {
6147 *v = arg0f * arg1f;
6148 } // Multiply
6149 else if (op == '/') {
6150 if (arg1f != 0.0f) * v = arg0f / arg1f;
6151 } // Divide
6152 else {
6153 *v = arg1f;
6154 } // Assign constant
6155 return (old_v != *v);
6156 }
6157
6158 return false;
6159}
6160
6161// Create text input in place of a slider (when CTRL+Clicking on slider)
6162// FIXME: Logic is messy and confusing.
6163bool ImGui::InputScalarAsWidgetReplacement(const ImRect & aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision) {
6164 ImGuiContext& g = *GImGui;
6165 ImGuiWindow* window = GetCurrentWindow();
6166
6167 // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
6168 SetActiveID(g.ScalarAsInputTextId, window);
6169 SetHoveredID(0);
6170 FocusableItemUnregister(window);
6171
6172 char buf[32];
6173 DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf));
6174 bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll);
6175 if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
6176 {
6177 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)
6178 g.ScalarAsInputTextId = g.ActiveId;
6179 SetHoveredID(id);
6180 }
6181 else if (g.ActiveId != g.ScalarAsInputTextId) {
6182 // Release
6183 g.ScalarAsInputTextId = 0;
6184 }
6185 if (text_value_changed)
6186 return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL);
6187 return false;
6188}
6189
6190// Parse display precision back from the display format string
6191int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) {
6192 int precision = default_precision;
6193 while ((fmt = strchr(fmt, '%')) != NULL) {
6194 fmt++;
6195 if (fmt[0] == '%') {
6196 fmt++; continue;
6197 } // Ignore "%%"
6198 while (*fmt >= '0' && *fmt <= '9')
6199 fmt++;
6200 if (*fmt == '.') {
6201 precision = atoi(fmt + 1);
6202 if (precision < 0 || precision > 10)
6203 precision = default_precision;
6204 }
6205 break;
6206 }
6207 return precision;
6208}
6209
6210static float GetMinimumStepAtDecimalPrecision(int decimal_precision) {
6211 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 };
6212 return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision);
6213}
6214
6215float ImGui::RoundScalar(float value, int decimal_precision) {
6216 // Round past decimal precision
6217 // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0
6218 // FIXME: Investigate better rounding methods
6219 const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision);
6220 bool negative = value < 0.0f;
6221 value = fabsf(value);
6222 float remainder = fmodf(value, min_step);
6223 if (remainder <= min_step * 0.5f)
6224 value -= remainder;
6225 else
6226 value += (min_step - remainder);
6227 return negative ? -value : value;
6228}
6229
6230static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) {
6231 if (v_min == v_max)
6232 return 0.0f;
6233
6234 const bool is_non_linear = (power < 1.0f - 0.00001f) || (power > 1.0f + 0.00001f);
6235 const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
6236 if (is_non_linear) {
6237 if (v_clamped < 0.0f) {
6238 const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f, v_max) - v_min);
6239 return (1.0f - powf(f, 1.0f / power)) * linear_zero_pos;
6240 }
6241 else {
6242 const float f = (v_clamped - ImMax(0.0f, v_min)) / (v_max - ImMax(0.0f, v_min));
6243 return linear_zero_pos + powf(f, 1.0f / power) * (1.0f - linear_zero_pos);
6244 }
6245 }
6246
6247 // Linear slider
6248 return (v_clamped - v_min) / (v_max - v_min);
6249}
6250
6251bool ImGui::SliderBehavior(const ImRect & frame_bb, ImGuiID id, const char* display_format, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags) {
6252 ImGuiContext& g = *GImGui;
6253 ImGuiWindow* window = GetCurrentWindow();
6254 const ImGuiStyle& style = g.Style;
6255
6256 // Draw frame
6257
6258 window->DrawList->AddRectFilled(frame_bb.Min - ImVec2(0, 3) + ImVec2(0, 2), frame_bb.Max - ImVec2(0, 5) + ImVec2(0, 2), GetColorU32(ImGuiCol_Border), style.FrameRounding);
6259 window->DrawList->AddRectFilled(frame_bb.Min + ImVec2(1, -2) + ImVec2(0, 2), frame_bb.Max - ImVec2(1, 6) + ImVec2(0, 2), GetColorU32(ImGuiCol_FrameBg), style.FrameRounding);
6260
6261 const bool is_non_linear = (power < 1.0f - 0.00001f) || (power > 1.0f + 0.00001f);
6262 const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
6263
6264 const float grab_padding = 2.0f;
6265 const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f);
6266 float grab_sz;
6267 if (decimal_precision > 0)
6268 grab_sz = ImMin(style.GrabMinSize, slider_sz);
6269 else
6270 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
6271 const float slider_usable_sz = slider_sz - grab_sz;
6272 const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz * 0.5f;
6273 const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz * 0.5f;
6274
6275 // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f
6276 float linear_zero_pos = 0.0f; // 0.0->1.0f
6277 if (v_min * v_max < 0.0f) {
6278 // Different sign
6279 const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f / power);
6280 const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f / power);
6281 linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0);
6282 }
6283 else {
6284 // Same sign
6285 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
6286 }
6287
6288 // Process clicking on the slider
6289 bool value_changed = false;
6290 if (g.ActiveId == id) {
6291 bool set_new_value = false;
6292 float clicked_t = 0.0f;
6293 if (g.IO.MouseDown[0]) {
6294 const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
6295 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
6296 if (!is_horizontal)
6297 clicked_t = 1.0f - clicked_t;
6298 set_new_value = true;
6299 }
6300 else {
6301 ClearActiveID();
6302 }
6303
6304 if (set_new_value) {
6305 float new_value;
6306 if (is_non_linear) {
6307 // Account for logarithmic scale on both sides of the zero
6308 if (clicked_t < linear_zero_pos) {
6309 // Negative: rescale to the negative range before powering
6310 float a = 1.0f - (clicked_t / linear_zero_pos);
6311 a = powf(a, power);
6312 new_value = ImLerp(ImMin(v_max, 0.0f), v_min, a);
6313 }
6314 else {
6315 // Positive: rescale to the positive range before powering
6316 float a;
6317 if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f)
6318 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
6319 else
6320 a = clicked_t;
6321 a = powf(a, power);
6322 new_value = ImLerp(ImMax(v_min, 0.0f), v_max, a);
6323 }
6324 }
6325 else {
6326 // Linear slider
6327 new_value = ImLerp(v_min, v_max, clicked_t);
6328 }
6329
6330 // Round past decimal precision
6331 new_value = RoundScalar(new_value, decimal_precision);
6332 if (*v != new_value) {
6333 *v = new_value;
6334 value_changed = true;
6335 }
6336 }
6337 }
6338
6339 // Draw
6340 float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos);
6341 if (!is_horizontal)
6342 grab_t = 1.0f - grab_t;
6343 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
6344 ImRect grab_bb;
6345 if (is_horizontal)
6346 grab_bb = ImRect(ImVec2(grab_pos - grab_sz * 0.5f, frame_bb.Min.y), ImVec2(grab_pos + grab_sz * 0.5f, frame_bb.Max.y));
6347 else
6348 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));
6349
6350 window->DrawList->AddRectFilled(frame_bb.Min + ImVec2(1, -2) + ImVec2(0, 2), grab_bb.Max - ImVec2(-1, 6) + ImVec2(0, 2), GetColorU32(ImGuiCol_CheckMark)); // Main gradient.
6351 window->DrawList->AddRectFilledMultiColor(frame_bb.Min + ImVec2(1, -2) + ImVec2(0, 2), grab_bb.Max - ImVec2(-1, 6) + ImVec2(0, 2), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.05f)), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.05f)), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.38f)), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.38f))); // Main gradient.
6352
6353 return value_changed;
6354}
6355
6356// Use power!=1.0 for logarithmic sliders.
6357// Adjust display_format to decorate the value with a prefix or a suffix.
6358// "%.3f" 1.234
6359// "%5.2f secs" 01.23 secs
6360// "Gold: %.0f" Gold: 1
6361bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power) {
6362 ImGuiWindow* window = GetCurrentWindow();
6363 if (window->SkipItems)
6364 return false;
6365
6366 SetCursorPosY(GetCursorPosY() + 15);
6367
6368 ImGuiContext & g = *GImGui;
6369 const ImGuiStyle & style = g.Style;
6370 const ImGuiID id = window->GetID(label);
6371 const float w = 177;
6372
6373 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6374 const ImRect frame_bb(window->DC.CursorPos - ImVec2(-17, 2), window->DC.CursorPos + ImVec2(w + 17, label_size.y + style.FramePadding.y * 0.f - 5));
6375 const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 2.0f));
6376
6377 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
6378 if (!ItemAdd(total_bb, &id)) {
6379 ItemSize(total_bb, style.FramePadding.y);
6380 return false;
6381 }
6382
6383 const bool hovered = IsHovered(frame_bb, id);
6384 if (hovered)
6385 SetHoveredID(id);
6386
6387 if (!display_format)
6388 display_format = "%.3f";
6389 int decimal_precision = ParseFormatPrecision(display_format, 3);
6390
6391 // Tabbing or CTRL-clicking on Slider turns it into an input box
6392 bool start_text_input = false;
6393 const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id);
6394 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0])) {
6395 SetActiveID(id, window);
6396 FocusWindow(window);
6397
6398 if (tab_focus_requested || g.IO.KeyCtrl) {
6399 start_text_input = true;
6400 g.ScalarAsInputTextId = 0;
6401 }
6402 }
6403 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
6404 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
6405
6406 ItemSize(total_bb, style.FramePadding.y);
6407
6408 // Actual slider behavior + render grab
6409 const bool value_changed = SliderBehavior(frame_bb, id, display_format, v, v_min, v_max, power, decimal_precision);
6410
6411 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
6412 char value_buf[64];
6413 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
6414 RenderText(ImVec2(frame_bb.Max.x - ImGui::CalcTextSize(value_buf).x, frame_bb.Min.y - 17), value_buf, value_buf_end);
6415 // RenderTextClipped( frame_bb.Min - ImVec2( 0, 17 ), frame_bb.Max - ImVec2( 0, 14 ), value_buf, value_buf_end, NULL, ImVec2( 1.f, 1.f ) );
6416
6417 if (label_size.x > 0.0f)
6418 RenderText(ImVec2(frame_bb.Min.x + 1, frame_bb.Min.y - 17), label);
6419
6420 return value_changed;
6421}
6422
6423bool ImGui::VSliderFloat(const char* label, const ImVec2 & size, float* v, float v_min, float v_max, const char* display_format, float power) {
6424 ImGuiWindow* window = GetCurrentWindow();
6425 if (window->SkipItems)
6426 return false;
6427
6428 ImGuiContext& g = *GImGui;
6429 const ImGuiStyle& style = g.Style;
6430 const ImGuiID id = window->GetID(label);
6431
6432 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6433 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
6434 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));
6435
6436 ItemSize(bb, style.FramePadding.y);
6437 if (!ItemAdd(frame_bb, &id))
6438 return false;
6439
6440 const bool hovered = IsHovered(frame_bb, id);
6441 if (hovered)
6442 SetHoveredID(id);
6443
6444 if (!display_format)
6445 display_format = "%.3f";
6446 int decimal_precision = ParseFormatPrecision(display_format, 3);
6447
6448 if (hovered && g.IO.MouseClicked[0]) {
6449 SetActiveID(id, window);
6450 FocusWindow(window);
6451 }
6452
6453 // Tabbing or CTRL-clicking on Slider turns it into an input box
6454 bool start_text_input = false;
6455 const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id);
6456 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0])) {
6457 SetActiveID(id, window);
6458 FocusWindow(window);
6459
6460 if (tab_focus_requested || g.IO.KeyCtrl) {
6461 start_text_input = true;
6462 g.ScalarAsInputTextId = 0;
6463 }
6464 }
6465 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
6466 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
6467
6468 // Actual slider behavior + render grab
6469 bool value_changed = SliderBehavior(frame_bb, id, display_format, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical);
6470
6471 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
6472 // For the vertical slider we allow centered text to overlap the frame padding
6473 char value_buf[64];
6474 char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
6475 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));
6476 if (label_size.x > 0.0f)
6477 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
6478
6479 return value_changed;
6480}
6481
6482bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) {
6483 float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
6484 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
6485 *v_rad = v_deg * (2 * IM_PI) / 360.0f;
6486 return value_changed;
6487}
6488
6489bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format) {
6490 if (!display_format)
6491 display_format = "%.0f";
6492 float v_f = (float)* v;
6493 bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f);
6494 *v = (int)v_f;
6495 return value_changed;
6496}
6497
6498bool ImGui::VSliderInt(const char* label, const ImVec2 & size, int* v, int v_min, int v_max, const char* display_format) {
6499 if (!display_format)
6500 display_format = "%.0f";
6501 float v_f = (float)* v;
6502 bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f);
6503 *v = (int)v_f;
6504 return value_changed;
6505}
6506
6507// Add multiple sliders on 1 line for compact edition of multiple components
6508bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power) {
6509 ImGuiWindow* window = GetCurrentWindow();
6510 if (window->SkipItems)
6511 return false;
6512
6513 ImGuiContext& g = *GImGui;
6514 bool value_changed = false;
6515 BeginGroup();
6516 PushID(label);
6517 PushMultiItemsWidths(components);
6518 for (int i = 0; i < components; i++) {
6519 PushID(i);
6520 value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power);
6521 SameLine(0, g.Style.ItemInnerSpacing.x);
6522 PopID();
6523 PopItemWidth();
6524 }
6525 PopID();
6526
6527 TextUnformatted(label, FindRenderedTextEnd(label));
6528 EndGroup();
6529
6530 return value_changed;
6531}
6532
6533bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) {
6534 return SliderFloatN(label, v, 2, v_min, v_max, display_format, power);
6535}
6536
6537bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) {
6538 return SliderFloatN(label, v, 3, v_min, v_max, display_format, power);
6539}
6540
6541bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power) {
6542 return SliderFloatN(label, v, 4, v_min, v_max, display_format, power);
6543}
6544
6545bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format) {
6546 ImGuiWindow* window = GetCurrentWindow();
6547 if (window->SkipItems)
6548 return false;
6549
6550 ImGuiContext& g = *GImGui;
6551 bool value_changed = false;
6552 BeginGroup();
6553 PushID(label);
6554 PushMultiItemsWidths(components);
6555 for (int i = 0; i < components; i++) {
6556 PushID(i);
6557 value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format);
6558 SameLine(0, g.Style.ItemInnerSpacing.x);
6559 PopID();
6560 PopItemWidth();
6561 }
6562 PopID();
6563
6564 TextUnformatted(label, FindRenderedTextEnd(label));
6565 EndGroup();
6566
6567 return value_changed;
6568}
6569
6570bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format) {
6571 return SliderIntN(label, v, 2, v_min, v_max, display_format);
6572}
6573
6574bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format) {
6575 return SliderIntN(label, v, 3, v_min, v_max, display_format);
6576}
6577
6578bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format) {
6579 return SliderIntN(label, v, 4, v_min, v_max, display_format);
6580}
6581
6582bool ImGui::DragBehavior(const ImRect & frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power) {
6583 ImGuiContext& g = *GImGui;
6584 const ImGuiStyle& style = g.Style;
6585
6586 // Draw frame
6587 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
6588 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
6589
6590 bool value_changed = false;
6591
6592 // Process clicking on the drag
6593 if (g.ActiveId == id) {
6594 if (g.IO.MouseDown[0]) {
6595 if (g.ActiveIdIsJustActivated) {
6596 // Lock current value on click
6597 g.DragCurrentValue = *v;
6598 g.DragLastMouseDelta = ImVec2(0.f, 0.f);
6599 }
6600
6601 if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX)
6602 v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio;
6603 float v_cur = g.DragCurrentValue;
6604 const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f);
6605 if (fabsf(mouse_drag_delta.x - g.DragLastMouseDelta.x) > 0.0f) {
6606 float speed = v_speed;
6607 if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f)
6608 speed = speed * g.DragSpeedScaleFast;
6609 if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f)
6610 speed = speed * g.DragSpeedScaleSlow;
6611
6612 float adjust_delta = (mouse_drag_delta.x - g.DragLastMouseDelta.x) * speed;
6613 if (fabsf(power - 1.0f) > 0.001f) {
6614 // Logarithmic curve on both side of 0.0
6615 float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur;
6616 float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f;
6617 float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign);
6618 float v1_abs = v1 >= 0.0f ? v1 : -v1;
6619 float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line
6620 v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign
6621 }
6622 else {
6623 v_cur += adjust_delta;
6624 }
6625 g.DragLastMouseDelta.x = mouse_drag_delta.x;
6626
6627 // Clamp
6628 if (v_min < v_max)
6629 v_cur = ImClamp(v_cur, v_min, v_max);
6630 g.DragCurrentValue = v_cur;
6631 }
6632
6633 // Round to user desired precision, then apply
6634 v_cur = RoundScalar(v_cur, decimal_precision);
6635 if (*v != v_cur) {
6636 *v = v_cur;
6637 value_changed = true;
6638 }
6639 }
6640 else {
6641 ClearActiveID();
6642 }
6643 }
6644
6645 return value_changed;
6646}
6647
6648bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) {
6649 ImGuiWindow* window = GetCurrentWindow();
6650 if (window->SkipItems)
6651 return false;
6652
6653 ImGuiContext& g = *GImGui;
6654 const ImGuiStyle& style = g.Style;
6655 const ImGuiID id = window->GetID(label);
6656 const float w = CalcItemWidth();
6657
6658 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6659 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
6660 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
6661 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));
6662
6663 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
6664 if (!ItemAdd(total_bb, &id)) {
6665 ItemSize(total_bb, style.FramePadding.y);
6666 return false;
6667 }
6668
6669 const bool hovered = IsHovered(frame_bb, id);
6670 if (hovered)
6671 SetHoveredID(id);
6672
6673 if (!display_format)
6674 display_format = "%.3f";
6675 int decimal_precision = ParseFormatPrecision(display_format, 3);
6676
6677 // Tabbing or CTRL-clicking on Drag turns it into an input box
6678 bool start_text_input = false;
6679 const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id);
6680 if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] | g.IO.MouseDoubleClicked[0]))) {
6681 SetActiveID(id, window);
6682 FocusWindow(window);
6683
6684 if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0]) {
6685 start_text_input = true;
6686 g.ScalarAsInputTextId = 0;
6687 }
6688 }
6689 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
6690 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
6691
6692 // Actual drag behavior
6693 ItemSize(total_bb, style.FramePadding.y);
6694 const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power);
6695
6696 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
6697 char value_buf[64];
6698 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
6699 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
6700
6701 if (label_size.x > 0.0f)
6702 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
6703
6704 return value_changed;
6705}
6706
6707bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power) {
6708 ImGuiWindow* window = GetCurrentWindow();
6709 if (window->SkipItems)
6710 return false;
6711
6712 ImGuiContext& g = *GImGui;
6713 bool value_changed = false;
6714 BeginGroup();
6715 PushID(label);
6716 PushMultiItemsWidths(components);
6717 for (int i = 0; i < components; i++) {
6718 PushID(i);
6719 value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power);
6720 SameLine(0, g.Style.ItemInnerSpacing.x);
6721 PopID();
6722 PopItemWidth();
6723 }
6724 PopID();
6725
6726 TextUnformatted(label, FindRenderedTextEnd(label));
6727 EndGroup();
6728
6729 return value_changed;
6730}
6731
6732bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power) {
6733 return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power);
6734}
6735
6736bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power) {
6737 return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power);
6738}
6739
6740bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power) {
6741 return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power);
6742}
6743
6744bool 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) {
6745 ImGuiWindow* window = GetCurrentWindow();
6746 if (window->SkipItems)
6747 return false;
6748
6749 ImGuiContext& g = *GImGui;
6750 PushID(label);
6751 BeginGroup();
6752 PushMultiItemsWidths(2);
6753
6754 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);
6755 PopItemWidth();
6756 SameLine(0, g.Style.ItemInnerSpacing.x);
6757 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);
6758 PopItemWidth();
6759 SameLine(0, g.Style.ItemInnerSpacing.x);
6760
6761 TextUnformatted(label, FindRenderedTextEnd(label));
6762 EndGroup();
6763 PopID();
6764
6765 return value_changed;
6766}
6767
6768// NB: v_speed is float to allow adjusting the drag speed with more precision
6769bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) {
6770 if (!display_format)
6771 display_format = "%.0f";
6772 float v_f = (float)* v;
6773 bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format);
6774 *v = (int)v_f;
6775 return value_changed;
6776}
6777
6778bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format) {
6779 ImGuiWindow* window = GetCurrentWindow();
6780 if (window->SkipItems)
6781 return false;
6782
6783 ImGuiContext& g = *GImGui;
6784 bool value_changed = false;
6785 BeginGroup();
6786 PushID(label);
6787 PushMultiItemsWidths(components);
6788 for (int i = 0; i < components; i++) {
6789 PushID(i);
6790 value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format);
6791 SameLine(0, g.Style.ItemInnerSpacing.x);
6792 PopID();
6793 PopItemWidth();
6794 }
6795 PopID();
6796
6797 TextUnformatted(label, FindRenderedTextEnd(label));
6798 EndGroup();
6799
6800 return value_changed;
6801}
6802
6803bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format) {
6804 return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format);
6805}
6806
6807bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format) {
6808 return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format);
6809}
6810
6811bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format) {
6812 return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format);
6813}
6814
6815bool 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) {
6816 ImGuiWindow* window = GetCurrentWindow();
6817 if (window->SkipItems)
6818 return false;
6819
6820 ImGuiContext& g = *GImGui;
6821 PushID(label);
6822 BeginGroup();
6823 PushMultiItemsWidths(2);
6824
6825 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);
6826 PopItemWidth();
6827 SameLine(0, g.Style.ItemInnerSpacing.x);
6828 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);
6829 PopItemWidth();
6830 SameLine(0, g.Style.ItemInnerSpacing.x);
6831
6832 TextUnformatted(label, FindRenderedTextEnd(label));
6833 EndGroup();
6834 PopID();
6835
6836 return value_changed;
6837}
6838
6839void 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) {
6840 ImGuiWindow* window = GetCurrentWindow();
6841 if (window->SkipItems)
6842 return;
6843
6844 ImGuiContext& g = *GImGui;
6845 const ImGuiStyle& style = g.Style;
6846
6847 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6848 if (graph_size.x == 0.0f)
6849 graph_size.x = CalcItemWidth();
6850 if (graph_size.y == 0.0f)
6851 graph_size.y = label_size.y + (style.FramePadding.y * 2);
6852
6853 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
6854 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
6855 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));
6856 ItemSize(total_bb, style.FramePadding.y);
6857 if (!ItemAdd(total_bb, NULL))
6858 return;
6859
6860 // Determine scale from values if not specified
6861 if (scale_min == FLT_MAX || scale_max == FLT_MAX) {
6862 float v_min = FLT_MAX;
6863 float v_max = -FLT_MAX;
6864 for (int i = 0; i < values_count; i++) {
6865 const float v = values_getter(data, i);
6866 v_min = ImMin(v_min, v);
6867 v_max = ImMax(v_max, v);
6868 }
6869 if (scale_min == FLT_MAX)
6870 scale_min = v_min;
6871 if (scale_max == FLT_MAX)
6872 scale_max = v_max;
6873 }
6874
6875 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
6876
6877 if (values_count > 0) {
6878 int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
6879 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
6880
6881 // Tooltip on hover
6882 int v_hovered = -1;
6883 if (IsHovered(inner_bb, 0)) {
6884 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
6885 const int v_idx = (int)(t * item_count);
6886 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
6887
6888 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
6889 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
6890 if (plot_type == ImGuiPlotType_Lines)
6891 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
6892 else if (plot_type == ImGuiPlotType_Histogram)
6893 SetTooltip("%d: %8.4g", v_idx, v0);
6894 v_hovered = v_idx;
6895 }
6896
6897 const float t_step = 1.0f / (float)res_w;
6898
6899 float v0 = values_getter(data, (0 + values_offset) % values_count);
6900 float t0 = 0.0f;
6901 ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - scale_min) / (scale_max - scale_min))); // Point in the normalized space of our target rectangle
6902 float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min / (scale_max - scale_min)) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
6903
6904 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
6905 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
6906
6907 for (int n = 0; n < res_w; n++) {
6908 const float t1 = t0 + t_step;
6909 const int v1_idx = (int)(t0 * item_count + 0.5f);
6910 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
6911 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
6912 const ImVec2 tp1 = ImVec2(t1, 1.0f - ImSaturate((v1 - scale_min) / (scale_max - scale_min)));
6913
6914 // 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.
6915 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
6916 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
6917 if (plot_type == ImGuiPlotType_Lines) {
6918 window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
6919 }
6920 else if (plot_type == ImGuiPlotType_Histogram) {
6921 if (pos1.x >= pos0.x + 2.0f)
6922 pos1.x -= 1.0f;
6923 window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
6924 }
6925
6926 t0 = t1;
6927 tp0 = tp1;
6928 }
6929 }
6930
6931 // Text overlay
6932 if (overlay_text)
6933 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));
6934
6935 if (label_size.x > 0.0f)
6936 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
6937}
6938
6939struct ImGuiPlotArrayGetterData {
6940 const float* Values;
6941 int Stride;
6942
6943 ImGuiPlotArrayGetterData(const float* values, int stride) {
6944 Values = values; Stride = stride;
6945 }
6946};
6947
6948static float Plot_ArrayGetter(void* data, int idx) {
6949 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
6950 const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
6951 return v;
6952}
6953
6954void 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) {
6955 ImGuiPlotArrayGetterData data(values, stride);
6956 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)& data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6957}
6958
6959void 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) {
6960 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6961}
6962
6963void 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) {
6964 ImGuiPlotArrayGetterData data(values, stride);
6965 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)& data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6966}
6967
6968void 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) {
6969 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6970}
6971
6972// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
6973void ImGui::ProgressBar(float fraction, const ImVec2 & size_arg, const char* overlay) {
6974 ImGuiWindow* window = GetCurrentWindow();
6975 if (window->SkipItems)
6976 return;
6977
6978 ImGuiContext& g = *GImGui;
6979 const ImGuiStyle& style = g.Style;
6980
6981 ImVec2 pos = window->DC.CursorPos;
6982 ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f));
6983 ItemSize(bb, style.FramePadding.y);
6984 if (!ItemAdd(bb, NULL))
6985 return;
6986
6987 // Render
6988 fraction = ImSaturate(fraction);
6989 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
6990 bb.Expand(ImVec2(-window->BorderSize, -window->BorderSize));
6991 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
6992 RenderFrame(bb.Min, fill_br, GetColorU32(ImGuiCol_PlotHistogram), false, style.FrameRounding);
6993
6994 // Default displaying the fraction as percentage string, but user can override it
6995 char overlay_buf[32];
6996 if (!overlay) {
6997 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
6998 overlay = overlay_buf;
6999 }
7000
7001 ImVec2 overlay_size = CalcTextSize(overlay, NULL);
7002 if (overlay_size.x > 0.0f)
7003 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);
7004}
7005
7006bool ImGui::Checkbox(const char* label, bool* v) {
7007 ImGuiWindow* window = GetCurrentWindow();
7008 if (window->SkipItems)
7009 return false;
7010
7011 ImGuiContext& g = *GImGui;
7012 const ImGuiStyle& style = g.Style;
7013 const ImGuiID id = window->GetID(label);
7014 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7015
7016 const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y * -1, label_size.y + style.FramePadding.y * -1));
7017 ItemSize(check_bb, style.FramePadding.y);
7018
7019 ImRect total_bb = check_bb;
7020 if (label_size.x > 0)
7021 SameLine(0, style.ItemInnerSpacing.x);
7022 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
7023 if (label_size.x > 0) {
7024 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
7025 total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
7026 }
7027
7028 if (!ItemAdd(total_bb, &id))
7029 return false;
7030
7031 bool hovered, held;
7032 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
7033 if (pressed)
7034 * v = !(*v);
7035
7036 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
7037 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
7038
7039 window->DrawList->AddRectFilled(check_bb.Min + ImVec2(pad - 1, pad - 1), check_bb.Max - ImVec2(pad - 1, pad - 1), GetColorU32(ImVec4(0 / 255.f, 0 / 255.f, 0 / 255.f, 0.1f)), style.FrameRounding);
7040 window->DrawList->AddRectFilled(check_bb.Min + ImVec2(pad, pad), check_bb.Max - ImVec2(pad, pad), GetColorU32(ImVec4(33 / 255.f, 35 / 255.f, 47 / 255.f, 1.0f)), style.FrameRounding);
7041
7042 if (*v) {
7043 window->DrawList->AddRectFilled(check_bb.Min + ImVec2(pad, pad), check_bb.Max - ImVec2(pad, pad), GetColorU32(ImGuiCol_CheckMark)); // Main gradient.
7044 window->DrawList->AddRectFilledMultiColor(check_bb.Min + ImVec2(pad, pad), check_bb.Max - ImVec2(pad, pad), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.05f)), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.05f)), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.38f)), GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.38f))); // Main gradient.
7045 }
7046
7047 if (g.LogEnabled)
7048 LogRenderedText(text_bb.GetTL(), *v ? "[x]" : "[ ]");
7049 if (label_size.x > 0.0f) {
7050 if (*v) {
7051 PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1));
7052 }
7053 else {
7054 PushStyleColor(ImGuiCol_Text, ImVec4(.6f, .6f, .6f, 1));
7055 }
7056 RenderText(text_bb.GetTL() - ImVec2(0, 6), label);
7057 PopStyleColor();
7058 }
7059
7060 return pressed;
7061}
7062
7063bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) {
7064 bool v = ((*flags & flags_value) == flags_value);
7065 bool pressed = Checkbox(label, &v);
7066 if (pressed) {
7067 if (v)
7068 * flags |= flags_value;
7069 else
7070 *flags &= ~flags_value;
7071 }
7072
7073 return pressed;
7074}
7075
7076bool ImGui::RadioButton(const char* label, bool active) {
7077 ImGuiWindow* window = GetCurrentWindow();
7078 if (window->SkipItems)
7079 return false;
7080
7081 ImGuiContext& g = *GImGui;
7082 const ImGuiStyle& style = g.Style;
7083 const ImGuiID id = window->GetID(label);
7084 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7085
7086 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));
7087 ItemSize(check_bb, style.FramePadding.y);
7088
7089 ImRect total_bb = check_bb;
7090 if (label_size.x > 0)
7091 SameLine(0, style.ItemInnerSpacing.x);
7092 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
7093 if (label_size.x > 0) {
7094 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
7095 total_bb.Add(text_bb);
7096 }
7097
7098 if (!ItemAdd(total_bb, &id))
7099 return false;
7100
7101 ImVec2 center = check_bb.GetCenter();
7102 center.x = (float)(int)center.x + 0.5f;
7103 center.y = (float)(int)center.y + 0.5f;
7104 const float radius = check_bb.GetHeight() * 0.5f;
7105
7106 bool hovered, held;
7107 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
7108
7109 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
7110 if (active) {
7111 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
7112 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
7113 window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
7114 }
7115
7116 if (window->Flags & ImGuiWindowFlags_ShowBorders) {
7117 window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16);
7118 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16);
7119 }
7120
7121 if (g.LogEnabled)
7122 LogRenderedText(text_bb.GetTL(), active ? "(x)" : "( )");
7123 if (label_size.x > 0.0f)
7124 RenderText(text_bb.GetTL(), label);
7125
7126 return pressed;
7127}
7128
7129bool ImGui::RadioButton(const char* label, int* v, int v_button) {
7130 const bool pressed = RadioButton(label, *v == v_button);
7131 if (pressed) {
7132 *v = v_button;
7133 }
7134 return pressed;
7135}
7136
7137static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) {
7138 int line_count = 0;
7139 const char* s = text_begin;
7140 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
7141 if (c == '\n')
7142 line_count++;
7143 s--;
7144 if (s[0] != '\n' && s[0] != '\r')
7145 line_count++;
7146 *out_text_end = s;
7147 return line_count;
7148}
7149
7150static ImVec2 InputTextCalcTextSizeW(const ImWchar * text_begin, const ImWchar * text_end, const ImWchar * *remaining, ImVec2 * out_offset, bool stop_on_new_line) {
7151 ImFont* font = GImGui->Font;
7152 const float line_height = GImGui->FontSize;
7153 const float scale = line_height / font->FontSize;
7154
7155 ImVec2 text_size = ImVec2(0, 0);
7156 float line_width = 0.0f;
7157
7158 const ImWchar* s = text_begin;
7159 while (s < text_end) {
7160 unsigned int c = (unsigned int)(*s++);
7161 if (c == '\n') {
7162 text_size.x = ImMax(text_size.x, line_width);
7163 text_size.y += line_height;
7164 line_width = 0.0f;
7165 if (stop_on_new_line)
7166 break;
7167 continue;
7168 }
7169 if (c == '\r')
7170 continue;
7171
7172 const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
7173 line_width += char_width;
7174 }
7175
7176 if (text_size.x < line_width)
7177 text_size.x = line_width;
7178
7179 if (out_offset)
7180 * out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
7181
7182 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
7183 text_size.y += line_height;
7184
7185 if (remaining)
7186 * remaining = s;
7187
7188 return text_size;
7189}
7190
7191// 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)
7192namespace ImGuiStb {
7193 static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) {
7194 return obj->CurLenW;
7195 }
7196 static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) {
7197 return obj->Text[idx];
7198 }
7199 static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) {
7200 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);
7201 }
7202 static int STB_TEXTEDIT_KEYTOTEXT(int key) {
7203 return key >= 0x10000 ? 0 : key;
7204 }
7205 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
7206 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow * r, STB_TEXTEDIT_STRING * obj, int line_start_idx) {
7207 const ImWchar* text = obj->Text.Data;
7208 const ImWchar* text_remaining = NULL;
7209 const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
7210 r->x0 = 0.0f;
7211 r->x1 = size.x;
7212 r->baseline_y_delta = size.y;
7213 r->ymin = 0.0f;
7214 r->ymax = size.y;
7215 r->num_chars = (int)(text_remaining - (text + line_start_idx));
7216 }
7217
7218 static bool is_separator(unsigned int c) {
7219 return ImCharIsSpace(c) || c == ',' || c == ';' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || c == '|';
7220 }
7221 static int is_word_boundary_from_right(STB_TEXTEDIT_STRING * obj, int idx) {
7222 return idx > 0 ? (is_separator(obj->Text[idx - 1]) && !is_separator(obj->Text[idx])) : 1;
7223 }
7224 static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING * obj, int idx) {
7225 idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx;
7226 }
7227#ifdef __APPLE__ // FIXME: Move setting to IO structure
7228 static int is_word_boundary_from_left(STB_TEXTEDIT_STRING * obj, int idx) {
7229 return idx > 0 ? (!is_separator(obj->Text[idx - 1]) && is_separator(obj->Text[idx])) : 1;
7230 }
7231 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj, int idx) {
7232 idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx;
7233 }
7234#else
7235 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj, int idx) {
7236 idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx;
7237 }
7238#endif
7239#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
7240#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
7241
7242 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING * obj, int pos, int n) {
7243 ImWchar* dst = obj->Text.Data + pos;
7244
7245 // We maintain our buffer length in both UTF-8 and wchar formats
7246 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
7247 obj->CurLenW -= n;
7248
7249 // Offset remaining text
7250 const ImWchar * src = obj->Text.Data + pos + n;
7251 while (ImWchar c = *src++)
7252 * dst++ = c;
7253 *dst = '\0';
7254 }
7255
7256 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING * obj, int pos, const ImWchar * new_text, int new_text_len) {
7257 const int text_len = obj->CurLenW;
7258 IM_ASSERT(pos <= text_len);
7259 if (new_text_len + text_len + 1 > obj->Text.Size)
7260 return false;
7261
7262 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
7263 if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA)
7264 return false;
7265
7266 ImWchar * text = obj->Text.Data;
7267 if (pos != text_len)
7268 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
7269 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
7270
7271 obj->CurLenW += new_text_len;
7272 obj->CurLenA += new_text_len_utf8;
7273 obj->Text[obj->CurLenW] = '\0';
7274
7275 return true;
7276 }
7277
7278 // 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)
7279#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
7280#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
7281#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
7282#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
7283#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
7284#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
7285#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
7286#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
7287#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
7288#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
7289#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
7290#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
7291#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
7292#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
7293#define STB_TEXTEDIT_K_SHIFT 0x20000
7294
7295#define STB_TEXTEDIT_IMPLEMENTATION
7296#include "stb_textedit.h"
7297}
7298
7299void ImGuiTextEditState::OnKeyPressed(int key) {
7300 stb_textedit_key(this, &StbState, key);
7301 CursorFollow = true;
7302 CursorAnimReset();
7303}
7304
7305// Public API to manipulate UTF-8 text
7306// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
7307// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
7308void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) {
7309 IM_ASSERT(pos + bytes_count <= BufTextLen);
7310 char* dst = Buf + pos;
7311 const char* src = Buf + pos + bytes_count;
7312 while (char c = *src++)
7313 * dst++ = c;
7314 *dst = '\0';
7315
7316 if (CursorPos + bytes_count >= pos)
7317 CursorPos -= bytes_count;
7318 else if (CursorPos >= pos)
7319 CursorPos = pos;
7320 SelectionStart = SelectionEnd = CursorPos;
7321 BufDirty = true;
7322 BufTextLen -= bytes_count;
7323}
7324
7325void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) {
7326 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
7327 if (new_text_len + BufTextLen + 1 >= BufSize)
7328 return;
7329
7330 if (BufTextLen != pos)
7331 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
7332 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
7333 Buf[BufTextLen + new_text_len] = '\0';
7334
7335 if (CursorPos >= pos)
7336 CursorPos += new_text_len;
7337 SelectionStart = SelectionEnd = CursorPos;
7338 BufDirty = true;
7339 BufTextLen += new_text_len;
7340}
7341
7342// Return false to discard a character.
7343static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) {
7344 unsigned int c = *p_char;
7345
7346 if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) {
7347 bool pass = false;
7348 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
7349 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
7350 if (!pass)
7351 return false;
7352 }
7353
7354 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.
7355 return false;
7356
7357 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank)) {
7358 if (flags & ImGuiInputTextFlags_CharsDecimal)
7359 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
7360 return false;
7361
7362 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
7363 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
7364 return false;
7365
7366 if (flags & ImGuiInputTextFlags_CharsUppercase)
7367 if (c >= 'a' && c <= 'z')
7368 * p_char = (c += (unsigned int)('A' - 'a'));
7369
7370 if (flags & ImGuiInputTextFlags_CharsNoBlank)
7371 if (ImCharIsSpace(c))
7372 return false;
7373 }
7374
7375 if (flags & ImGuiInputTextFlags_CallbackCharFilter) {
7376 ImGuiTextEditCallbackData callback_data;
7377 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
7378 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
7379 callback_data.EventChar = (ImWchar)c;
7380 callback_data.Flags = flags;
7381 callback_data.UserData = user_data;
7382 if (callback(&callback_data) != 0)
7383 return false;
7384 *p_char = callback_data.EventChar;
7385 if (!callback_data.EventChar)
7386 return false;
7387 }
7388
7389 return true;
7390}
7391
7392// Edit a string of text
7393// 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.
7394// 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
7395bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 & size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) {
7396 ImGuiWindow* window = GetCurrentWindow();
7397 if (window->SkipItems)
7398 return false;
7399
7400 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
7401 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
7402
7403 ImGuiContext& g = *GImGui;
7404 const ImGuiIO& io = g.IO;
7405 const ImGuiStyle& style = g.Style;
7406
7407 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
7408 const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
7409 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
7410
7411 if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn
7412 BeginGroup();
7413 const ImGuiID id = window->GetID(label);
7414 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7415 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
7416 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
7417 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));
7418
7419 ImGuiWindow * draw_window = window;
7420 if (is_multiline) {
7421 if (!BeginChildFrame(id, frame_bb.GetSize())) {
7422 EndChildFrame();
7423 EndGroup();
7424 return false;
7425 }
7426 draw_window = GetCurrentWindow();
7427 size.x -= draw_window->ScrollbarSizes.x;
7428 }
7429 else {
7430 ItemSize(total_bb, style.FramePadding.y);
7431 if (!ItemAdd(total_bb, &id))
7432 return false;
7433 }
7434
7435 // Password pushes a temporary font with only a fallback glyph
7436 if (is_password) {
7437 const ImFont::Glyph* glyph = g.Font->FindGlyph('*');
7438 ImFont* password_font = &g.InputTextPasswordFont;
7439 password_font->FontSize = g.Font->FontSize;
7440 password_font->Scale = g.Font->Scale;
7441 password_font->DisplayOffset = g.Font->DisplayOffset;
7442 password_font->Ascent = g.Font->Ascent;
7443 password_font->Descent = g.Font->Descent;
7444 password_font->ContainerAtlas = g.Font->ContainerAtlas;
7445 password_font->FallbackGlyph = glyph;
7446 password_font->FallbackXAdvance = glyph->XAdvance;
7447 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexXAdvance.empty() && password_font->IndexLookup.empty());
7448 PushFont(password_font);
7449 }
7450
7451 // NB: we are only allowed to access 'edit_state' if we are the active widget.
7452 ImGuiTextEditState& edit_state = g.InputTextState;
7453
7454 const bool focus_requested = FocusableItemRegister(window, g.ActiveId == id, (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
7455 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
7456 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
7457
7458 const bool hovered = IsHovered(frame_bb, id);
7459 if (hovered) {
7460 SetHoveredID(id);
7461 g.MouseCursor = ImGuiMouseCursor_TextInput;
7462 }
7463 const bool user_clicked = hovered && io.MouseClicked[0];
7464 const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
7465
7466 bool select_all = (g.ActiveId != id) && (flags & ImGuiInputTextFlags_AutoSelectAll) != 0;
7467 if (focus_requested || user_clicked || user_scrolled) {
7468 if (g.ActiveId != id) {
7469 // Start edition
7470 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
7471 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
7472 const int prev_len_w = edit_state.CurLenW;
7473 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.
7474 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.
7475 ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size);
7476 const char* buf_end = NULL;
7477 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
7478 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.
7479 edit_state.CursorAnimReset();
7480
7481 // Preserve cursor position and undo/redo stack if we come back to same widget
7482 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
7483 const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW);
7484 if (recycle_state) {
7485 // Recycle existing cursor/selection/undo stack but clamp position
7486 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
7487 edit_state.CursorClamp();
7488 }
7489 else {
7490 edit_state.Id = id;
7491 edit_state.ScrollX = 0.0f;
7492 stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
7493 if (!is_multiline && focus_requested_by_code)
7494 select_all = true;
7495 }
7496 if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
7497 edit_state.StbState.insert_mode = true;
7498 if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
7499 select_all = true;
7500 }
7501 SetActiveID(id, window);
7502 FocusWindow(window);
7503 }
7504 else if (io.MouseClicked[0]) {
7505 // Release focus when we click outside
7506 if (g.ActiveId == id)
7507 ClearActiveID();
7508 }
7509
7510 bool value_changed = false;
7511 bool enter_pressed = false;
7512
7513 if (g.ActiveId == id) {
7514 if (!is_editable && !g.ActiveIdIsJustActivated) {
7515 // When read-only we always use the live data passed to the function
7516 edit_state.Text.resize(buf_size + 1);
7517 const char* buf_end = NULL;
7518 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
7519 edit_state.CurLenA = (int)(buf_end - buf);
7520 edit_state.CursorClamp();
7521 }
7522
7523 edit_state.BufSizeA = buf_size;
7524
7525 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
7526 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
7527 g.ActiveIdAllowOverlap = !io.MouseDown[0];
7528
7529 // Edit in progress
7530 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
7531 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize * 0.5f));
7532
7533 const bool osx_double_click_selects_words = io.OSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text
7534 if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0])) {
7535 edit_state.SelectAll();
7536 edit_state.SelectedAllMouseLock = true;
7537 }
7538 else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0]) {
7539 // Select a word only, OS X style (by simulating keystrokes)
7540 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
7541 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
7542 }
7543 else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) {
7544 stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
7545 edit_state.CursorAnimReset();
7546 }
7547 else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) {
7548 stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
7549 edit_state.CursorAnimReset();
7550 edit_state.CursorFollow = true;
7551 }
7552 if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
7553 edit_state.SelectedAllMouseLock = false;
7554
7555 if (io.InputCharacters[0]) {
7556 // Process text input (before we check for Return because using some IME will effectively send a Return?)
7557 // We ignore CTRL inputs, but need to allow CTRL+ALT as some keyboards (e.g. German) use AltGR - which is Alt+Ctrl - to input certain characters.
7558 if (!(io.KeyCtrl && !io.KeyAlt) && is_editable) {
7559 for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
7560 if (unsigned int c = (unsigned int)io.InputCharacters[n]) {
7561 // Insert character if they pass filtering
7562 if (!InputTextFilterCharacter(&c, flags, callback, user_data))
7563 continue;
7564 edit_state.OnKeyPressed((int)c);
7565 }
7566 }
7567
7568 // Consume characters
7569 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
7570 }
7571
7572 // Handle various key-presses
7573 bool cancel_edit = false;
7574 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
7575 const bool is_shortcut_key_only = (io.OSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
7576 const bool is_wordmove_key_down = io.OSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
7577 const bool is_startend_key_down = io.OSXBehaviors && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
7578
7579 if (IsKeyPressedMap(ImGuiKey_LeftArrow)) {
7580 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);
7581 }
7582 else if (IsKeyPressedMap(ImGuiKey_RightArrow)) {
7583 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);
7584 }
7585 else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) {
7586 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);
7587 }
7588 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) {
7589 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);
7590 }
7591 else if (IsKeyPressedMap(ImGuiKey_Home)) {
7592 edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask);
7593 }
7594 else if (IsKeyPressedMap(ImGuiKey_End)) {
7595 edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask);
7596 }
7597 else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) {
7598 edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
7599 }
7600 else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) {
7601 if (!edit_state.HasSelection()) {
7602 if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
7603 else if (io.OSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
7604 }
7605 edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
7606 }
7607 else if (IsKeyPressedMap(ImGuiKey_Enter)) {
7608 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
7609 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) {
7610 ClearActiveID();
7611 enter_pressed = true;
7612 }
7613 else if (is_editable) {
7614 unsigned int c = '\n'; // Insert new line
7615 if (InputTextFilterCharacter(&c, flags, callback, user_data))
7616 edit_state.OnKeyPressed((int)c);
7617 }
7618 }
7619 else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) {
7620 unsigned int c = '\t'; // Insert TAB
7621 if (InputTextFilterCharacter(&c, flags, callback, user_data))
7622 edit_state.OnKeyPressed((int)c);
7623 }
7624 else if (IsKeyPressedMap(ImGuiKey_Escape)) {
7625 ClearActiveID(); cancel_edit = true;
7626 }
7627 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable) {
7628 edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection();
7629 }
7630 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable) {
7631 edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection();
7632 }
7633 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) {
7634 edit_state.SelectAll(); edit_state.CursorFollow = true;
7635 }
7636 else if (is_shortcut_key_only && !is_password && ((IsKeyPressedMap(ImGuiKey_X) && is_editable) || IsKeyPressedMap(ImGuiKey_C)) && (!is_multiline || edit_state.HasSelection())) {
7637 // Cut, Copy
7638 const bool cut = IsKeyPressedMap(ImGuiKey_X);
7639 if (cut && !edit_state.HasSelection())
7640 edit_state.SelectAll();
7641
7642 if (io.SetClipboardTextFn) {
7643 const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
7644 const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
7645 edit_state.TempTextBuffer.resize((ie - ib) * 4 + 1);
7646 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data + ib, edit_state.Text.Data + ie);
7647 SetClipboardText(edit_state.TempTextBuffer.Data);
7648 }
7649
7650 if (cut) {
7651 edit_state.CursorFollow = true;
7652 stb_textedit_cut(&edit_state, &edit_state.StbState);
7653 }
7654 }
7655 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V) && is_editable) {
7656 // Paste
7657 if (const char* clipboard = GetClipboardText()) {
7658 // Filter pasted buffer
7659 const int clipboard_len = (int)strlen(clipboard);
7660 ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len + 1) * sizeof(ImWchar));
7661 int clipboard_filtered_len = 0;
7662 for (const char* s = clipboard; *s; ) {
7663 unsigned int c;
7664 s += ImTextCharFromUtf8(&c, s, NULL);
7665 if (c == 0)
7666 break;
7667 if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data))
7668 continue;
7669 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
7670 }
7671 clipboard_filtered[clipboard_filtered_len] = 0;
7672 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
7673 {
7674 stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
7675 edit_state.CursorFollow = true;
7676 }
7677 ImGui::MemFree(clipboard_filtered);
7678 }
7679 }
7680
7681 if (cancel_edit) {
7682 // Restore initial value
7683 if (is_editable) {
7684 ImStrncpy(buf, edit_state.InitialText.Data, buf_size);
7685 value_changed = true;
7686 }
7687 }
7688 else {
7689 // Apply new value immediately - copy modified buffer back
7690 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
7691 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
7692 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
7693 if (is_editable) {
7694 edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4);
7695 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL);
7696 }
7697
7698 // User callback
7699 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) {
7700 IM_ASSERT(callback != NULL);
7701
7702 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
7703 ImGuiInputTextFlags event_flag = 0;
7704 ImGuiKey event_key = ImGuiKey_COUNT;
7705 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) {
7706 event_flag = ImGuiInputTextFlags_CallbackCompletion;
7707 event_key = ImGuiKey_Tab;
7708 }
7709 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) {
7710 event_flag = ImGuiInputTextFlags_CallbackHistory;
7711 event_key = ImGuiKey_UpArrow;
7712 }
7713 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) {
7714 event_flag = ImGuiInputTextFlags_CallbackHistory;
7715 event_key = ImGuiKey_DownArrow;
7716 }
7717 else if (flags & ImGuiInputTextFlags_CallbackAlways)
7718 event_flag = ImGuiInputTextFlags_CallbackAlways;
7719
7720 if (event_flag) {
7721 ImGuiTextEditCallbackData callback_data;
7722 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
7723 callback_data.EventFlag = event_flag;
7724 callback_data.Flags = flags;
7725 callback_data.UserData = user_data;
7726 callback_data.ReadOnly = !is_editable;
7727
7728 callback_data.EventKey = event_key;
7729 callback_data.Buf = edit_state.TempTextBuffer.Data;
7730 callback_data.BufTextLen = edit_state.CurLenA;
7731 callback_data.BufSize = edit_state.BufSizeA;
7732 callback_data.BufDirty = false;
7733
7734 // 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)
7735 ImWchar* text = edit_state.Text.Data;
7736 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
7737 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
7738 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
7739
7740 // Call user code
7741 callback(&callback_data);
7742
7743 // Read back what user may have modified
7744 IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields
7745 IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA);
7746 IM_ASSERT(callback_data.Flags == flags);
7747 if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos);
7748 if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart);
7749 if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd);
7750 if (callback_data.BufDirty) {
7751 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
7752 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL);
7753 edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
7754 edit_state.CursorAnimReset();
7755 }
7756 }
7757 }
7758
7759 // Copy back to user buffer
7760 if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0) {
7761 ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size);
7762 value_changed = true;
7763 }
7764 }
7765 }
7766
7767 // Render
7768 // 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.
7769 const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL;
7770
7771 if (!is_multiline)
7772 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
7773
7774 window->DrawList->AddRectFilled(frame_bb.Min - ImVec2(1, 1), frame_bb.Max + ImVec2(1, 1), GetColorU32(ImVec4(0 / 255.f, 0 / 255.f, 0 / 255.f, 0.1f)), style.FrameRounding);
7775 window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), style.FrameRounding);
7776
7777 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
7778 ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
7779 ImVec2 text_size(0.f, 0.f);
7780 const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
7781 if (g.ActiveId == id || is_currently_scrolling) {
7782 edit_state.CursorAnim += io.DeltaTime;
7783
7784 // This is going to be messy. We need to:
7785 // - Display the text (this alone can be more easily clipped)
7786 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
7787 // - Measure text height (for scrollbar)
7788 // 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)
7789 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
7790 const ImWchar* text_begin = edit_state.Text.Data;
7791 ImVec2 cursor_offset, select_start_offset;
7792
7793 {
7794 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
7795 const ImWchar* searches_input_ptr[2];
7796 searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
7797 searches_input_ptr[1] = NULL;
7798 int searches_remaining = 1;
7799 int searches_result_line_number[2] = { -1, -999 };
7800 if (edit_state.StbState.select_start != edit_state.StbState.select_end) {
7801 searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
7802 searches_result_line_number[1] = -1;
7803 searches_remaining++;
7804 }
7805
7806 // Iterate all lines to find our line numbers
7807 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
7808 searches_remaining += is_multiline ? 1 : 0;
7809 int line_count = 0;
7810 for (const ImWchar* s = text_begin; *s != 0; s++)
7811 if (*s == '\n') {
7812 line_count++;
7813 if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) {
7814 searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break;
7815 }
7816 if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) {
7817 searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break;
7818 }
7819 }
7820 line_count++;
7821 if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
7822 if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
7823
7824 // Calculate 2d position by finding the beginning of the line and measuring distance
7825 cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
7826 cursor_offset.y = searches_result_line_number[0] * g.FontSize;
7827 if (searches_result_line_number[1] >= 0) {
7828 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
7829 select_start_offset.y = searches_result_line_number[1] * g.FontSize;
7830 }
7831
7832 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
7833 if (is_multiline)
7834 text_size = ImVec2(size.x, line_count * g.FontSize);
7835 }
7836
7837 // Scroll
7838 if (edit_state.CursorFollow) {
7839 // Horizontal scroll in chunks of quarter width
7840 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) {
7841 const float scroll_increment_x = size.x * 0.25f;
7842 if (cursor_offset.x < edit_state.ScrollX)
7843 edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
7844 else if (cursor_offset.x - size.x >= edit_state.ScrollX)
7845 edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
7846 }
7847 else {
7848 edit_state.ScrollX = 0.0f;
7849 }
7850
7851 // Vertical scroll
7852 if (is_multiline) {
7853 float scroll_y = draw_window->Scroll.y;
7854 if (cursor_offset.y - g.FontSize < scroll_y)
7855 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
7856 else if (cursor_offset.y - size.y >= scroll_y)
7857 scroll_y = cursor_offset.y - size.y;
7858 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
7859 draw_window->Scroll.y = scroll_y;
7860 render_pos.y = draw_window->DC.CursorPos.y;
7861 }
7862 }
7863 edit_state.CursorFollow = false;
7864 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
7865
7866 // Draw selection
7867 if (edit_state.StbState.select_start != edit_state.StbState.select_end) {
7868 const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
7869 const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
7870
7871 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.
7872 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
7873 ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
7874 ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
7875 for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) {
7876 if (rect_pos.y > clip_rect.w + g.FontSize)
7877 break;
7878 if (rect_pos.y < clip_rect.y) {
7879 while (p < text_selected_end)
7880 if (*p++ == '\n')
7881 break;
7882 }
7883 else {
7884 ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
7885 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
7886 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
7887 rect.ClipWith(clip_rect);
7888 if (rect.Overlaps(clip_rect))
7889 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
7890 }
7891 rect_pos.x = render_pos.x - render_scroll.x;
7892 rect_pos.y += g.FontSize;
7893 }
7894 }
7895
7896 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);
7897
7898 // Draw blinking cursor
7899 bool cursor_is_visible = (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
7900 ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
7901 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);
7902 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
7903 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
7904
7905 // 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.)
7906 if (is_editable)
7907 g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
7908 }
7909 else {
7910 // Render text only
7911 const char* buf_end = NULL;
7912 if (is_multiline)
7913 text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
7914 draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
7915 }
7916
7917 if (is_multiline) {
7918 Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
7919 EndChildFrame();
7920 EndGroup();
7921 }
7922
7923 if (is_password)
7924 PopFont();
7925
7926 // Log as text
7927 if (g.LogEnabled && !is_password)
7928 LogRenderedText(render_pos, buf_display, NULL);
7929
7930 if (label_size.x > 0)
7931 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
7932
7933 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
7934 return enter_pressed;
7935 else
7936 return value_changed;
7937}
7938
7939bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) {
7940 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
7941 return InputTextEx(label, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
7942}
7943
7944bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2 & size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) {
7945 return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
7946}
7947
7948// NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument)
7949bool 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) {
7950 ImGuiWindow* window = GetCurrentWindow();
7951 if (window->SkipItems)
7952 return false;
7953
7954 ImGuiContext& g = *GImGui;
7955 const ImGuiStyle& style = g.Style;
7956 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7957
7958 BeginGroup();
7959 PushID(label);
7960 const ImVec2 button_sz = ImVec2(g.FontSize, g.FontSize) + style.FramePadding * 2.0f;
7961 if (step_ptr)
7962 PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x) * 2));
7963
7964 char buf[64];
7965 DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf));
7966
7967 bool value_changed = false;
7968 if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal))
7969 extra_flags |= ImGuiInputTextFlags_CharsDecimal;
7970 extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
7971 if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
7972 value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format);
7973
7974 // Step buttons
7975 if (step_ptr) {
7976 PopItemWidth();
7977 SameLine(0, style.ItemInnerSpacing.x);
7978 if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) {
7979 DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr);
7980 value_changed = true;
7981 }
7982 SameLine(0, style.ItemInnerSpacing.x);
7983 if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) {
7984 DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr);
7985 value_changed = true;
7986 }
7987 }
7988 PopID();
7989
7990 if (label_size.x > 0) {
7991 SameLine(0, style.ItemInnerSpacing.x);
7992 RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label);
7993 ItemSize(label_size, style.FramePadding.y);
7994 }
7995 EndGroup();
7996
7997 return value_changed;
7998}
7999
8000bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) {
8001 char display_format[16];
8002 if (decimal_precision < 0)
8003 strcpy(display_format, "%f"); // Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1
8004 else
8005 ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision);
8006 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);
8007}
8008
8009bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) {
8010 // 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.
8011 const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
8012 return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), scalar_format, extra_flags);
8013}
8014
8015bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) {
8016 ImGuiWindow* window = GetCurrentWindow();
8017 if (window->SkipItems)
8018 return false;
8019
8020 ImGuiContext& g = *GImGui;
8021 bool value_changed = false;
8022 BeginGroup();
8023 PushID(label);
8024 PushMultiItemsWidths(components);
8025 for (int i = 0; i < components; i++) {
8026 PushID(i);
8027 value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags);
8028 SameLine(0, g.Style.ItemInnerSpacing.x);
8029 PopID();
8030 PopItemWidth();
8031 }
8032 PopID();
8033
8034 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
8035 TextUnformatted(label, FindRenderedTextEnd(label));
8036 EndGroup();
8037
8038 return value_changed;
8039}
8040
8041bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) {
8042 return InputFloatN(label, v, 2, decimal_precision, extra_flags);
8043}
8044
8045bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) {
8046 return InputFloatN(label, v, 3, decimal_precision, extra_flags);
8047}
8048
8049bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) {
8050 return InputFloatN(label, v, 4, decimal_precision, extra_flags);
8051}
8052
8053bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags) {
8054 ImGuiWindow* window = GetCurrentWindow();
8055 if (window->SkipItems)
8056 return false;
8057
8058 ImGuiContext& g = *GImGui;
8059 bool value_changed = false;
8060 BeginGroup();
8061 PushID(label);
8062 PushMultiItemsWidths(components);
8063 for (int i = 0; i < components; i++) {
8064 PushID(i);
8065 value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags);
8066 SameLine(0, g.Style.ItemInnerSpacing.x);
8067 PopID();
8068 PopItemWidth();
8069 }
8070 PopID();
8071
8072 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
8073 TextUnformatted(label, FindRenderedTextEnd(label));
8074 EndGroup();
8075
8076 return value_changed;
8077}
8078
8079bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) {
8080 return InputIntN(label, v, 2, extra_flags);
8081}
8082
8083bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) {
8084 return InputIntN(label, v, 3, extra_flags);
8085}
8086
8087bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) {
8088 return InputIntN(label, v, 4, extra_flags);
8089}
8090
8091bool ImGui::Hotkey(const char* label, int* k, const ImVec2 & size_arg) {
8092 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10);
8093
8094 ImGuiWindow * window = GetCurrentWindow();
8095 if (window->SkipItems)
8096 return false;
8097
8098 ImGuiContext & g = *GImGui;
8099 ImGuiIO & io = g.IO;
8100 const ImGuiStyle & style = g.Style;
8101
8102 const ImGuiID id = window->GetID(label);
8103 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8104 ImVec2 size = ImVec2(183, 18);
8105 const ImRect frame_bb(window->DC.CursorPos + ImVec2(label_size.x + style.ItemInnerSpacing.x, 0.0f), window->DC.CursorPos + size);
8106 const ImRect total_bb(window->DC.CursorPos, frame_bb.Max);
8107
8108 ItemSize(total_bb, style.FramePadding.y);
8109 if (!ItemAdd(total_bb, &id))
8110 return false;
8111
8112 const bool focus_requested = FocusableItemRegister(window, g.ActiveId == id, false);
8113 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
8114 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
8115
8116 const bool hovered = IsHovered(frame_bb, id);
8117
8118 if (hovered) {
8119 SetHoveredID(id);
8120 g.MouseCursor = ImGuiMouseCursor_TextInput;
8121 }
8122
8123 const bool user_clicked = hovered && io.MouseClicked[0];
8124
8125 if (focus_requested || user_clicked) {
8126 if (g.ActiveId != id) {
8127 memset(io.MouseDown, 0, sizeof(io.MouseDown));
8128 memset(io.KeysDown, 0, sizeof(io.KeysDown));
8129 *k = 0;
8130 }
8131 SetActiveID(id, window);
8132 FocusWindow(window);
8133 }
8134 else if (io.MouseClicked[0]) {
8135 if (g.ActiveId == id)
8136 ClearActiveID();
8137 }
8138
8139 bool value_changed = false;
8140 int key = *k;
8141
8142 if (g.ActiveId == id) {
8143 for (auto i = 0; i < 5; i++) {
8144 if (io.MouseDown[i]) {
8145 switch (i) {
8146 case 0:
8147 key = VK_LBUTTON;
8148 break;
8149 case 1:
8150 key = VK_RBUTTON;
8151 break;
8152 case 2:
8153 key = VK_MBUTTON;
8154 break;
8155 case 3:
8156 key = VK_XBUTTON1;
8157 break;
8158 case 4:
8159 key = VK_XBUTTON2;
8160 break;
8161 }
8162 value_changed = true;
8163 ClearActiveID();
8164 }
8165 }
8166 if (!value_changed) {
8167 for (auto i = VK_BACK; i <= VK_RMENU; i++) {
8168 if (io.KeysDown[i]) {
8169 key = i;
8170 value_changed = true;
8171 ClearActiveID();
8172 }
8173 }
8174 }
8175
8176 if (IsKeyPressedMap(ImGuiKey_Escape)) {
8177 *k = 0;
8178 ClearActiveID();
8179 }
8180 else {
8181 *k = key;
8182 }
8183 }
8184
8185 // Render
8186 // 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.
8187
8188 char buf_display[64] = "none";
8189
8190 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
8191 window->DrawList->AddRectFilled(frame_bb.Min - ImVec2(1, 1), frame_bb.Max + ImVec2(1, 1), GetColorU32(ImVec4(0 / 255.f, 0 / 255.f, 0 / 255.f, 0.1f)), style.FrameRounding);
8192 window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), style.FrameRounding);
8193
8194 if (*k != 0 && g.ActiveId != id) {
8195 strcpy(buf_display, KeyNames[*k]);
8196 }
8197 else if (g.ActiveId == id) {
8198 strcpy(buf_display, "press a key");
8199 }
8200
8201 const ImRect 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
8202 ImVec2 render_pos = frame_bb.Min + style.FramePadding;
8203 RenderTextClipped(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding, buf_display, NULL, NULL);
8204 //draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, NULL, 0.0f, &clip_rect);
8205
8206 if (label_size.x > 0)
8207 RenderText(ImVec2(total_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), label);
8208
8209 return value_changed;
8210}
8211
8212static bool Items_ArrayGetter(void* data, int idx, const char** out_text) {
8213 const char* const* items = (const char* const*)data;
8214 if (out_text)
8215 * out_text = items[idx];
8216 return true;
8217}
8218
8219static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) {
8220 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
8221 const char* items_separated_by_zeros = (const char*)data;
8222 int items_count = 0;
8223 const char* p = items_separated_by_zeros;
8224 while (*p) {
8225 if (idx == items_count)
8226 break;
8227 p += strlen(p) + 1;
8228 items_count++;
8229 }
8230 if (!*p)
8231 return false;
8232 if (out_text)
8233 * out_text = p;
8234 return true;
8235}
8236
8237// Combo box helper allowing to pass an array of strings.
8238bool ImGui::Combo(const char* label, int* current_item, const char* const* items, int items_count, int height_in_items) {
8239 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
8240 return value_changed;
8241}
8242
8243// Combo box helper allowing to pass all items in a single string.
8244bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) {
8245 SetCursorPosY(GetCursorPosY() + 4);
8246 int items_count = 0;
8247 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
8248 while (*p) {
8249 p += strlen(p) + 1;
8250 items_count++;
8251 }
8252
8253 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, (height_in_items == -1 ? items_count : height_in_items));
8254 return value_changed;
8255}
8256
8257static inline float SmallSquareSize()
8258{
8259 ImGuiContext& g = *GImGui;
8260 return g.FontSize + g.Style.FramePadding.y * 2.0f;
8261}
8262
8263// Render a triangle to denote expanded/collapsed state
8264void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale)
8265{
8266 ImGuiContext& g = *GImGui;
8267 ImGuiWindow* window = g.CurrentWindow;
8268
8269 const float h = g.FontSize * 1.00f;
8270 float r = h * 0.40f * scale;
8271 ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale);
8272
8273 ImVec2 a, b, c;
8274 switch (dir)
8275 {
8276 case ImGuiDir_Up:
8277 r = -r; // ...fall through, no break!
8278 case ImGuiDir_Down:
8279 center.y -= r * 0.25f;
8280 a = ImVec2(0, 1) * r;
8281 b = ImVec2(-0.866f, -0.5f) * r;
8282 c = ImVec2(+0.866f, -0.5f) * r;
8283 break;
8284 case ImGuiDir_Left:
8285 r = -r; // ...fall through, no break!
8286 case ImGuiDir_Right:
8287 a = ImVec2(1, 0) * r;
8288 b = ImVec2(-0.500f, +0.866f) * r;
8289 c = ImVec2(-0.500f, -0.866f) * r;
8290 break;
8291 default:
8292 IM_ASSERT(0);
8293 break;
8294 }
8295
8296 window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text));
8297}
8298
8299
8300bool ImGui::BeginCombo(const char* label, const char* preview_value, ImVec2 popup_size)
8301{
8302 SetCursorPosY(GetCursorPosY() + 4);
8303 ImGuiWindow * window = GetCurrentWindow();
8304 if (window->SkipItems)
8305 return false;
8306
8307 Dummy(ImVec2(0, 2));
8308
8309 ImGuiContext & g = *GImGui;
8310 const ImGuiStyle & style = g.Style;
8311 const ImGuiID id = window->GetID(label);
8312 float w = 175;
8313
8314 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8315 const ImRect frame_bb(window->DC.CursorPos + ImVec2(18, 0), window->DC.CursorPos + ImVec2(w + 18, label_size.y + style.FramePadding.y * 2.0f - 1));
8316 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));
8317 ItemSize(total_bb, style.FramePadding.y);
8318 if (!ItemAdd(total_bb, &id))
8319 return false;
8320
8321 const float arrow_size = SmallSquareSize();
8322
8323 bool hovered, held;
8324 bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
8325
8326 bool popup_open = IsPopupOpen(id);
8327
8328 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
8329 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
8330 window->DrawList->AddRectFilled(frame_bb.Min - ImVec2(1, 1), frame_bb.Max + ImVec2(1, 1), GetColorU32(ImVec4(0 / 255.f, 0 / 255.f, 0 / 255.f, 0.1f)), style.FrameRounding);
8331 window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), style.FrameRounding);
8332 // lambda for the arrow
8333 auto arrow = [window](int x, int y, float clr = 0.59) {
8334 for (auto i = 5; i >= 2; --i) {
8335 auto offset = 5 - i;
8336
8337 window->DrawList->AddLine(ImVec2(x + offset, y + offset), ImVec2(x + offset + nigga(i - offset, 0, 5), y + offset), GetColorU32(ImVec4(clr, clr, clr, 1)));
8338 }
8339 };
8340
8341 arrow(frame_bb.Max.x - 10, frame_bb.Max.y - 11, 0.f);
8342 arrow(frame_bb.Max.x - 10, frame_bb.Max.y - 10);
8343
8344 if (preview_value != NULL)
8345 RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f, 0.0f));
8346
8347 if (label_size.x > 0)
8348 RenderText(ImVec2(frame_bb.Min.x, frame_bb.Min.y - 17), label);
8349
8350 if (pressed && !popup_open)
8351 {
8352 OpenPopupEx(id, false);
8353 popup_open = true;
8354 }
8355
8356 if (!popup_open)
8357 return false;
8358
8359 if (popup_size.x == 0.0f)
8360 popup_size.x = w;
8361
8362 float popup_y1 = frame_bb.Max.y;
8363 float popup_y2 = ImClamp(popup_y1 + popup_size.y, popup_y1, g.IO.DisplaySize.y - style.DisplaySafeAreaPadding.y);
8364 if ((popup_y2 - popup_y1) < ImMin(popup_size.y, frame_bb.Min.y - style.DisplaySafeAreaPadding.y))
8365 {
8366 // Position our combo ABOVE because there's more space to fit! (FIXME: Handle in Begin() or use a shared helper. We have similar code in Begin() for popup placement)
8367 popup_y1 = ImClamp(frame_bb.Min.y - popup_size.y, style.DisplaySafeAreaPadding.y, frame_bb.Min.y);
8368 popup_y2 = frame_bb.Min.y;
8369 SetNextWindowPos(ImVec2(frame_bb.Min.x, frame_bb.Min.y), ImGuiCond_Always);
8370 }
8371 else
8372 {
8373 // Position our combo below
8374 SetNextWindowPos(ImVec2(frame_bb.Min.x, frame_bb.Max.y + 3), ImGuiCond_Always);
8375 }
8376 SetNextWindowSize(ImVec2(popup_size.x, popup_size.y), ImGuiCond_Always);
8377 PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
8378
8379 const ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0);
8380 if (!BeginPopupEx(id, flags))
8381 {
8382 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
8383 return false;
8384 }
8385 Spacing();
8386
8387 return true;
8388}
8389
8390void ImGui::EndCombo()
8391{
8392 EndPopup();
8393 PopStyleVar();
8394}
8395
8396//Combo box function.
8397bool ImGui::Combo(const char* label, int* current_item, bool(*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) {
8398 ImGuiWindow* window = GetCurrentWindow();
8399 if (window->SkipItems)
8400 return false;
8401
8402 Dummy(ImVec2(0, 2));
8403
8404 ImGuiContext& g = *GImGui;
8405 const ImGuiStyle& style = g.Style;
8406 const ImGuiID id = window->GetID(label);
8407 const float w = 175;//CalcItemWidth();
8408
8409 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8410 const ImRect frame_bb(window->DC.CursorPos + ImVec2(18, 0), window->DC.CursorPos + ImVec2(w + 18, label_size.y + style.FramePadding.y * 2.0f - 1));
8411 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));
8412
8413 ItemSize(total_bb, style.FramePadding.y);
8414 if (!ItemAdd(total_bb, &id))
8415 return false;
8416
8417 const float arrow_size = (g.FontSize + style.FramePadding.x * 2.0f);
8418 const bool hovered = IsHovered(frame_bb, id);
8419 bool popup_open = IsPopupOpen(id);
8420 bool popup_opened_now = false;
8421
8422 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
8423
8424 window->DrawList->AddRectFilled(frame_bb.Min - ImVec2(1, 1), frame_bb.Max + ImVec2(1, 1), GetColorU32(ImVec4(0 / 255.f, 0 / 255.f, 0 / 255.f, 0.1f)), style.FrameRounding);
8425 window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), style.FrameRounding);
8426
8427 if (*current_item >= 0 && *current_item < items_count) {
8428 const char* item_text;
8429 if (items_getter(data, *current_item, &item_text))
8430 RenderTextClipped(frame_bb.Min + ImVec2(6, 1), value_bb.Max, item_text, NULL, NULL, ImVec2(0.0f, 0.0f));
8431 }
8432
8433 // lambda for the arrow
8434 auto arrow = [window](int x, int y, float clr = 0.59) {
8435 for (auto i = 5; i >= 2; --i) {
8436 auto offset = 5 - i;
8437
8438 window->DrawList->AddLine(ImVec2(x + offset, y + offset), ImVec2(x + offset + nigga(i - offset, 0, 5), y + offset), GetColorU32(ImVec4(clr, clr, clr, 1)));
8439 }
8440 };
8441
8442 arrow(frame_bb.Max.x - 10, frame_bb.Max.y - 11, 0.f);
8443 arrow(frame_bb.Max.x - 10, frame_bb.Max.y - 10);
8444
8445 if (label_size.x > 0)
8446 RenderText(ImVec2(frame_bb.Min.x, frame_bb.Min.y - 17), label);
8447
8448 if (hovered) {
8449 SetHoveredID(id);
8450 if (g.IO.MouseClicked[0]) {
8451 ClearActiveID();
8452 if (IsPopupOpen(id)) {
8453 ClosePopup(id);
8454 }
8455 else {
8456 FocusWindow(window);
8457 OpenPopup(label);
8458 popup_open = popup_opened_now = true;
8459 }
8460 }
8461 }
8462
8463 bool value_changed = false;
8464 if (IsPopupOpen(id)) {
8465 // Size default to hold ~**5** items
8466 if (height_in_items < 0)
8467 height_in_items = 5;
8468
8469 float popup_height = -8.0f + (label_size.y + style.ItemSpacing.y) * ImMin(items_count, height_in_items) + (style.FramePadding.y * 3);
8470 float popup_y1 = frame_bb.Max.y;
8471 float popup_y2 = ImClamp(popup_y1 + popup_height, popup_y1, g.IO.DisplaySize.y - style.DisplaySafeAreaPadding.y);
8472 if ((popup_y2 - popup_y1) < ImMin(popup_height, frame_bb.Min.y - style.DisplaySafeAreaPadding.y)) {
8473 // Position our combo ABOVE because there's more space to fit! (FIXME: Handle in Begin() or use a shared helper. We have similar code in Begin() for popup placement)
8474 popup_y1 = ImClamp(frame_bb.Min.y - popup_height, style.DisplaySafeAreaPadding.y, frame_bb.Min.y);
8475 popup_y2 = frame_bb.Min.y;
8476 }
8477 ImRect popup_rect(ImVec2(frame_bb.Min.x, popup_y1), ImVec2(frame_bb.Max.x, popup_y2));
8478 SetNextWindowPos(popup_rect.Min + ImVec2(-1, 3));
8479 SetNextWindowSize(popup_rect.GetSize() + ImVec2(2, 0));
8480 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
8481
8482 const ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0);
8483 if (BeginPopupEx(id, flags)) {
8484 // Display items
8485 for (int i = 0; i < items_count; i++) {
8486 PushID((void*)(intptr_t)i);
8487 const bool item_selected = (i == *current_item);
8488 const char* item_text;
8489 if (!items_getter(data, i, &item_text))
8490 item_text = "*Unknown item*";
8491 if (Selectable(item_text, item_selected)) {
8492 ClearActiveID();
8493 value_changed = true;
8494 *current_item = i;
8495 }
8496 if (item_selected && popup_opened_now)
8497 SetScrollHere(0.f);
8498 PopID();
8499 }
8500 EndPopup();
8501 }
8502 PopStyleVar();
8503 }
8504 return value_changed;
8505}
8506
8507// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
8508// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID.
8509bool ImGui::SelectableMeme(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2 & size_arg) {
8510 ImGuiWindow* window = GetCurrentWindow();
8511 if (window->SkipItems)
8512 return false;
8513
8514 ImGuiContext& g = *GImGui;
8515 const ImGuiStyle& style = g.Style;
8516
8517 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) // FIXME-OPT: Avoid if vertically clipped.
8518 PopClipRect();
8519
8520 ImGuiID id = window->GetID(label);
8521 ImVec2 label_size = CalcTextSize(label, NULL, true);
8522 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
8523 ImVec2 pos = window->DC.CursorPos;
8524 pos.y += window->DC.CurrentLineTextBaseOffset;
8525 ImRect bb(pos, pos + size);
8526 ItemSize(bb);
8527
8528 // Fill horizontal space.
8529 ImVec2 window_padding = window->WindowPadding;
8530 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
8531 float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
8532 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);
8533 ImRect bb_with_spacing(pos, pos + size_draw);
8534 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
8535 bb_with_spacing.Max.x += window_padding.x;
8536
8537 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
8538 float spacing_L = (float)(int)(0);
8539 float spacing_U = (float)(int)(0);
8540 float spacing_R = style.ItemSpacing.x - spacing_L;
8541 float spacing_D = style.ItemSpacing.y - spacing_U;
8542 bb_with_spacing.Min.x -= spacing_L;
8543 bb_with_spacing.Min.y -= spacing_U;
8544 bb_with_spacing.Max.x += spacing_R;
8545 bb_with_spacing.Max.y += spacing_D;
8546 if (!ItemAdd(bb_with_spacing, &id)) {
8547 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1)
8548 PushColumnClipRect();
8549 return false;
8550 }
8551
8552 ImGuiButtonFlags button_flags = 0;
8553 if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick;
8554 if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease;
8555 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
8556 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
8557 bool hovered, held;
8558 bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags);
8559 if (flags & ImGuiSelectableFlags_Disabled)
8560 selected = false;
8561
8562 // Render
8563 if (selected) {
8564 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : ImGuiCol_Header);
8565 RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f);
8566 }
8567
8568 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) {
8569 PushColumnClipRect();
8570 bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x);
8571 }
8572
8573 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
8574 RenderTextClipped(bb.Min + ImVec2(4, 2), bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f, 0.0f));
8575 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
8576
8577 // Automatically close popups
8578 if (pressed && !(flags & ImGuiSelectableFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
8579 CloseCurrentPopup();
8580 return pressed;
8581}
8582
8583// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
8584// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID.
8585bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2 & size_arg) {
8586 ImGuiWindow* window = GetCurrentWindow();
8587 if (window->SkipItems)
8588 return false;
8589
8590 ImGuiContext& g = *GImGui;
8591 const ImGuiStyle& style = g.Style;
8592
8593 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) // FIXME-OPT: Avoid if vertically clipped.
8594 PopClipRect();
8595
8596 ImGuiID id = window->GetID(label);
8597 ImVec2 label_size = CalcTextSize(label, NULL, true);
8598 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
8599 ImVec2 pos = window->DC.CursorPos;
8600 pos.y += window->DC.CurrentLineTextBaseOffset;
8601 ImRect bb(pos, pos + size);
8602 ItemSize(bb);
8603
8604 // Fill horizontal space.
8605 ImVec2 window_padding = window->WindowPadding;
8606 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
8607 float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
8608 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);
8609 ImRect bb_with_spacing(pos, pos + size_draw);
8610 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
8611 bb_with_spacing.Max.x += window_padding.x;
8612
8613 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
8614 float spacing_L = (float)(int)(0);
8615 float spacing_U = (float)(int)(0);
8616 float spacing_R = style.ItemSpacing.x - spacing_L;
8617 float spacing_D = style.ItemSpacing.y - spacing_U;
8618 bb_with_spacing.Min.x -= spacing_L;
8619 bb_with_spacing.Min.y -= spacing_U;
8620 bb_with_spacing.Max.x += spacing_R;
8621 bb_with_spacing.Max.y += spacing_D;
8622 if (!ItemAdd(bb_with_spacing, &id)) {
8623 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1)
8624 PushColumnClipRect();
8625 return false;
8626 }
8627
8628 ImGuiButtonFlags button_flags = 0;
8629 if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick;
8630 if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease;
8631 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
8632 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
8633 bool hovered, held;
8634 bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags);
8635 if (flags & ImGuiSelectableFlags_Disabled)
8636 selected = false;
8637
8638 // Render
8639 if (selected) {
8640 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : ImGuiCol_Header);
8641 RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f);
8642 }
8643
8644 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) {
8645 PushColumnClipRect();
8646 bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x);
8647 }
8648
8649 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
8650 RenderTextClipped(bb.Min + ImVec2(4, 4), bb_with_spacing.Max + ImVec2(4, 4), label, NULL, &label_size, ImVec2(0.0f, 0.0f));
8651 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
8652
8653 // Automatically close popups
8654 if (pressed && !(flags & ImGuiSelectableFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
8655 CloseCurrentPopup();
8656 return pressed;
8657}
8658
8659bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2 & size_arg) {
8660 if (Selectable(label, *p_selected, flags, size_arg)) {
8661 *p_selected = !*p_selected;
8662 return true;
8663 }
8664 return false;
8665}
8666
8667// Helper to calculate the size of a listbox and display a label on the right.
8668// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
8669bool ImGui::ListBoxHeader(const char* label, const ImVec2 & size_arg) {
8670 ImGuiWindow* window = GetCurrentWindow();
8671 if (window->SkipItems)
8672 return false;
8673
8674 const ImGuiStyle& style = GetStyle();
8675 const ImGuiID id = GetID(label);
8676 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8677
8678 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
8679 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
8680 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
8681 ImRect frame_bb(window->DC.CursorPos + ImVec2(0, 5), window->DC.CursorPos + frame_size);
8682 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
8683 window->DC.LastItemRect = bb;
8684
8685 SetCursorPosX(GetCursorPosX() + 17);
8686 BeginGroup();
8687 if (label_size.x > 0)
8688 RenderText(ImVec2(frame_bb.Min.x + 17, frame_bb.Min.y - 20), label);
8689
8690 BeginChildFrame(id, frame_bb.GetSize());
8691 return true;
8692}
8693
8694bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) {
8695 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
8696 // 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.
8697 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
8698 if (height_in_items < 0)
8699 height_in_items = ImMin(items_count, 7);
8700 float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
8701
8702 // 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().
8703 ImVec2 size;
8704 size.x = 0.0f;
8705 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
8706 return ListBoxHeader(label, size);
8707}
8708
8709void ImGui::ListBoxFooter() {
8710 ImGuiWindow* parent_window = GetParentWindow();
8711 const ImRect bb = parent_window->DC.LastItemRect;
8712 const ImGuiStyle& style = GetStyle();
8713
8714 EndChildFrame();
8715
8716 SameLine();
8717 parent_window->DC.CursorPos = bb.Min;
8718 ItemSize(bb, style.FramePadding.y);
8719
8720 EndGroup();
8721}
8722
8723bool ImGui::ListBox(const char* label, int* current_item, const char* const* items, int items_count, int height_items) {
8724 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
8725 return value_changed;
8726}
8727
8728bool ImGui::ListBox(const char* label, int* current_item, bool(*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) {
8729 if (!ListBoxHeader(label, items_count, height_in_items))
8730 return false;
8731
8732 // 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.
8733 bool value_changed = false;
8734 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.
8735 while (clipper.Step())
8736 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
8737 const bool item_selected = (i == *current_item);
8738 const char* item_text;
8739 if (!items_getter(data, i, &item_text))
8740 item_text = "*Unknown item*";
8741
8742 PushID(i);
8743 SetCursorPosX(GetCursorPosX() - 4);
8744 SetCursorPosY(GetCursorPosY() - 2);
8745
8746 if (SelectableMeme(item_text, item_selected, 0, ImVec2(0, 0))) {
8747 *current_item = i;
8748 value_changed = true;
8749 }
8750 PopID();
8751 }
8752 ListBoxFooter();
8753 return value_changed;
8754}
8755
8756bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) {
8757 ImGuiWindow* window = GetCurrentWindow();
8758 if (window->SkipItems)
8759 return false;
8760
8761 ImGuiContext& g = *GImGui;
8762 ImVec2 pos = window->DC.CursorPos;
8763 ImVec2 label_size = CalcTextSize(label, NULL, true);
8764 ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
8765 float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
8766 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
8767
8768 bool pressed = Selectable(label, false, ImGuiSelectableFlags_MenuItem | ImGuiSelectableFlags_DrawFillAvailWidth | (enabled ? 0 : ImGuiSelectableFlags_Disabled), ImVec2(w, 0.0f));
8769 if (shortcut_size.x > 0.0f) {
8770 PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
8771 RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
8772 PopStyleColor();
8773 }
8774
8775 if (selected)
8776 RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled));
8777
8778 return pressed;
8779}
8780
8781bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) {
8782 if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) {
8783 if (p_selected)
8784 * p_selected = !*p_selected;
8785 return true;
8786 }
8787 return false;
8788}
8789
8790bool ImGui::BeginMainMenuBar() {
8791 ImGuiContext& g = *GImGui;
8792 SetNextWindowPos(ImVec2(0.0f, 0.0f));
8793 SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f));
8794 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
8795 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
8796 if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar)
8797 || !BeginMenuBar()) {
8798 End();
8799 PopStyleVar(2);
8800 return false;
8801 }
8802 g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x;
8803 return true;
8804}
8805
8806void ImGui::EndMainMenuBar() {
8807 EndMenuBar();
8808 End();
8809 PopStyleVar(2);
8810}
8811
8812bool ImGui::BeginMenuBar() {
8813 ImGuiWindow* window = GetCurrentWindow();
8814 if (window->SkipItems)
8815 return false;
8816 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
8817 return false;
8818
8819 IM_ASSERT(!window->DC.MenuBarAppending);
8820 BeginGroup(); // Save position
8821 PushID("##menubar");
8822 ImRect rect = window->MenuBarRect();
8823 PushClipRect(ImVec2(ImFloor(rect.Min.x + 0.5f), ImFloor(rect.Min.y + window->BorderSize + 0.5f)), ImVec2(ImFloor(rect.Max.x + 0.5f), ImFloor(rect.Max.y + 0.5f)), false);
8824 window->DC.CursorPos = ImVec2(rect.Min.x + window->DC.MenuBarOffsetX, rect.Min.y);// + g.Style.FramePadding.y);
8825 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
8826 window->DC.MenuBarAppending = true;
8827 AlignFirstTextHeightToWidgets();
8828 return true;
8829}
8830
8831void ImGui::EndMenuBar() {
8832 ImGuiWindow* window = GetCurrentWindow();
8833 if (window->SkipItems)
8834 return;
8835
8836 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
8837 IM_ASSERT(window->DC.MenuBarAppending);
8838 PopClipRect();
8839 PopID();
8840 window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x;
8841 window->DC.GroupStack.back().AdvanceCursor = false;
8842 EndGroup();
8843 window->DC.LayoutType = ImGuiLayoutType_Vertical;
8844 window->DC.MenuBarAppending = false;
8845}
8846
8847bool ImGui::BeginMenu(const char* label, bool enabled) {
8848 ImGuiWindow* window = GetCurrentWindow();
8849 if (window->SkipItems)
8850 return false;
8851
8852 ImGuiContext& g = *GImGui;
8853 const ImGuiStyle& style = g.Style;
8854 const ImGuiID id = window->GetID(label);
8855
8856 ImVec2 label_size = CalcTextSize(label, NULL, true);
8857
8858 bool pressed;
8859 bool menu_is_open = IsPopupOpen(id);
8860 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentMenuSet == window->GetID("##menus"));
8861 ImGuiWindow * backed_nav_window = g.NavWindow;
8862 if (menuset_is_open)
8863 g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
8864
8865 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos).
8866 ImVec2 popup_pos, pos = window->DC.CursorPos;
8867 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) {
8868 popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
8869 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
8870 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
8871 float w = label_size.x;
8872 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
8873 PopStyleVar();
8874 SameLine();
8875 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
8876 }
8877 else {
8878 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
8879 float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
8880 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
8881 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
8882 if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
8883 RenderCollapseTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), false);
8884 if (!enabled) PopStyleColor();
8885 }
8886
8887 bool hovered = enabled && IsHovered(window->DC.LastItemRect, id);
8888 if (menuset_is_open)
8889 g.NavWindow = backed_nav_window;
8890
8891 bool want_open = false, want_close = false;
8892 if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) {
8893 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
8894 bool moving_within_opened_triangle = false;
8895 if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window) {
8896 if (ImGuiWindow * next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) {
8897 ImRect next_window_rect = next_window->Rect();
8898 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
8899 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
8900 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
8901 float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
8902 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
8903 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?
8904 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
8905 moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
8906 //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
8907 }
8908 }
8909
8910 want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
8911 want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
8912 }
8913 else if (menu_is_open && pressed && menuset_is_open) // Menu bar: click an open menu again to close it
8914 {
8915 want_close = true;
8916 want_open = menu_is_open = false;
8917 }
8918 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // menu-bar: first click to open, then hover to open others
8919 want_open = true;
8920 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.. }'
8921 want_close = true;
8922 if (want_close && IsPopupOpen(id))
8923 ClosePopupToLevel(GImGui->CurrentPopupStack.Size);
8924
8925 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) {
8926 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
8927 OpenPopup(label);
8928 return false;
8929 }
8930
8931 menu_is_open |= want_open;
8932 if (want_open)
8933 OpenPopup(label);
8934
8935 if (menu_is_open) {
8936 SetNextWindowPos(popup_pos, ImGuiCond_Always);
8937 ImGuiWindowFlags flags = ImGuiWindowFlags_ShowBorders | ((window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu);
8938 menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
8939 }
8940
8941 return menu_is_open;
8942}
8943
8944void ImGui::EndMenu() {
8945 EndPopup();
8946}
8947
8948// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
8949void ImGui::ColorTooltip(const char* text, const float col[4], ImGuiColorEditFlags flags) {
8950 ImGuiContext& g = *GImGui;
8951
8952 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]);
8953 BeginTooltipEx(true);
8954
8955 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
8956 if (text_end > text) {
8957 TextUnformatted(text, text_end);
8958 Separator();
8959 }
8960
8961 ImVec2 sz(g.FontSize * 3, g.FontSize * 3);
8962 ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
8963 SameLine();
8964 if (flags & ImGuiColorEditFlags_NoAlpha)
8965 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]);
8966 else
8967 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]);
8968 EndTooltip();
8969}
8970
8971static inline float ColorSquareSize() {
8972 ImGuiContext& g = *GImGui;
8973 return g.FontSize + g.Style.FramePadding.y * 2.0f;
8974}
8975
8976static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) {
8977 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
8978 int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
8979 int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
8980 int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
8981 return IM_COL32(r, g, b, 0xFF);
8982}
8983
8984// 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.
8985// 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.
8986void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) {
8987 ImGuiWindow* window = GetCurrentWindow();
8988 if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) {
8989 ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204, 204, 204, 255), col));
8990 ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128, 128, 128, 255), col));
8991 window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
8992
8993 int yi = 0;
8994 for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) {
8995 float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
8996 if (y2 <= y1)
8997 continue;
8998 for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) {
8999 float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
9000 if (x2 <= x1)
9001 continue;
9002 int rounding_corners_flags_cell = 0;
9003 if (y1 <= p_min.y) {
9004 if (x1 <= p_min.x) rounding_corners_flags_cell |= ImGuiCorner_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImGuiCorner_TopRight;
9005 }
9006 if (y2 >= p_max.y) {
9007 if (x1 <= p_min.x) rounding_corners_flags_cell |= ImGuiCorner_BottomLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImGuiCorner_BottomRight;
9008 }
9009 rounding_corners_flags_cell &= rounding_corners_flags;
9010 window->DrawList->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
9011 }
9012 }
9013 }
9014 else {
9015 window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
9016 }
9017}
9018
9019void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) {
9020 ImGuiContext& g = *GImGui;
9021 if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
9022 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
9023 if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
9024 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
9025 if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
9026 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
9027 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
9028 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
9029 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
9030 g.ColorEditOptions = flags;
9031}
9032
9033// A little colored square. Return true when clicked.
9034// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
9035// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
9036bool ImGui::ColorButton(const char* desc_id, const ImVec4 & col, ImGuiColorEditFlags flags, ImVec2 size) {
9037 ImGuiWindow* window = GetCurrentWindow();
9038 if (window->SkipItems)
9039 return false;
9040
9041 ImGuiContext& g = *GImGui;
9042 const ImGuiID id = window->GetID(desc_id);
9043 float default_size = ColorSquareSize();
9044 if (size.x == 0.0f)
9045 size.x = 18;
9046 if (size.y == 0.0f)
9047 size.y = 8;
9048 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
9049 ItemSize(bb);
9050 if (!ItemAdd(bb, &id))
9051 return false;
9052
9053 bool hovered, held;
9054 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
9055
9056 if (flags & ImGuiColorEditFlags_NoAlpha)
9057 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
9058
9059 window->DrawList->AddRectFilled(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), GetColorU32(ImGuiCol_Border), 0); // Color button are often in need of some sort of border
9060
9061 ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
9062 float grid_step = ImMin(size.x, size.y) / 2.99f;
9063 float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
9064 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) {
9065 float mid_x = (float)(int)((bb.Min.x + bb.Max.x) * 0.5f + 0.5f);
9066 RenderColorRectWithAlphaCheckerboard(ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, GetColorU32(col), grid_step, ImVec2(-grid_step, 0.0f), rounding, ImGuiCorner_TopRight | ImGuiCorner_BottomRight);
9067 window->DrawList->AddRectFilledMultiColor(bb.Min, ImVec2(mid_x, bb.Max.y), GetColorU32(ImVec4(0, 0, 0, 0.078f)), GetColorU32(ImVec4(0, 0, 0, 0.078f)), GetColorU32(ImVec4(0, 0, 0, 0.45f)), GetColorU32(ImVec4(0, 0, 0, 0.45f)));
9068 }
9069 else {
9070 RenderColorRectWithAlphaCheckerboard(bb.Min, bb.Max, GetColorU32((flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha), grid_step, ImVec2(0, 0), rounding);
9071 window->DrawList->AddRectFilledMultiColor(bb.Min, bb.Max, GetColorU32(ImVec4(0, 0, 0, 0.078f)), GetColorU32(ImVec4(0, 0, 0, 0.078f)), GetColorU32(ImVec4(0, 0, 0, 0.45f)), GetColorU32(ImVec4(0, 0, 0, 0.45f)));
9072 }
9073
9074 return pressed;
9075}
9076
9077bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) {
9078 return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
9079}
9080
9081static void ColorEditOptionsPopup(ImGuiColorEditFlags flags) {
9082 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
9083 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
9084 if ((!allow_opt_inputs && !allow_opt_datatype) || !ImGui::BeginPopup("context"))
9085 return;
9086 ImGuiContext & g = *GImGui;
9087 ImGuiColorEditFlags opts = g.ColorEditOptions;
9088 if (allow_opt_inputs) {
9089 if (ImGui::RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
9090 if (ImGui::RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
9091 if (ImGui::RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
9092 }
9093 if (allow_opt_datatype) {
9094 if (allow_opt_inputs) ImGui::Separator();
9095 if (ImGui::RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
9096 if (ImGui::RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
9097 }
9098 g.ColorEditOptions = opts;
9099 ImGui::EndPopup();
9100}
9101
9102static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, float* ref_col) {
9103 bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
9104 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
9105 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
9106 return;
9107 ImGuiContext & g = *GImGui;
9108 if (allow_opt_picker) {
9109 ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ColorSquareSize() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
9110 ImGui::PushItemWidth(picker_size.x);
9111 for (int picker_type = 0; picker_type < 2; picker_type++) {
9112 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
9113 if (picker_type > 0) ImGui::Separator();
9114 ImGui::PushID(picker_type);
9115 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
9116 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
9117 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
9118 ImVec2 backup_pos = ImGui::GetCursorScreenPos();
9119 if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
9120 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
9121 ImGui::SetCursorScreenPos(backup_pos);
9122 ImVec4 dummy_ref_col;
9123 memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
9124 ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
9125 ImGui::PopID();
9126 }
9127 ImGui::PopItemWidth();
9128 }
9129 if (allow_opt_alpha_bar) {
9130 if (allow_opt_picker) ImGui::Separator();
9131 ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)& g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
9132 }
9133 ImGui::EndPopup();
9134}
9135
9136// Edit colors components (each component in 0.0f..1.0f range).
9137// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
9138// 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.
9139bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) {
9140 ImGui::SameLine();
9141 ImGui::SetCursorPos(ImVec2(ImGui::GetWindowSize().x - 32.0f - ImGui::GetStyle().FramePadding.x * 2.0f, ImGui::GetCursorPosY() + 1));
9142
9143 ImGui::GetStyle().WindowPadding = ImVec2(8, 8);
9144
9145 ImGuiWindow * window = GetCurrentWindow();
9146 if (window->SkipItems)
9147 return false;
9148
9149 ImGuiContext & g = *GImGui;
9150 const ImGuiStyle & style = g.Style;
9151 const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (ColorSquareSize() + style.ItemInnerSpacing.x);
9152 const float w_items_all = CalcItemWidth() - w_extra;
9153 const char* label_display_end = FindRenderedTextEnd(label);
9154
9155 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
9156 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
9157 const int components = alpha ? 4 : 3;
9158 const ImGuiColorEditFlags flags_untouched = flags;
9159
9160 BeginGroup();
9161 PushID(label);
9162
9163 // If we're not showing any slider there's no point in doing any HSV conversions
9164 if (flags & ImGuiColorEditFlags_NoInputs)
9165 flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
9166
9167 // Context menu: display and modify options (before defaults are applied)
9168 if (!(flags & ImGuiColorEditFlags_NoOptions))
9169 ColorEditOptionsPopup(flags);
9170
9171 // Read stored options
9172 if (!(flags & ImGuiColorEditFlags__InputsMask))
9173 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
9174 if (!(flags & ImGuiColorEditFlags__DataTypeMask))
9175 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
9176 if (!(flags & ImGuiColorEditFlags__PickerMask))
9177 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
9178 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
9179
9180 // Convert to the formats we need
9181 float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
9182 if (flags & ImGuiColorEditFlags_HSV)
9183 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
9184 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]) };
9185
9186 bool value_changed = false;
9187 bool value_changed_as_float = false;
9188
9189 if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) {
9190 // RGB/HSV 0..255 Sliders
9191 const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
9192 const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
9193
9194 const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
9195 const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
9196 const char* fmt_table_int[3][4] =
9197 {
9198 { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, // Short display
9199 { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, // Long display for RGBA
9200 { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } // Long display for HSVA
9201 };
9202 const char* fmt_table_float[3][4] =
9203 {
9204 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
9205 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
9206 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
9207 };
9208 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
9209
9210 PushItemWidth(w_item_one);
9211 for (int n = 0; n < components; n++) {
9212 if (n > 0)
9213 SameLine(0, style.ItemInnerSpacing.x);
9214 if (n + 1 == components)
9215 PushItemWidth(w_item_last);
9216 if (flags & ImGuiColorEditFlags_Float)
9217 value_changed |= value_changed_as_float |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
9218 else
9219 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
9220 if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1))
9221 OpenPopup("context");
9222 }
9223 PopItemWidth();
9224 PopItemWidth();
9225 }
9226
9227 bool picker_active = false;
9228 if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) {
9229 if (!(flags & ImGuiColorEditFlags_NoInputs))
9230 SameLine(0, style.ItemInnerSpacing.x);
9231
9232 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
9233
9234 if (ColorButton("##ColorButton", col_v4, flags)) {
9235 if (!(flags & ImGuiColorEditFlags_NoPicker)) {
9236 // Store current color and open a picker
9237 g.ColorPickerRef = col_v4;
9238 OpenPopup("picker");
9239 SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-2, style.ItemSpacing.y));
9240 }
9241 }
9242
9243 if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1))
9244 OpenPopup("context");
9245
9246 if (BeginPopupEx(g.CurrentWindow->GetID("picker"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_ShowBorders)) {
9247 picker_active = true;
9248 float square_sz = ColorSquareSize();
9249 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
9250 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
9251 PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
9252 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
9253 PopItemWidth();
9254 EndPopup();
9255 }
9256 }
9257
9258 // Convert back
9259 if (!picker_active) {
9260 if (!value_changed_as_float)
9261 for (int n = 0; n < 4; n++)
9262 f[n] = i[n] / 255.0f;
9263 if (flags & ImGuiColorEditFlags_HSV)
9264 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
9265 if (value_changed) {
9266 col[0] = f[0];
9267 col[1] = f[1];
9268 col[2] = f[2];
9269 if (alpha)
9270 col[3] = f[3];
9271 }
9272 }
9273
9274 PopID();
9275 EndGroup();
9276
9277 ImGui::GetStyle().WindowPadding = ImVec2(16, 18);
9278
9279 return value_changed;
9280}
9281
9282// 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side.
9283static void RenderArrow(ImDrawList * draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) {
9284 switch (direction) {
9285 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;
9286 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;
9287 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;
9288 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;
9289 default: return; // Fix warning for ImGuiDir_None
9290 }
9291}
9292
9293static void RenderArrowsForVerticalBar(ImDrawList * draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) {
9294 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);
9295 RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
9296 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);
9297 RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
9298}
9299
9300static void PaintVertsLinearGradientKeepAlpha(ImDrawVert * vert_start, ImDrawVert * vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) {
9301 ImVec2 gradient_extent = gradient_p1 - gradient_p0;
9302 float gradient_inv_length = ImInvLength(gradient_extent, 0.0f);
9303 for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) {
9304 float d = ImDot(vert->pos - gradient_p0, gradient_extent);
9305 float t = ImMin(sqrtf(ImMax(d, 0.0f)) * gradient_inv_length, 1.0f);
9306 int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t);
9307 int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t);
9308 int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t);
9309 vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK);
9310 }
9311}
9312
9313// ColorPicker
9314// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
9315// 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..)
9316bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) {
9317 ImGuiContext& g = *GImGui;
9318 ImGuiWindow* window = GetCurrentWindow();
9319 ImDrawList* draw_list = window->DrawList;
9320
9321 ImGuiStyle& style = g.Style;
9322 ImGuiIO& io = g.IO;
9323
9324 PushID(label);
9325 BeginGroup();
9326
9327 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
9328 flags |= ImGuiColorEditFlags_NoSmallPreview;
9329
9330 // Context menu: display and store options.
9331 if (!(flags & ImGuiColorEditFlags_NoOptions))
9332 ColorPickerOptionsPopup(flags, col);
9333
9334 // Read stored options
9335 if (!(flags & ImGuiColorEditFlags__PickerMask))
9336 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
9337 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
9338 if (!(flags & ImGuiColorEditFlags_NoOptions))
9339 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
9340
9341 // Setup
9342 bool alpha_bar = true/*, because fuck you.*/;
9343 ImVec2 picker_pos = window->DC.CursorPos;
9344 float bars_width = 14; // Arbitrary smallish width of Hue/Alpha picking bars
9345 float sv_picker_size = 160; // ImMax( bars_width * 1, CalcItemWidth( ) - ( alpha_bar ? 2 : 1 ) * ( bars_width + style.ItemInnerSpacing.x ) ); // Saturation/Value picking box
9346 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
9347 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
9348 float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
9349
9350 float wheel_thickness = sv_picker_size * 0.08f;
9351 float wheel_r_outer = sv_picker_size * 0.50f;
9352 float wheel_r_inner = wheel_r_outer - wheel_thickness;
9353 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width) * 0.5f, picker_pos.y + sv_picker_size * 0.5f);
9354
9355 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
9356 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
9357 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
9358 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
9359 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
9360
9361 float H, S, V;
9362 ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
9363
9364 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
9365
9366 // SV rectangle logic
9367 InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
9368 if (IsItemActive()) {
9369 S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
9370 V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
9371 value_changed = value_changed_sv = true;
9372 }
9373
9374 // Hue bar logic
9375 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
9376 InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
9377 if (IsItemActive()) {
9378 H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
9379 value_changed = value_changed_h = true;
9380 }
9381
9382 if (!(flags & ImGuiColorEditFlags_NoLabel)) {
9383 const char* label_display_end = FindRenderedTextEnd(label);
9384 if (label != label_display_end) {
9385 if ((flags & ImGuiColorEditFlags_NoSidePreview))
9386 SameLine(0, style.ItemInnerSpacing.x);
9387 }
9388 }
9389
9390 // Convert back color to RGB
9391 if (value_changed_h || value_changed_sv)
9392 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]);
9393
9394 // Try to cancel hue wrap (after ColorEdit), if any
9395 if (value_changed) {
9396 float new_H, new_S, new_V;
9397 ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
9398 if (new_H <= 0 && H > 0) {
9399 if (new_V <= 0 && V != new_V)
9400 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
9401 else if (new_S <= 0)
9402 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
9403 }
9404 }
9405
9406 ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
9407 ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
9408 ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
9409
9410 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) };
9411 ImVec2 sv_cursor_pos;
9412
9413 if (flags & ImGuiColorEditFlags_PickerHueBar) {
9414 // Alpha bar logic
9415 if (alpha_bar) {
9416 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
9417 InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
9418 if (IsItemActive()) {
9419 col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
9420 value_changed = true;
9421 }
9422 }
9423
9424 draw_list->AddRectFilled(ImVec2(bar0_pos_x - 1, picker_pos.y - 1), ImVec2(bar0_pos_x + bars_width + 1, picker_pos.y + sv_picker_size + 1), IM_COL32(0, 0, 0, 255));
9425
9426 // Render SV Square
9427 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
9428 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);
9429
9430 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
9431 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);
9432
9433 // Render Hue Bar
9434 for (int i = 0; i < 6; ++i)
9435 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]);
9436 float bar0_line_y = (float)(int)(picker_pos.y + H * (sv_picker_size - 4.0f + 0.5f));
9437 draw_list->AddRectFilled(ImVec2(bar0_pos_x, bar0_line_y), ImVec2(bar0_pos_x + bars_width, bar0_line_y + 4), IM_COL32_BLACK);
9438 draw_list->AddRectFilled(ImVec2(bar0_pos_x + 1, bar0_line_y + 1), ImVec2(bar0_pos_x + bars_width - 1, bar0_line_y + 3), hue_color32);
9439 }
9440
9441 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
9442 float sv_cursor_rad = 3;
9443 draw_list->AddRectFilled(sv_cursor_pos - ImVec2(2, 2), sv_cursor_pos + ImVec2(2, 2), IM_COL32(0, 0, 0, 255));
9444 draw_list->AddRectFilled(sv_cursor_pos - ImVec2(1, 1), sv_cursor_pos + ImVec2(1, 1), col32_no_alpha);
9445
9446 if (alpha_bar) {
9447 float alpha = ImSaturate(col[3]);
9448 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
9449 RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0, 0, 0, 0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
9450 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);
9451 float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * (sv_picker_size - 4.0f + 0.5f));
9452 RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
9453 draw_list->AddRect(ImVec2(bar1_pos_x, bar1_line_y), ImVec2(bar1_pos_x + bars_width, bar1_line_y + 4), IM_COL32_BLACK);
9454 draw_list->AddRectFilled(ImVec2(bar1_pos_x + 1, bar1_line_y + 1), ImVec2(bar1_pos_x + bars_width - 1, bar1_line_y + 3), IM_COL32(col[0], col[1], col[2], col[3]));
9455 }
9456
9457 EndGroup();
9458 PopID();
9459
9460 return value_changed;
9461}
9462
9463// Horizontal separating line.
9464void ImGui::Separator() {
9465 ImGuiWindow* window = GetCurrentWindow();
9466 if (window->SkipItems)
9467 return;
9468
9469 if (window->DC.ColumnsCount > 1)
9470 PopClipRect();
9471
9472 float x1 = window->Pos.x;
9473 float x2 = window->Pos.x + window->Size.x;
9474 if (!window->DC.GroupStack.empty())
9475 x1 += window->DC.IndentX;
9476
9477 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + 1.0f));
9478 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.
9479 if (!ItemAdd(bb, NULL)) {
9480 if (window->DC.ColumnsCount > 1)
9481 PushColumnClipRect();
9482 return;
9483 }
9484
9485 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
9486
9487 ImGuiContext& g = *GImGui;
9488 if (g.LogEnabled)
9489 LogText(IM_NEWLINE "--------------------------------");
9490
9491 if (window->DC.ColumnsCount > 1) {
9492 PushColumnClipRect();
9493 window->DC.ColumnsCellMinY = window->DC.CursorPos.y;
9494 }
9495}
9496
9497void ImGui::Spacing() {
9498 ImGuiWindow* window = GetCurrentWindow();
9499 if (window->SkipItems)
9500 return;
9501 ItemSize(ImVec2(0, 0));
9502}
9503
9504void ImGui::Dummy(const ImVec2 & size) {
9505 ImGuiWindow* window = GetCurrentWindow();
9506 if (window->SkipItems)
9507 return;
9508
9509 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
9510 ItemSize(bb);
9511 ItemAdd(bb, NULL);
9512}
9513
9514bool ImGui::IsRectVisible(const ImVec2 & size) {
9515 ImGuiWindow* window = GetCurrentWindowRead();
9516 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
9517}
9518
9519bool ImGui::IsRectVisible(const ImVec2 & rect_min, const ImVec2 & rect_max) {
9520 ImGuiWindow* window = GetCurrentWindowRead();
9521 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
9522}
9523
9524// 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.)
9525void ImGui::BeginGroup() {
9526 ImGuiWindow* window = GetCurrentWindow();
9527
9528 window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
9529 ImGuiGroupData & group_data = window->DC.GroupStack.back();
9530 group_data.BackupCursorPos = window->DC.CursorPos;
9531 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
9532 group_data.BackupIndentX = window->DC.IndentX;
9533 group_data.BackupGroupOffsetX = window->DC.GroupOffsetX;
9534 group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight;
9535 group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
9536 group_data.BackupLogLinePosY = window->DC.LogLinePosY;
9537 group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive;
9538 group_data.AdvanceCursor = true;
9539
9540 window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX;
9541 window->DC.IndentX = window->DC.GroupOffsetX;
9542 window->DC.CursorMaxPos = window->DC.CursorPos;
9543 window->DC.CurrentLineHeight = 0.0f;
9544 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
9545}
9546
9547void ImGui::EndGroup() {
9548 ImGuiContext& g = *GImGui;
9549 ImGuiWindow* window = GetCurrentWindow();
9550
9551 IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
9552
9553 ImGuiGroupData& group_data = window->DC.GroupStack.back();
9554
9555 ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
9556 group_bb.Max.y -= g.Style.ItemSpacing.y; // Cancel out last vertical spacing because we are adding one ourselves.
9557 group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
9558
9559 window->DC.CursorPos = group_data.BackupCursorPos;
9560 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
9561 window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight;
9562 window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
9563 window->DC.IndentX = group_data.BackupIndentX;
9564 window->DC.GroupOffsetX = group_data.BackupGroupOffsetX;
9565 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
9566
9567 if (group_data.AdvanceCursor) {
9568 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.
9569 ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
9570 ItemAdd(group_bb, NULL);
9571 }
9572
9573 // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will function on the entire group.
9574 // 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.
9575 const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow);
9576 if (active_id_within_group)
9577 window->DC.LastItemId = g.ActiveId;
9578 if (active_id_within_group && g.HoveredId == g.ActiveId)
9579 window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = true;
9580
9581 window->DC.GroupStack.pop_back();
9582
9583 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
9584}
9585
9586// Gets back to previous line and continue with horizontal layout
9587// pos_x == 0 : follow right after previous item
9588// pos_x != 0 : align to specified x position (relative to window/group left)
9589// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
9590// spacing_w >= 0 : enforce spacing amount
9591void ImGui::SameLine(float pos_x, float spacing_w) {
9592 ImGuiWindow* window = GetCurrentWindow();
9593 if (window->SkipItems)
9594 return;
9595
9596 ImGuiContext& g = *GImGui;
9597 if (pos_x != 0.0f) {
9598 if (spacing_w < 0.0f) spacing_w = 0.0f;
9599 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX;
9600 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
9601 }
9602 else {
9603 if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
9604 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
9605 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
9606 }
9607 window->DC.CurrentLineHeight = window->DC.PrevLineHeight;
9608 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
9609}
9610
9611void ImGui::NewLine() {
9612 ImGuiWindow* window = GetCurrentWindow();
9613 if (window->SkipItems)
9614 return;
9615 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.
9616 ItemSize(ImVec2(0, 0));
9617 else
9618 ItemSize(ImVec2(0.0f, GImGui->FontSize));
9619}
9620
9621void ImGui::NextColumn() {
9622 ImGuiWindow* window = GetCurrentWindow();
9623 if (window->SkipItems || window->DC.ColumnsCount <= 1)
9624 return;
9625
9626 ImGuiContext & g = *GImGui;
9627 PopItemWidth();
9628 PopClipRect();
9629
9630 window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y);
9631 if (++window->DC.ColumnsCurrent < window->DC.ColumnsCount) {
9632 // Columns 1+ cancel out IndentX
9633 window->DC.ColumnsOffsetX = GetColumnOffset(window->DC.ColumnsCurrent) - window->DC.IndentX + g.Style.ItemSpacing.x;
9634 window->DrawList->ChannelsSetCurrent(window->DC.ColumnsCurrent);
9635 }
9636 else {
9637 window->DC.ColumnsCurrent = 0;
9638 window->DC.ColumnsOffsetX = 0.0f;
9639 window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY;
9640 window->DrawList->ChannelsSetCurrent(0);
9641 }
9642 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
9643 window->DC.CursorPos.y = window->DC.ColumnsCellMinY;
9644 window->DC.CurrentLineHeight = 0.0f;
9645 window->DC.CurrentLineTextBaseOffset = 0.0f;
9646
9647 PushColumnClipRect();
9648 PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup
9649}
9650
9651int ImGui::GetColumnIndex() {
9652 ImGuiWindow* window = GetCurrentWindowRead();
9653 return window->DC.ColumnsCurrent;
9654}
9655
9656int ImGui::GetColumnsCount() {
9657 ImGuiWindow* window = GetCurrentWindowRead();
9658 return window->DC.ColumnsCount;
9659}
9660
9661static float OffsetNormToPixels(ImGuiWindow * window, float offset_norm) {
9662 return offset_norm * (window->DC.ColumnsMaxX - window->DC.ColumnsMinX);
9663}
9664
9665static float PixelsToOffsetNorm(ImGuiWindow * window, float offset) {
9666 return (offset - window->DC.ColumnsMinX) / (window->DC.ColumnsMaxX - window->DC.ColumnsMinX);
9667}
9668
9669static float GetDraggedColumnOffset(int column_index) {
9670 // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
9671 // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
9672 ImGuiContext& g = *GImGui;
9673 ImGuiWindow* window = ImGui::GetCurrentWindowRead();
9674 IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets.
9675 IM_ASSERT(g.ActiveId == window->DC.ColumnsSetId + ImGuiID(column_index));
9676
9677 float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x - window->Pos.x;
9678 x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
9679 if ((window->DC.ColumnsFlags & ImGuiColumnsFlags_NoPreserveWidths))
9680 x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
9681
9682 return x;
9683}
9684
9685float ImGui::GetColumnOffset(int column_index) {
9686 ImGuiWindow* window = GetCurrentWindowRead();
9687 if (column_index < 0)
9688 column_index = window->DC.ColumnsCurrent;
9689
9690 /*
9691 if (g.ActiveId)
9692 {
9693 ImGuiContext& g = *GImGui;
9694 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(column_index);
9695 if (g.ActiveId == column_id)
9696 return GetDraggedColumnOffset(column_index);
9697 }
9698 */
9699
9700 IM_ASSERT(column_index < window->DC.ColumnsData.Size);
9701 const float t = window->DC.ColumnsData[column_index].OffsetNorm;
9702 const float x_offset = ImLerp(window->DC.ColumnsMinX, window->DC.ColumnsMaxX, t);
9703 return x_offset;
9704}
9705
9706void ImGui::SetColumnOffset(int column_index, float offset) {
9707 ImGuiContext& g = *GImGui;
9708 ImGuiWindow* window = GetCurrentWindow();
9709 if (column_index < 0)
9710 column_index = window->DC.ColumnsCurrent;
9711
9712 IM_ASSERT(column_index < window->DC.ColumnsData.Size);
9713
9714 const bool preserve_width = !(window->DC.ColumnsFlags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < window->DC.ColumnsCount - 1);
9715 const float width = preserve_width ? GetColumnWidth(column_index) : 0.0f;
9716
9717 if (!(window->DC.ColumnsFlags & ImGuiColumnsFlags_NoForceWithinWindow))
9718 offset = ImMin(offset, window->DC.ColumnsMaxX - g.Style.ColumnsMinSpacing * (window->DC.ColumnsCount - column_index));
9719 const float offset_norm = PixelsToOffsetNorm(window, offset);
9720
9721 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(column_index);
9722 window->DC.StateStorage->SetFloat(column_id, offset_norm);
9723 window->DC.ColumnsData[column_index].OffsetNorm = offset_norm;
9724
9725 if (preserve_width)
9726 SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
9727}
9728
9729float ImGui::GetColumnWidth(int column_index) {
9730 ImGuiWindow* window = GetCurrentWindowRead();
9731 if (column_index < 0)
9732 column_index = window->DC.ColumnsCurrent;
9733
9734 return OffsetNormToPixels(window, window->DC.ColumnsData[column_index + 1].OffsetNorm - window->DC.ColumnsData[column_index].OffsetNorm);
9735}
9736
9737void ImGui::SetColumnWidth(int column_index, float width) {
9738 ImGuiWindow* window = GetCurrentWindowRead();
9739 if (column_index < 0)
9740 column_index = window->DC.ColumnsCurrent;
9741
9742 SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
9743}
9744
9745void ImGui::PushColumnClipRect(int column_index) {
9746 ImGuiWindow* window = GetCurrentWindowRead();
9747 if (column_index < 0)
9748 column_index = window->DC.ColumnsCurrent;
9749
9750 PushClipRect(window->DC.ColumnsData[column_index].ClipRect.Min, window->DC.ColumnsData[column_index].ClipRect.Max, false);
9751}
9752
9753void ImGui::BeginColumns(const char* id, int columns_count, ImGuiColumnsFlags flags) {
9754 ImGuiContext& g = *GImGui;
9755 ImGuiWindow* window = GetCurrentWindow();
9756
9757 IM_ASSERT(columns_count > 1);
9758 IM_ASSERT(window->DC.ColumnsCount == 1); // Nested columns are currently not supported
9759
9760 // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
9761 // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
9762 PushID(0x11223347 + (id ? 0 : columns_count));
9763 window->DC.ColumnsSetId = window->GetID(id ? id : "columns");
9764 PopID();
9765
9766 // Set state for first column
9767 window->DC.ColumnsCurrent = 0;
9768 window->DC.ColumnsCount = columns_count;
9769 window->DC.ColumnsFlags = flags;
9770
9771 const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->Size.x - window->ScrollbarSizes.x);
9772 window->DC.ColumnsMinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range
9773 //window->DC.ColumnsMaxX = content_region_width - window->Scroll.x -((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x;
9774 window->DC.ColumnsMaxX = content_region_width - window->Scroll.x;
9775 window->DC.ColumnsStartPosY = window->DC.CursorPos.y;
9776 window->DC.ColumnsStartMaxPosX = window->DC.CursorMaxPos.x;
9777 window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY = window->DC.CursorPos.y;
9778 window->DC.ColumnsOffsetX = 0.0f;
9779 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
9780
9781 // Cache column offsets
9782 window->DC.ColumnsData.resize(columns_count + 1);
9783 for (int column_index = 0; column_index < columns_count + 1; column_index++) {
9784 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(column_index);
9785 KeepAliveID(column_id);
9786 const float default_t = column_index / (float)window->DC.ColumnsCount;
9787 float t = window->DC.StateStorage->GetFloat(column_id, default_t);
9788 if (!(window->DC.ColumnsFlags & ImGuiColumnsFlags_NoForceWithinWindow))
9789 t = ImMin(t, PixelsToOffsetNorm(window, window->DC.ColumnsMaxX - g.Style.ColumnsMinSpacing * (window->DC.ColumnsCount - column_index)));
9790 window->DC.ColumnsData[column_index].OffsetNorm = t;
9791 }
9792
9793 // Cache clipping rectangles
9794 for (int column_index = 0; column_index < columns_count; column_index++) {
9795 float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(column_index) - 1.0f);
9796 float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(column_index + 1) - 1.0f);
9797 window->DC.ColumnsData[column_index].ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
9798 window->DC.ColumnsData[column_index].ClipRect.ClipWith(window->ClipRect);
9799 }
9800
9801 window->DrawList->ChannelsSplit(window->DC.ColumnsCount);
9802 PushColumnClipRect();
9803 PushItemWidth(GetColumnWidth() * 0.65f);
9804}
9805
9806void ImGui::EndColumns() {
9807 ImGuiContext& g = *GImGui;
9808 ImGuiWindow* window = GetCurrentWindow();
9809 IM_ASSERT(window->DC.ColumnsCount > 1);
9810
9811 PopItemWidth();
9812 PopClipRect();
9813 window->DrawList->ChannelsMerge();
9814
9815 window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y);
9816 window->DC.CursorPos.y = window->DC.ColumnsCellMaxY;
9817 window->DC.CursorMaxPos.x = ImMax(window->DC.ColumnsStartMaxPosX, window->DC.ColumnsMaxX); // Columns don't grow parent
9818
9819 // Draw columns borders and handle resize
9820 if (!(window->DC.ColumnsFlags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) {
9821 const float y1 = window->DC.ColumnsStartPosY;
9822 const float y2 = window->DC.CursorPos.y;
9823 int dragging_column = -1;
9824 for (int i = 1; i < window->DC.ColumnsCount; i++) {
9825 float x = window->Pos.x + GetColumnOffset(i);
9826 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(i);
9827 const float column_w = 4.0f; // Width for interaction
9828 const ImRect column_rect(ImVec2(x - column_w, y1), ImVec2(x + column_w, y2));
9829 if (IsClippedEx(column_rect, &column_id, false))
9830 continue;
9831
9832 bool hovered = false, held = false;
9833 if (!(window->DC.ColumnsFlags & ImGuiColumnsFlags_NoResize)) {
9834 ButtonBehavior(column_rect, column_id, &hovered, &held);
9835 if (hovered || held)
9836 g.MouseCursor = ImGuiMouseCursor_ResizeEW;
9837 if (held && g.ActiveIdIsJustActivated)
9838 g.ActiveIdClickOffset.x -= column_w; // Store from center of column line (we used a 8 wide rect for columns clicking). This is used by GetDraggedColumnOffset().
9839 if (held)
9840 dragging_column = i;
9841 }
9842
9843 // Draw column
9844 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
9845 const float xi = (float)(int)x;
9846 window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col);
9847 }
9848
9849 // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
9850 if (dragging_column != -1) {
9851 float x = GetDraggedColumnOffset(dragging_column);
9852 SetColumnOffset(dragging_column, x);
9853 }
9854 }
9855
9856 window->DC.ColumnsSetId = 0;
9857 window->DC.ColumnsCurrent = 0;
9858 window->DC.ColumnsCount = 1;
9859 window->DC.ColumnsFlags = 0;
9860 window->DC.ColumnsData.resize(0);
9861 window->DC.ColumnsOffsetX = 0.0f;
9862 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
9863}
9864
9865// [2017/08: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing]
9866void ImGui::Columns(int columns_count, const char* id, bool border) {
9867 ImGuiWindow* window = GetCurrentWindow();
9868 IM_ASSERT(columns_count >= 1);
9869
9870 if (window->DC.ColumnsCount != columns_count && window->DC.ColumnsCount != 1)
9871 EndColumns();
9872
9873 ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
9874 //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
9875 if (columns_count != 1)
9876 BeginColumns(id, columns_count, flags);
9877}
9878
9879void ImGui::Indent(float indent_w) {
9880 ImGuiContext& g = *GImGui;
9881 ImGuiWindow* window = GetCurrentWindow();
9882 window->DC.IndentX += (indent_w > 0.0f) ? indent_w : g.Style.IndentSpacing;
9883 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
9884}
9885
9886void ImGui::Unindent(float indent_w) {
9887 ImGuiContext& g = *GImGui;
9888 ImGuiWindow* window = GetCurrentWindow();
9889 window->DC.IndentX -= (indent_w > 0.0f) ? indent_w : g.Style.IndentSpacing;
9890 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
9891}
9892
9893void ImGui::TreePush(const char* str_id) {
9894 ImGuiWindow* window = GetCurrentWindow();
9895 Indent();
9896 window->DC.TreeDepth++;
9897 PushID(str_id ? str_id : "#TreePush");
9898}
9899
9900void ImGui::TreePush(const void* ptr_id) {
9901 ImGuiWindow* window = GetCurrentWindow();
9902 Indent();
9903 window->DC.TreeDepth++;
9904 PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
9905}
9906
9907void ImGui::TreePushRawID(ImGuiID id) {
9908 ImGuiWindow* window = GetCurrentWindow();
9909 Indent();
9910 window->DC.TreeDepth++;
9911 window->IDStack.push_back(id);
9912}
9913
9914void ImGui::TreePop() {
9915 ImGuiWindow* window = GetCurrentWindow();
9916 Unindent();
9917 window->DC.TreeDepth--;
9918 PopID();
9919}
9920
9921void ImGui::Value(const char* prefix, bool b) {
9922 Text("%s: %s", prefix, (b ? "true" : "false"));
9923}
9924
9925void ImGui::Value(const char* prefix, int v) {
9926 Text("%s: %d", prefix, v);
9927}
9928
9929void ImGui::Value(const char* prefix, unsigned int v) {
9930 Text("%s: %d", prefix, v);
9931}
9932
9933void ImGui::Value(const char* prefix, float v, const char* float_format) {
9934 if (float_format) {
9935 char fmt[64];
9936 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
9937 Text(fmt, prefix, v);
9938 }
9939 else {
9940 Text("%s: %.3f", prefix, v);
9941 }
9942}
9943
9944//-----------------------------------------------------------------------------
9945// PLATFORM DEPENDENT HELPERS
9946//-----------------------------------------------------------------------------
9947
9948#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS))
9949#undef WIN32_LEAN_AND_MEAN
9950#define WIN32_LEAN_AND_MEAN
9951#include <windows.h>
9952#endif
9953
9954// Win32 API clipboard implementation
9955#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS)
9956
9957#ifdef _MSC_VER
9958#pragma comment(lib, "user32")
9959#endif
9960
9961static const char* GetClipboardTextFn_DefaultImpl(void*) {
9962 static ImVector<char> buf_local;
9963 buf_local.clear();
9964 if (!OpenClipboard(NULL))
9965 return NULL;
9966 HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT);
9967 if (wbuf_handle == NULL) {
9968 CloseClipboard();
9969 return NULL;
9970 }
9971 if (ImWchar * wbuf_global = (ImWchar*)GlobalLock(wbuf_handle)) {
9972 int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1;
9973 buf_local.resize(buf_len);
9974 ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL);
9975 }
9976 GlobalUnlock(wbuf_handle);
9977 CloseClipboard();
9978 return buf_local.Data;
9979}
9980
9981static void SetClipboardTextFn_DefaultImpl(void*, const char* text) {
9982 if (!OpenClipboard(NULL))
9983 return;
9984 const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1;
9985 HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar));
9986 if (wbuf_handle == NULL)
9987 return;
9988 ImWchar * wbuf_global = (ImWchar*)GlobalLock(wbuf_handle);
9989 ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL);
9990 GlobalUnlock(wbuf_handle);
9991 EmptyClipboard();
9992 SetClipboardData(CF_UNICODETEXT, wbuf_handle);
9993 CloseClipboard();
9994}
9995
9996#else
9997
9998// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
9999static const char* GetClipboardTextFn_DefaultImpl(void*) {
10000 ImGuiContext& g = *GImGui;
10001 return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin();
10002}
10003
10004// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
10005static void SetClipboardTextFn_DefaultImpl(void*, const char* text) {
10006 ImGuiContext& g = *GImGui;
10007 g.PrivateClipboard.clear();
10008 const char* text_end = text + strlen(text);
10009 g.PrivateClipboard.resize((size_t)(text_end - text) + 1);
10010 memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text));
10011 g.PrivateClipboard[(int)(text_end - text)] = 0;
10012}
10013
10014#endif
10015
10016// Win32 API IME support (for Asian languages, etc.)
10017#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS)
10018
10019#include <imm.h>
10020#ifdef _MSC_VER
10021#pragma comment(lib, "imm32")
10022#endif
10023
10024static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) {
10025 // Notify OS Input Method Editor of text input position
10026 if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle)
10027 if (HIMC himc = ImmGetContext(hwnd)) {
10028 COMPOSITIONFORM cf;
10029 cf.ptCurrentPos.x = x;
10030 cf.ptCurrentPos.y = y;
10031 cf.dwStyle = CFS_FORCE_POSITION;
10032 ImmSetCompositionWindow(himc, &cf);
10033 }
10034}
10035
10036#else
10037
10038static void ImeSetInputScreenPosFn_DefaultImpl(int, int) { }
10039
10040#endif
10041
10042//-----------------------------------------------------------------------------
10043// HELP
10044//-----------------------------------------------------------------------------
10045
10046void ImGui::ShowMetricsWindow(bool* p_open) {
10047 if (ImGui::Begin("ImGui Metrics", p_open)) {
10048 ImGui::Text("ImGui %s", ImGui::GetVersion());
10049 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
10050 ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3);
10051 ImGui::Text("%d allocations", ImGui::GetIO().MetricsAllocs);
10052 static bool show_clip_rects = true;
10053 ImGui::Checkbox("Show clipping rectangles when hovering an ImDrawCmd", &show_clip_rects);
10054 ImGui::Separator();
10055
10056 struct Funcs {
10057 static void NodeDrawList(ImDrawList* draw_list, const char* label) {
10058 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);
10059 if (draw_list == ImGui::GetWindowDrawList()) {
10060 ImGui::SameLine();
10061 ImGui::TextColored(ImColor(255, 100, 100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
10062 if (node_open) ImGui::TreePop();
10063 return;
10064 }
10065 if (!node_open)
10066 return;
10067
10068 ImDrawList* overlay_draw_list = &GImGui->OverlayDrawList; // Render additional visuals into the top-most draw list
10069 overlay_draw_list->PushClipRectFullScreen();
10070 int elem_offset = 0;
10071 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) {
10072 if (pcmd->UserCallback) {
10073 ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
10074 continue;
10075 }
10076 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
10077 bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %-4d %s vtx, tex = %p, clip_rect = (%.0f,%.0f)..(%.0f,%.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);
10078 if (show_clip_rects && ImGui::IsItemHovered()) {
10079 ImRect clip_rect = pcmd->ClipRect;
10080 ImRect vtxs_rect;
10081 for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++)
10082 vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos);
10083 clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255, 255, 0, 255));
10084 vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255, 0, 255, 255));
10085 }
10086 if (!pcmd_node_open)
10087 continue;
10088 ImGuiListClipper clipper(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
10089 while (clipper.Step())
10090 for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++) {
10091 char buf[300], * buf_p = buf;
10092 ImVec2 triangles_pos[3];
10093 for (int n = 0; n < 3; n++, vtx_i++) {
10094 ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i];
10095 triangles_pos[n] = v.pos;
10096 buf_p += sprintf(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);
10097 }
10098 ImGui::Selectable(buf, false);
10099 if (ImGui::IsItemHovered())
10100 overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255, 255, 0, 255), true, 1.0f, false); // Add triangle without AA, more readable for large-thin triangle
10101 }
10102 ImGui::TreePop();
10103 }
10104 overlay_draw_list->PopClipRect();
10105 ImGui::TreePop();
10106 }
10107
10108 static void NodeWindows(ImVector<ImGuiWindow*> & windows, const char* label) {
10109 if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size))
10110 return;
10111 for (int i = 0; i < windows.Size; i++)
10112 Funcs::NodeWindow(windows[i], "Window");
10113 ImGui::TreePop();
10114 }
10115
10116 static void NodeWindow(ImGuiWindow * window, const char* label) {
10117 if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window))
10118 return;
10119 NodeDrawList(window->DrawList, "DrawList");
10120 ImGui::BulletText("Pos: (%.1f,%.1f)", window->Pos.x, window->Pos.y);
10121 ImGui::BulletText("Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y);
10122 ImGui::BulletText("Scroll: (%.2f,%.2f)", window->Scroll.x, window->Scroll.y);
10123 ImGui::BulletText("Active: %d, Accessed: %d", window->Active, window->Accessed);
10124 if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
10125 if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
10126 ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair));
10127 ImGui::TreePop();
10128 }
10129 };
10130
10131 ImGuiContext& g = *GImGui; // Access private state
10132 Funcs::NodeWindows(g.Windows, "Windows");
10133 if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.RenderDrawLists[0].Size)) {
10134 for (int i = 0; i < g.RenderDrawLists[0].Size; i++)
10135 Funcs::NodeDrawList(g.RenderDrawLists[0][i], "DrawList");
10136 ImGui::TreePop();
10137 }
10138 if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) {
10139 for (int i = 0; i < g.OpenPopupStack.Size; i++) {
10140 ImGuiWindow* window = g.OpenPopupStack[i].Window;
10141 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" : "");
10142 }
10143 ImGui::TreePop();
10144 }
10145 if (ImGui::TreeNode("Basic state")) {
10146 ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
10147 ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
10148 ImGui::Text("HoveredId: 0x%08X/0x%08X", g.HoveredId, g.HoveredIdPreviousFrame); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not
10149 ImGui::Text("ActiveId: 0x%08X/0x%08X", g.ActiveId, g.ActiveIdPreviousFrame);
10150 ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
10151 ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
10152 ImGui::TreePop();
10153 }
10154 }
10155 ImGui::End();
10156}
10157
10158//-----------------------------------------------------------------------------
10159
10160// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
10161// 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.
10162#ifdef IMGUI_INCLUDE_IMGUI_USER_INL
10163#include "imgui_user.inl"
10164#endif