imgui.cc (436179B)
1 // dear imgui, v1.50 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 - API BREAKING CHANGES (read me when you update!) 20 - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS 21 - How can I help? 22 - How do I update to a newer version of ImGui? 23 - What is ImTextureID and how do I display an image? 24 - I integrated ImGui in my engine and the text or lines are blurry.. 25 - I integrated ImGui in my engine and some elements are clipping or disappearing when I move windows around.. 26 - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs. 27 - How can I tell when ImGui wants my mouse/keyboard inputs and when I can pass them to my application? 28 - How can I load a different font than the default? 29 - How can I easily use icons in my application? 30 - How can I load multiple fonts? 31 - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? 32 - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) 33 - ISSUES & TODO-LIST 34 - CODE 35 36 37 MISSION STATEMENT 38 ================= 39 40 - easy to use to create code-driven and data-driven tools 41 - easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools 42 - easy to hack and improve 43 - minimize screen real-estate usage 44 - minimize setup and maintenance 45 - minimize state storage on user side 46 - portable, minimize dependencies, run on target (consoles, phones, etc.) 47 - efficient runtime (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) 48 - read about immediate-mode gui principles @ http://mollyrocket.com/861, http://mollyrocket.com/forums/index.html 49 50 Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: 51 - doesn't look fancy, doesn't animate 52 - limited layout features, intricate layouts are typically crafted in code 53 - occasionally uses statically sized buffers for string manipulations - won't crash, but some very long pieces of text may be clipped. functions like ImGui::TextUnformatted() don't have such restriction. 54 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 78 79 PROGRAMMER GUIDE 80 ================ 81 82 - read the FAQ below this section! 83 - 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. 84 - call and read ImGui::ShowTestWindow() for demo code demonstrating most features. 85 - see examples/ folder for standalone sample applications. Prefer reading examples/opengl2_example/ first as it is the simplest. 86 you may be able to grab and copy a ready made imgui_impl_*** file from the examples/. 87 - customization: PushStyleColor()/PushStyleVar() or the style editor to tweak the look of the interface (e.g. if you want a more compact UI or a different color scheme). 88 89 - getting started: 90 - init: call ImGui::GetIO() to retrieve the ImGuiIO structure and fill the fields marked 'Settings'. 91 - init: call io.Fonts->GetTexDataAsRGBA32(...) and load the font texture pixels into graphics memory. 92 - every frame: 93 1/ in your mainloop or right after you got your keyboard/mouse info, call ImGui::GetIO() and fill the fields marked 'Input' 94 2/ call ImGui::NewFrame() as early as you can! 95 3/ use any ImGui function you want between NewFrame() and Render() 96 4/ call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your RenderDrawListFn handler that you set in the IO structure. 97 (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.) 98 - all rendering information are stored into command-lists until ImGui::Render() is called. 99 - ImGui never touches or know about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. 100 - 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. 101 - refer to the examples applications in the examples/ folder for instruction on how to setup your code. 102 - a typical application skeleton may be: 103 104 // Application init 105 ImGuiIO& io = ImGui::GetIO(); 106 io.DisplaySize.x = 1920.0f; 107 io.DisplaySize.y = 1280.0f; 108 io.IniFilename = "imgui.ini"; 109 io.RenderDrawListsFn = my_render_function; // Setup a render function, or set to NULL and call GetDrawData() after Render() to access the render data. 110 // TODO: Fill others settings of the io structure 111 112 // Load texture atlas 113 // There is a default font so you don't need to care about choosing a font yet 114 unsigned char* pixels; 115 int width, height; 116 io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height); 117 // TODO: At this points you've got a texture pointed to by 'pixels' and you need to upload that your your graphic system 118 // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID' 119 120 // Application main loop 121 while (true) 122 { 123 // 1) get low-level inputs (e.g. on Win32, GetKeyboardState(), or poll your events, etc.) 124 // TODO: fill all fields of IO structure and call NewFrame 125 ImGuiIO& io = ImGui::GetIO(); 126 io.DeltaTime = 1.0f/60.0f; 127 io.MousePos = mouse_pos; 128 io.MouseDown[0] = mouse_button_0; 129 io.MouseDown[1] = mouse_button_1; 130 io.KeysDown[i] = ... 131 132 // 2) call NewFrame(), after this point you can use ImGui::* functions anytime 133 ImGui::NewFrame(); 134 135 // 3) most of your application code here 136 MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); 137 MyGameRender(); // may use any ImGui functions 138 139 // 4) render & swap video buffers 140 ImGui::Render(); 141 SwapBuffers(); 142 } 143 144 - You can read back 'io.WantCaptureMouse', 'io.WantCaptureKeybord' etc. flags from the IO structure to tell how ImGui intends to use your 145 inputs and to know if you should share them or hide them from the rest of your application. Read the FAQ below for more information. 146 147 148 API BREAKING CHANGES 149 ==================== 150 151 Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. 152 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. 153 Also read releases logs https://github.com/ocornut/imgui/releases for more details. 154 155 - 2017/05/26 (1.50) - Removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. 156 - 2017/05/01 (1.50) - Renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. 157 - 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(). 158 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. 159 - 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. 160 - 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. 161 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. 162 If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. 163 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. 164 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. 165 ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) 166 { 167 float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; 168 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); 169 } 170 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. 171 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). 172 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. 173 - 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). 174 - 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. 175 - 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). 176 - 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) 177 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). 178 - 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. 179 - 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. 180 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. 181 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. 182 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. 183 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. 184 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! 185 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize 186 - 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. 187 - 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 188 - 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. 189 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. 190 - 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. 191 this necessary change will break your rendering function! the fix should be very easy. sorry for that :( 192 - 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. 193 - the signature of the io.RenderDrawListsFn handler has changed! 194 ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) 195 became: 196 ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). 197 argument 'cmd_lists' -> 'draw_data->CmdLists' 198 argument 'cmd_lists_count' -> 'draw_data->CmdListsCount' 199 ImDrawList 'commands' -> 'CmdBuffer' 200 ImDrawList 'vtx_buffer' -> 'VtxBuffer' 201 ImDrawList n/a -> 'IdxBuffer' (new) 202 ImDrawCmd 'vtx_count' -> 'ElemCount' 203 ImDrawCmd 'clip_rect' -> 'ClipRect' 204 ImDrawCmd 'user_callback' -> 'UserCallback' 205 ImDrawCmd 'texture_id' -> 'TextureId' 206 - 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. 207 - 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! 208 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! 209 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. 210 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). 211 - 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. 212 - 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 213 - 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! 214 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). 215 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). 216 - 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. 217 - 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. 218 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). 219 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. 220 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API 221 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. 222 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. 223 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. 224 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing 225 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. 226 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) 227 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. 228 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. 229 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. 230 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior 231 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() 232 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) 233 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. 234 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. 235 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. 236 this sequence: 237 const void* png_data; 238 unsigned int png_size; 239 ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); 240 // <Copy to GPU> 241 became: 242 unsigned char* pixels; 243 int width, height; 244 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); 245 // <Copy to GPU> 246 io.Fonts->TexID = (your_texture_identifier); 247 you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. 248 it is now recommended that you sample the font texture with bilinear interpolation. 249 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. 250 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) 251 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets 252 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) 253 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) 254 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility 255 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() 256 - 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) 257 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) 258 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() 259 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn 260 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) 261 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite 262 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes 263 264 265 FREQUENTLY ASKED QUESTIONS (FAQ), TIPS 266 ====================================== 267 268 Q: How can I help? 269 A: - If you are experienced enough with ImGui and with C/C++, look at the todo list and see how you want/can help! 270 - Become a Patron/donate. Convince your company to become a Patron or provide serious funding for development time. 271 272 Q: How do I update to a newer version of ImGui? 273 A: Overwrite the following files: 274 imgui.cpp 275 imgui.h 276 imgui_demo.cpp 277 imgui_draw.cpp 278 imgui_internal.h 279 stb_rect_pack.h 280 stb_textedit.h 281 stb_truetype.h 282 Don't overwrite imconfig.h if you have made modification to your copy. 283 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. 284 Check the "API BREAKING CHANGES" sections for a list of occasional API breaking changes. 285 Please report any issue to the GitHub page! 286 287 Q: What is ImTextureID and how do I display an image? 288 A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function. 289 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! 290 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. 291 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. 292 Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing. 293 (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!) 294 To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions. 295 ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. 296 It is your responsibility to get textures uploaded to your GPU. 297 298 Q: I integrated ImGui in my engine and the text or lines are blurry.. 299 A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). 300 Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. 301 302 Q: I integrated ImGui in my engine and some elements are clipping or disappearing when I move windows around.. 303 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). 304 305 Q: Can I have multiple widgets with the same label? Can I have widget without a label? (Yes) 306 A: Yes. A primer on the use of labels/IDs in ImGui.. 307 308 - Elements that are not clickable, such as Text() items don't need an ID. 309 310 - Interactive widgets require state to be carried over multiple frames (most typically ImGui often needs to remember what is the "active" widget). 311 to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. 312 313 Button("OK"); // Label = "OK", ID = hash of "OK" 314 Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" 315 316 - 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 317 or in two different locations of a tree. 318 319 - If you have a same ID twice in the same location, you'll have a conflict: 320 321 Button("OK"); 322 Button("OK"); // ID collision! Both buttons will be treated as the same. 323 324 Fear not! this is easy to solve and there are many ways to solve it! 325 326 - When passing a label you can optionally specify extra unique ID information within string itself. This helps solving the simpler collision cases. 327 use "##" to pass a complement to the ID that won't be visible to the end-user: 328 329 Button("Play"); // Label = "Play", ID = hash of "Play" 330 Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above) 331 Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above) 332 333 - If you want to completely hide the label, but still need an ID: 334 335 Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!) 336 337 - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels. 338 For example you may want to include varying information in a window title bar (and windows are uniquely identified by their ID.. obviously) 339 Use "###" to pass a label that isn't part of ID: 340 341 Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" 342 Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) 343 344 sprintf(buf, "My game (%f FPS)###MyGame"); 345 Begin(buf); // Variable label, ID = hash of "MyGame" 346 347 - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window. 348 This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements. 349 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! 350 351 for (int i = 0; i < 100; i++) 352 { 353 PushID(i); 354 Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique) 355 PopID(); 356 } 357 358 for (int i = 0; i < 100; i++) 359 { 360 MyObject* obj = Objects[i]; 361 PushID(obj); 362 Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique) 363 PopID(); 364 } 365 366 for (int i = 0; i < 100; i++) 367 { 368 MyObject* obj = Objects[i]; 369 PushID(obj->Name); 370 Button("Click"); // Label = "Click", ID = hash of string + "label" (unique) 371 PopID(); 372 } 373 374 - More example showing that you can stack multiple prefixes into the ID stack: 375 376 Button("Click"); // Label = "Click", ID = hash of "Click" 377 PushID("node"); 378 Button("Click"); // Label = "Click", ID = hash of "node" + "Click" 379 PushID(my_ptr); 380 Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click" 381 PopID(); 382 PopID(); 383 384 - Tree nodes implicitly creates a scope for you by calling PushID(). 385 386 Button("Click"); // Label = "Click", ID = hash of "Click" 387 if (TreeNode("node")) 388 { 389 Button("Click"); // Label = "Click", ID = hash of "node" + "Click" 390 TreePop(); 391 } 392 393 - When working with trees, ID are used to preserve the open/close state of each tree node. 394 Depending on your use cases you may want to use strings, indices or pointers as ID. 395 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. 396 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! 397 398 Q: How can I tell when ImGui wants my mouse/keyboard inputs and when I can pass them to my application? 399 A: You can read the 'io.WantCaptureXXX' flags in the ImGuiIO structure. Preferably read them after calling ImGui::NewFrame() to avoid those flags lagging by one frame, but either should be fine. 400 When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application. 401 When 'io.WantInputsCharacters' is set to may want to notify your OS to popup an on-screen keyboard, if available. 402 ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, so 'io.WantCaptureMouse' is a more accurate and complete than testing for ImGui::IsMouseHoveringAnyWindow(). 403 (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'. 404 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.) 405 406 Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13) 407 A: Use the font atlas to load the TTF file you want: 408 409 ImGuiIO& io = ImGui::GetIO(); 410 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); 411 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() 412 413 Q: How can I easily use icons in my application? 414 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. 415 Read 'How can I load multiple fonts?' and the file 'extra_fonts/README.txt' for instructions. 416 417 Q: How can I load multiple fonts? 418 A: Use the font atlas to pack them into a single texture: 419 (Read extra_fonts/README.txt and the code in ImFontAtlas for more details.) 420 421 ImGuiIO& io = ImGui::GetIO(); 422 ImFont* font0 = io.Fonts->AddFontDefault(); 423 ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); 424 ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); 425 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() 426 // the first loaded font gets used by default 427 // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime 428 429 // Options 430 ImFontConfig config; 431 config.OversampleH = 3; 432 config.OversampleV = 1; 433 config.GlyphExtraSpacing.x = 1.0f; 434 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); 435 436 // Combine multiple fonts into one (e.g. for icon fonts) 437 ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; 438 ImFontConfig config; 439 config.MergeMode = true; 440 io.Fonts->AddFontDefault(); 441 io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font 442 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs 443 444 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? 445 A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. 446 All your strings needs to use UTF-8 encoding. 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. 447 In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax. Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. 448 You can also try to remap your local codepage characters to their Unicode codepoint using font->AddRemapChar(), but international users may have problems reading/editing your source code. 449 450 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); // Load Japanese characters 451 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() 452 io.ImeWindowHandle = MY_HWND; // To input using Microsoft IME, give ImGui the hwnd of your application 453 454 As for text input, depends on you passing the right character code to io.AddInputCharacter(). The example applications do that. 455 456 Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) 457 A: The easiest way is to create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, zero background alpha, 458 then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. 459 460 - 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. 461 - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug" 462 - 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) 463 - tip: you can call Render() multiple times (e.g for VR renders). 464 - tip: call and read the ShowTestWindow() code in imgui_demo.cpp for more example of how to use ImGui! 465 466 467 ISSUES & TODO-LIST 468 ================== 469 Issue numbers (#) refer to github issues listed at https://github.com/ocornut/imgui/issues 470 The list below consist mostly of ideas noted down before they are requested/discussed by users (at which point it usually moves to the github) 471 472 - doc: add a proper documentation+regression testing system (#435) 473 - window: add a way for very transient windows (non-saved, temporary overlay over hundreds of objects) to "clean" up from the global window list. perhaps a lightweight explicit cleanup pass. 474 - window: calling SetNextWindowSize() every frame with <= 0 doesn't do anything, may be useful to allow (particularly when used for a single axis) (#690) 475 - window: auto-fit feedback loop when user relies on any dynamic layout (window width multiplier, column) appears weird to end-user. clarify. 476 - window: allow resizing of child windows (possibly given min/max for each axis?) 477 - window: background options for child windows, border option (disable rounding) 478 - window: add a way to clear an existing window instead of appending (e.g. for tooltip override using a consistent api rather than the deferred tooltip) 479 - window: resizing from any sides? + mouse cursor directives for app. 480 !- window: begin with *p_open == false should return false. 481 - window: get size/pos helpers given names (see discussion in #249) 482 - window: a collapsed window can be stuck behind the main menu bar? 483 - window: when window is small, prioritize resize button over close button. 484 - window: detect extra End() call that pop the "Debug" window out and assert at call site instead of later. 485 - window/tooltip: allow to set the width of a tooltip to allow TextWrapped() etc. while keeping the height automatic. 486 - window: increase minimum size of a window with menus or fix the menu rendering so that it doesn't look odd. 487 - draw-list: maintaining bounding box per command would allow to merge draw command when clipping isn't relied on (typical non-scrolling window or non-overflowing column would merge with previous command). 488 !- scrolling: allow immediately effective change of scroll if we haven't appended items yet 489 - splitter/separator: formalize the splitter idiom into an official api (we want to handle n-way split) (#319) 490 - widgets: display mode: widget-label, label-widget (aligned on column or using fixed size), label-newline-tab-widget etc. 491 - widgets: clean up widgets internal toward exposing everything. 492 - widgets: add disabled and read-only modes (#211) 493 - main: considering adding an Init() function? some constructs are awkward in the implementation because of the lack of them. 494 !- main: make it so that a frame with no window registered won't refocus every window on subsequent frames (~bump LastFrameActive of all windows). 495 - main: IsItemHovered() make it more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes 496 - main: IsItemHovered() info stored in a stack? so that 'if TreeNode() { Text; TreePop; } if IsHovered' return the hover state of the TreeNode? 497 - input text: clean up the mess caused by converting UTF-8 <> wchar. the code is rather inefficient right now and super fragile. 498 - input text: reorganize event handling, allow CharFilter to modify buffers, allow multiple events? (#541) 499 - input text: expose CursorPos in char filter event (#816) 500 - input text: flag to disable live update of the user buffer (also applies to float/int text input) 501 - input text: resize behavior - field could stretch when being edited? hover tooltip shows more text? 502 - input text: add ImGuiInputTextFlags_EnterToApply? (off #218) 503 - input text: add discard flag (e.g. ImGuiInputTextFlags_DiscardActiveBuffer) or make it easier to clear active focus for text replacement during edition (#725) 504 - input text multi-line: don't directly call AddText() which does an unnecessary vertex reserve for character count prior to clipping. and/or more line-based clipping to AddText(). and/or reorganize TextUnformatted/RenderText for more efficiency for large text (e.g TextUnformatted could clip and log separately, etc). 505 - input text multi-line: way to dynamically grow the buffer without forcing the user to initially allocate for worse case (follow up on #200) 506 - input text multi-line: line numbers? status bar? (follow up on #200) 507 - input text multi-line: behave better when user changes input buffer while editing is active (even though it is illegal behavior). namely, the change of buffer can create a scrollbar glitch (#725) 508 - input text: allow centering/positioning text so that ctrl+clicking Drag or Slider keeps the textual value at the same pixel position. 509 - input number: optional range min/max for Input*() functions 510 - input number: holding [-]/[+] buttons could increase the step speed non-linearly (or user-controlled) 511 - input number: use mouse wheel to step up/down 512 - input number: applying arithmetics ops (+,-,*,/) messes up with text edit undo stack. 513 - button: provide a button that looks framed. 514 - text: proper alignment options 515 - image/image button: misalignment on padded/bordered button? 516 - image/image button: parameters are confusing, image() has tint_col,border_col whereas imagebutton() has bg_col/tint_col. Even thou they are different parameters ordering could be more consistent. can we fix that? 517 - layout: horizontal layout helper (#97) 518 - layout: horizontal flow until no space left (#404) 519 - layout: more generic alignment state (left/right/centered) for single items? 520 - layout: clean up the InputFloatN/SliderFloatN/ColorEdit4 layout code. item width should include frame padding. 521 - layout: BeginGroup() needs a border option. 522 - columns: declare column set (each column: fixed size, %, fill, distribute default size among fills) (#513, #125) 523 - columns: add a conditional parameter to SetColumnOffset() (#513, #125) 524 - columns: separator function or parameter that works within the column (currently Separator() bypass all columns) (#125) 525 - columns: columns header to act as button (~sort op) and allow resize/reorder (#513, #125) 526 - columns: user specify columns size (#513, #125) 527 - columns: flag to add horizontal separator above/below? 528 - columns/layout: setup minimum line height (equivalent of automatically calling AlignFirstTextHeightToWidgets) 529 - combo: sparse combo boxes (via function call?) / iterators 530 - combo: contents should extends to fit label if combo widget is small 531 - combo/listbox: keyboard control. need InputText-like non-active focus + key handling. considering keyboard for custom listbox (pr #203) 532 - listbox: multiple selection 533 - listbox: user may want to initial scroll to focus on the one selected value? 534 - listbox: keyboard navigation. 535 - listbox: scrolling should track modified selection. 536 !- popups/menus: clarify usage of popups id, how MenuItem/Selectable closing parent popups affects the ID, etc. this is quite fishy needs improvement! (#331, #402) 537 - popups: add variant using global identifier similar to Begin/End (#402) 538 - popups: border options. richer api like BeginChild() perhaps? (#197) 539 - tooltip: tooltip that doesn't fit in entire screen seems to lose their "last preferred button" and may teleport when moving mouse 540 - menus: local shortcuts, global shortcuts (#456, #126) 541 - menus: icons 542 - menus: menubars: some sort of priority / effect of main menu-bar on desktop size? 543 - menus: calling BeginMenu() twice with a same name doesn't seem to append nicely 544 - statusbar: add a per-window status bar helper similar to what menubar does. 545 - tabs (#261, #351) 546 - separator: separator on the initial position of a window is not visible (cursorpos.y <= clippos.y) 547 !- color: the color helpers/typing is a mess and needs sorting out. 548 - color: add a better color picker (#346) 549 - node/graph editor (#306) 550 - pie menus patterns (#434) 551 - drag'n drop, dragging helpers (carry dragging info, visualize drag source before clicking, drop target, etc.) (#143, #479) 552 - plot: PlotLines() should use the polygon-stroke facilities (currently issues with averaging normals) 553 - plot: make it easier for user to draw extra stuff into the graph (e.g: draw basis, highlight certain points, 2d plots, multiple plots) 554 - plot: "smooth" automatic scale over time, user give an input 0.0(full user scale) 1.0(full derived from value) 555 - plot: add a helper e.g. Plot(char* label, float value, float time_span=2.0f) that stores values and Plot them for you - probably another function name. and/or automatically allow to plot ANY displayed value (more reliance on stable ID) 556 - slider: allow using the [-]/[+] buttons used by InputFloat()/InputInt() 557 - slider: initial absolute click is imprecise. change to relative movement slider (same as scrollbar). 558 - slider: add dragging-based widgets to edit values with mouse (on 2 axises), saving screen real-estate. 559 - slider: tint background based on value (e.g. v_min -> v_max, or use 0.0f either side of the sign) 560 - slider & drag: int data passing through a float 561 - drag float: up/down axis 562 - drag float: added leeway on edge (e.g. a few invisible steps past the clamp limits) 563 - tree node / optimization: avoid formatting when clipped. 564 - tree node: tree-node/header right-most side doesn't take account of horizontal scrolling. 565 - tree node: add treenode/treepush int variants? not there because (void*) cast from int warns on some platforms/settings? 566 - tree node: try to apply scrolling at time of TreePop() if node was just opened and end of node is past scrolling limits? 567 - tree node / selectable render mismatch which is visible if you use them both next to each other (e.g. cf. property viewer) 568 - tree node: tweak color scheme to distinguish headers from selected tree node (#581) 569 - textwrapped: figure out better way to use TextWrapped() in an always auto-resize context (tooltip, etc.) (#249) 570 - settings: write more decent code to allow saving/loading new fields 571 - settings: api for per-tool simple persistent data (bool,int,float,columns sizes,etc.) in .ini file 572 - style: add window shadows. 573 - style/optimization: store rounded corners in texture to use 1 quad per corner (filled and wireframe) to lower the cost of rounding. 574 - style: color-box not always square? 575 - style: a concept of "compact style" that the end-user can easily rely on (e.g. PushStyleCompact()?) that maps to other settings? avoid implementing duplicate helpers such as SmallCheckbox(), etc. 576 - style: try to make PushStyleVar() more robust to incorrect parameters (to be more friendly to edit & continues situation). 577 - style: global scale setting. 578 - style: WindowPadding needs to be EVEN needs the 0.5 multiplier probably have a subtle effect on clip rectangle 579 - text: simple markup language for color change? 580 - font: dynamic font atlas to avoid baking huge ranges into bitmap and make scaling easier. 581 - font: small opt: for monospace font (like the defalt one) we can trim IndexXAdvance as long as trailing value is == FallbackXAdvance 582 - font: add support for kerning, probably optional. perhaps default to (32..128)^2 matrix ~ 36KB then hash fallback. 583 - font: add a simpler CalcTextSizeA() api? current one ok but not welcome if user needs to call it directly (without going through ImGui::CalcTextSize) 584 - font: fix AddRemapChar() to work before font has been built. 585 - log: LogButtons() options for specifying depth and/or hiding depth slider 586 - log: have more control over the log scope (e.g. stop logging when leaving current tree node scope) 587 - log: be able to log anything (e.g. right-click on a window/tree-node, shows context menu? log into tty/file/clipboard) 588 - log: let user copy any window content to clipboard easily (CTRL+C on windows? while moving it? context menu?). code is commented because it fails with multiple Begin/End pairs. 589 - filters: set a current filter that tree node can automatically query to hide themselves 590 - filters: handle wildcards (with implicit leading/trailing *), regexps 591 - shortcuts: add a shortcut api, e.g. parse "&Save" and/or "Save (CTRL+S)", pass in to widgets or provide simple ways to use (button=activate, input=focus) 592 !- keyboard: tooltip & combo boxes are messing up / not honoring keyboard tabbing 593 - keyboard: full keyboard navigation and focus. (#323) 594 - focus: preserve ActiveId/focus stack state, e.g. when opening a menu and close it, previously selected InputText() focus gets restored (#622) 595 - focus: SetKeyboardFocusHere() on with >= 0 offset could be done on same frame (else latch and modulate on beginning of next frame) 596 - input: rework IO system to be able to pass actual ordered/timestamped events. (~#335, #71) 597 - input: allow to decide and pass explicit double-clicks (e.g. for windows by the CS_DBLCLKS style). 598 - input: support track pad style scrolling & slider edit. 599 - misc: provide a way to compile out the entire implementation while providing a dummy API (e.g. #define IMGUI_DUMMY_IMPL) 600 - misc: double-clicking on title bar to minimize isn't consistent, perhaps move to single-click on left-most collapse icon? 601 - misc: provide HoveredTime and ActivatedTime to ease the creation of animations. 602 - style editor: have a more global HSV setter (e.g. alter hue on all elements). consider replacing active/hovered by offset in HSV space? (#438) 603 - style editor: color child window height expressed in multiple of line height. 604 - remote: make a system like RemoteImGui first-class citizen/project (#75) 605 - drawlist: move Font, FontSize, FontTexUvWhitePixel inside ImDrawList and make it self-contained (apart from drawing settings?) 606 - drawlist: end-user probably can't call Clear() directly because we expect a texture to be pushed in the stack. 607 - examples: directx9: save/restore device state more thoroughly. 608 - examples: window minimize, maximize (#583) 609 - optimization: add a flag to disable most of rendering, for the case where the user expect to skip it (#335) 610 - optimization: use another hash function than crc32, e.g. FNV1a 611 - optimization/render: merge command-lists with same clip-rect into one even if they aren't sequential? (as long as in-between clip rectangle don't overlap)? 612 - optimization: turn some the various stack vectors into statically-sized arrays 613 - optimization: better clipping for multi-component widgets 614 */ 615 616 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 617 #define _CRT_SECURE_NO_WARNINGS 618 #endif 619 620 #include "imgui.h" 621 #define IMGUI_DEFINE_MATH_OPERATORS 622 #define IMGUI_DEFINE_PLACEMENT_NEW 623 #include "imgui_internal.h" 624 625 #include <ctype.h> // toupper, isprint 626 #include <stdlib.h> // NULL, malloc, free, qsort, atoi 627 #include <stdio.h> // vsnprintf, sscanf, printf 628 #include <limits.h> // INT_MIN, INT_MAX 629 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier 630 #include <stddef.h> // intptr_t 631 #else 632 #include <stdint.h> // intptr_t 633 #endif 634 635 #ifdef _MSC_VER 636 #pragma warning (disable: 4127) // condition expression is constant 637 #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) 638 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen 639 #endif 640 641 // Clang warnings with -Weverything 642 #ifdef __clang__ 643 #pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. 644 #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. 645 #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. 646 #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. 647 #pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. 648 #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // 649 #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. 650 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' // 651 #elif defined(__GNUC__) 652 #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used 653 #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size 654 #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' 655 #pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function 656 #pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value 657 #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers 658 #endif 659 660 //------------------------------------------------------------------------- 661 // Forward Declarations 662 //------------------------------------------------------------------------- 663 664 static void LogRenderedText(const ImVec2& ref_pos, const char* text, const char* text_end = NULL); 665 666 static void PushMultiItemsWidths(int components, float w_full = 0.0f); 667 static float GetDraggedColumnOffset(int column_index); 668 669 static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); 670 671 static ImFont* GetDefaultFont(); 672 static void SetCurrentFont(ImFont* font); 673 static void SetCurrentWindow(ImGuiWindow* window); 674 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); 675 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiSetCond cond); 676 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiSetCond cond); 677 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiSetCond cond); 678 static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs); 679 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); 680 static inline bool IsWindowContentHoverable(ImGuiWindow* window); 681 static void ClearSetNextWindowData(); 682 static void CheckStacksSize(ImGuiWindow* window, bool write); 683 static void Scrollbar(ImGuiWindow* window, bool horizontal); 684 685 static void AddDrawListToRenderList(ImVector<ImDrawList*>& out_render_list, ImDrawList* draw_list); 686 static void AddWindowToRenderList(ImVector<ImDrawList*>& out_render_list, ImGuiWindow* window); 687 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>& out_sorted_windows, ImGuiWindow* window); 688 689 static ImGuiIniData* FindWindowSettings(const char* name); 690 static ImGuiIniData* AddWindowSettings(const char* name); 691 static void LoadIniSettingsFromDisk(const char* ini_filename); 692 static void SaveIniSettingsToDisk(const char* ini_filename); 693 static void MarkIniSettingsDirty(); 694 695 static void PushColumnClipRect(int column_index = -1); 696 static ImRect GetVisibleRect(); 697 698 static bool BeginPopupEx(const char* str_id, ImGuiWindowFlags extra_flags); 699 static void CloseInactivePopups(); 700 static void ClosePopupToLevel(int remaining); 701 static void ClosePopup(ImGuiID id); 702 static bool IsPopupOpen(ImGuiID id); 703 static ImGuiWindow* GetFrontMostModalRootWindow(); 704 static ImVec2 FindBestPopupWindowPos(const ImVec2& base_pos, const ImVec2& size, int* last_dir, const ImRect& rect_to_avoid); 705 706 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data); 707 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); 708 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); 709 710 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size); 711 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size); 712 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2); 713 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format); 714 715 //----------------------------------------------------------------------------- 716 // Platform dependent default implementations 717 //----------------------------------------------------------------------------- 718 719 static const char* GetClipboardTextFn_DefaultImpl(void* user_data); 720 static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); 721 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); 722 723 //----------------------------------------------------------------------------- 724 // Context 725 //----------------------------------------------------------------------------- 726 727 // Default font atlas storage . 728 // New contexts always point by default to this font atlas. It can be changed by reassigning the GetIO().Fonts variable. 729 static ImFontAtlas GImDefaultFontAtlas; 730 731 // Default context storage + current context pointer. 732 // Implicitely used by all ImGui functions. Always assumed to be != NULL. Change to a different context by calling ImGui::SetCurrentContext() 733 // 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: 734 // - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) 735 // - 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 736 #ifndef GImGui 737 static ImGuiContext GImDefaultContext; 738 ImGuiContext* GImGui = &GImDefaultContext; 739 #endif 740 741 //----------------------------------------------------------------------------- 742 // User facing structures 743 //----------------------------------------------------------------------------- 744 745 ImGuiStyle::ImGuiStyle() 746 { 747 Alpha = 1.0f; // Global alpha applies to everything in ImGui 748 WindowPadding = ImVec2(8,8); // Padding within a window 749 WindowMinSize = ImVec2(32,32); // Minimum window size 750 WindowRounding = 9.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows 751 WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text 752 ChildWindowRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows 753 FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) 754 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). 755 ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines 756 ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) 757 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! 758 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). 759 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns 760 ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar 761 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar 762 GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar 763 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. 764 ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. 765 DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. 766 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. 767 AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. 768 AntiAliasedShapes = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) 769 CurveTessellationTol = 1.25f; // Tessellation tolerance. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. 770 771 Colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); 772 Colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); 773 Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); 774 Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); 775 Colors[ImGuiCol_PopupBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f); 776 Colors[ImGuiCol_Border] = ImVec4(0.70f, 0.70f, 0.70f, 0.65f); 777 Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); 778 Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.30f); // Background of checkbox, radio button, plot, slider, text input 779 Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.90f, 0.80f, 0.80f, 0.40f); 780 Colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f); 781 Colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); 782 Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); 783 Colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); 784 Colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); 785 Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); 786 Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); 787 Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); 788 Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f); 789 Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); 790 Colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); 791 Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); 792 Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); 793 Colors[ImGuiCol_Button] = ImVec4(0.67f, 0.40f, 0.40f, 0.60f); 794 Colors[ImGuiCol_ButtonHovered] = ImVec4(0.67f, 0.40f, 0.40f, 1.00f); 795 Colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); 796 Colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); 797 Colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); 798 Colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); 799 Colors[ImGuiCol_Column] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); 800 Colors[ImGuiCol_ColumnHovered] = ImVec4(0.70f, 0.60f, 0.60f, 1.00f); 801 Colors[ImGuiCol_ColumnActive] = ImVec4(0.90f, 0.70f, 0.70f, 1.00f); 802 Colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); 803 Colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f); 804 Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f); 805 Colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); 806 Colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); 807 Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); 808 Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); 809 Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); 810 Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); 811 Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); 812 Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); 813 Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); 814 } 815 816 ImGuiIO::ImGuiIO() 817 { 818 // Most fields are initialized with zero 819 memset(this, 0, sizeof(*this)); 820 821 DisplaySize = ImVec2(-1.0f, -1.0f); 822 DeltaTime = 1.0f/60.0f; 823 IniSavingRate = 5.0f; 824 IniFilename = "imgui.ini"; 825 LogFilename = "imgui_log.txt"; 826 Fonts = &GImDefaultFontAtlas; 827 FontGlobalScale = 1.0f; 828 FontDefault = NULL; 829 DisplayFramebufferScale = ImVec2(1.0f, 1.0f); 830 MousePos = ImVec2(-1,-1); 831 MousePosPrev = ImVec2(-1,-1); 832 MouseDoubleClickTime = 0.30f; 833 MouseDoubleClickMaxDist = 6.0f; 834 MouseDragThreshold = 6.0f; 835 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) 836 MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; 837 for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) 838 KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; 839 for (int i = 0; i < ImGuiKey_COUNT; i++) 840 KeyMap[i] = -1; 841 KeyRepeatDelay = 0.250f; 842 KeyRepeatRate = 0.050f; 843 UserData = NULL; 844 845 // User functions 846 RenderDrawListsFn = NULL; 847 MemAllocFn = malloc; 848 MemFreeFn = free; 849 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations 850 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; 851 ClipboardUserData = NULL; 852 ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; 853 854 // Set OS X style defaults based on __APPLE__ compile time flag 855 #ifdef __APPLE__ 856 OSXBehaviors = true; 857 #endif 858 } 859 860 // Pass in translated ASCII characters for text input. 861 // - with glfw you can get those from the callback set in glfwSetCharCallback() 862 // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message 863 void ImGuiIO::AddInputCharacter(ImWchar c) 864 { 865 const int n = ImStrlenW(InputCharacters); 866 if (n + 1 < IM_ARRAYSIZE(InputCharacters)) 867 { 868 InputCharacters[n] = c; 869 InputCharacters[n+1] = '\0'; 870 } 871 } 872 873 void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) 874 { 875 // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more 876 const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); 877 ImWchar wchars[wchars_buf_len]; 878 ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); 879 for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) 880 AddInputCharacter(wchars[i]); 881 } 882 883 //----------------------------------------------------------------------------- 884 // HELPERS 885 //----------------------------------------------------------------------------- 886 887 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose 888 #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 889 890 // Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n. 891 #ifdef _WIN32 892 #define IM_NEWLINE "\r\n" 893 #else 894 #define IM_NEWLINE "\n" 895 #endif 896 897 bool ImIsPointInTriangle(const ImVec2& p, const ImVec2& a, const ImVec2& b, const ImVec2& c) 898 { 899 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; 900 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; 901 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; 902 return ((b1 == b2) && (b2 == b3)); 903 } 904 905 int ImStricmp(const char* str1, const char* str2) 906 { 907 int d; 908 while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } 909 return d; 910 } 911 912 int ImStrnicmp(const char* str1, const char* str2, int count) 913 { 914 int d = 0; 915 while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } 916 return d; 917 } 918 919 void ImStrncpy(char* dst, const char* src, int count) 920 { 921 if (count < 1) return; 922 strncpy(dst, src, (size_t)count); 923 dst[count-1] = 0; 924 } 925 926 char* ImStrdup(const char *str) 927 { 928 size_t len = strlen(str) + 1; 929 void* buff = ImGui::MemAlloc(len); 930 return (char*)memcpy(buff, (const void*)str, len); 931 } 932 933 int ImStrlenW(const ImWchar* str) 934 { 935 int n = 0; 936 while (*str++) n++; 937 return n; 938 } 939 940 const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line 941 { 942 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') 943 buf_mid_line--; 944 return buf_mid_line; 945 } 946 947 const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) 948 { 949 if (!needle_end) 950 needle_end = needle + strlen(needle); 951 952 const char un0 = (char)toupper(*needle); 953 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) 954 { 955 if (toupper(*haystack) == un0) 956 { 957 const char* b = needle + 1; 958 for (const char* a = haystack + 1; b < needle_end; a++, b++) 959 if (toupper(*a) != toupper(*b)) 960 break; 961 if (b == needle_end) 962 return haystack; 963 } 964 haystack++; 965 } 966 return NULL; 967 } 968 969 970 // MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). 971 // 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. 972 int ImFormatString(char* buf, int buf_size, const char* fmt, ...) 973 { 974 IM_ASSERT(buf_size > 0); 975 va_list args; 976 va_start(args, fmt); 977 int w = vsnprintf(buf, buf_size, fmt, args); 978 va_end(args); 979 if (w == -1 || w >= buf_size) 980 w = buf_size - 1; 981 buf[w] = 0; 982 return w; 983 } 984 985 int ImFormatStringV(char* buf, int buf_size, const char* fmt, va_list args) 986 { 987 IM_ASSERT(buf_size > 0); 988 int w = vsnprintf(buf, buf_size, fmt, args); 989 if (w == -1 || w >= buf_size) 990 w = buf_size - 1; 991 buf[w] = 0; 992 return w; 993 } 994 995 // Pass data_size==0 for zero-terminated strings 996 // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. 997 ImU32 ImHash(const void* data, int data_size, ImU32 seed) 998 { 999 static ImU32 crc32_lut[256] = { 0 }; 1000 if (!crc32_lut[1]) 1001 { 1002 const ImU32 polynomial = 0xEDB88320; 1003 for (ImU32 i = 0; i < 256; i++) 1004 { 1005 ImU32 crc = i; 1006 for (ImU32 j = 0; j < 8; j++) 1007 crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); 1008 crc32_lut[i] = crc; 1009 } 1010 } 1011 1012 seed = ~seed; 1013 ImU32 crc = seed; 1014 const unsigned char* current = (const unsigned char*)data; 1015 1016 if (data_size > 0) 1017 { 1018 // Known size 1019 while (data_size--) 1020 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; 1021 } 1022 else 1023 { 1024 // Zero-terminated string 1025 while (unsigned char c = *current++) 1026 { 1027 // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. 1028 // Because this syntax is rarely used we are optimizing for the common case. 1029 // - If we reach ### in the string we discard the hash so far and reset to the seed. 1030 // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. 1031 if (c == '#' && current[0] == '#' && current[1] == '#') 1032 crc = seed; 1033 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; 1034 } 1035 } 1036 return ~crc; 1037 } 1038 1039 //----------------------------------------------------------------------------- 1040 // ImText* helpers 1041 //----------------------------------------------------------------------------- 1042 1043 // Convert UTF-8 to 32-bits character, process single character input. 1044 // Based on stb_from_utf8() from github.com/nothings/stb/ 1045 // We handle UTF-8 decoding error by skipping forward. 1046 int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) 1047 { 1048 unsigned int c = (unsigned int)-1; 1049 const unsigned char* str = (const unsigned char*)in_text; 1050 if (!(*str & 0x80)) 1051 { 1052 c = (unsigned int)(*str++); 1053 *out_char = c; 1054 return 1; 1055 } 1056 if ((*str & 0xe0) == 0xc0) 1057 { 1058 *out_char = 0xFFFD; // will be invalid but not end of string 1059 if (in_text_end && in_text_end - (const char*)str < 2) return 1; 1060 if (*str < 0xc2) return 2; 1061 c = (unsigned int)((*str++ & 0x1f) << 6); 1062 if ((*str & 0xc0) != 0x80) return 2; 1063 c += (*str++ & 0x3f); 1064 *out_char = c; 1065 return 2; 1066 } 1067 if ((*str & 0xf0) == 0xe0) 1068 { 1069 *out_char = 0xFFFD; // will be invalid but not end of string 1070 if (in_text_end && in_text_end - (const char*)str < 3) return 1; 1071 if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; 1072 if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below 1073 c = (unsigned int)((*str++ & 0x0f) << 12); 1074 if ((*str & 0xc0) != 0x80) return 3; 1075 c += (unsigned int)((*str++ & 0x3f) << 6); 1076 if ((*str & 0xc0) != 0x80) return 3; 1077 c += (*str++ & 0x3f); 1078 *out_char = c; 1079 return 3; 1080 } 1081 if ((*str & 0xf8) == 0xf0) 1082 { 1083 *out_char = 0xFFFD; // will be invalid but not end of string 1084 if (in_text_end && in_text_end - (const char*)str < 4) return 1; 1085 if (*str > 0xf4) return 4; 1086 if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; 1087 if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below 1088 c = (unsigned int)((*str++ & 0x07) << 18); 1089 if ((*str & 0xc0) != 0x80) return 4; 1090 c += (unsigned int)((*str++ & 0x3f) << 12); 1091 if ((*str & 0xc0) != 0x80) return 4; 1092 c += (unsigned int)((*str++ & 0x3f) << 6); 1093 if ((*str & 0xc0) != 0x80) return 4; 1094 c += (*str++ & 0x3f); 1095 // utf-8 encodings of values used in surrogate pairs are invalid 1096 if ((c & 0xFFFFF800) == 0xD800) return 4; 1097 *out_char = c; 1098 return 4; 1099 } 1100 *out_char = 0; 1101 return 0; 1102 } 1103 1104 int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) 1105 { 1106 ImWchar* buf_out = buf; 1107 ImWchar* buf_end = buf + buf_size; 1108 while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) 1109 { 1110 unsigned int c; 1111 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); 1112 if (c == 0) 1113 break; 1114 if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes 1115 *buf_out++ = (ImWchar)c; 1116 } 1117 *buf_out = 0; 1118 if (in_text_remaining) 1119 *in_text_remaining = in_text; 1120 return (int)(buf_out - buf); 1121 } 1122 1123 int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) 1124 { 1125 int char_count = 0; 1126 while ((!in_text_end || in_text < in_text_end) && *in_text) 1127 { 1128 unsigned int c; 1129 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); 1130 if (c == 0) 1131 break; 1132 if (c < 0x10000) 1133 char_count++; 1134 } 1135 return char_count; 1136 } 1137 1138 // Based on stb_to_utf8() from github.com/nothings/stb/ 1139 static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) 1140 { 1141 if (c < 0x80) 1142 { 1143 buf[0] = (char)c; 1144 return 1; 1145 } 1146 if (c < 0x800) 1147 { 1148 if (buf_size < 2) return 0; 1149 buf[0] = (char)(0xc0 + (c >> 6)); 1150 buf[1] = (char)(0x80 + (c & 0x3f)); 1151 return 2; 1152 } 1153 if (c >= 0xdc00 && c < 0xe000) 1154 { 1155 return 0; 1156 } 1157 if (c >= 0xd800 && c < 0xdc00) 1158 { 1159 if (buf_size < 4) return 0; 1160 buf[0] = (char)(0xf0 + (c >> 18)); 1161 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); 1162 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); 1163 buf[3] = (char)(0x80 + ((c ) & 0x3f)); 1164 return 4; 1165 } 1166 //else if (c < 0x10000) 1167 { 1168 if (buf_size < 3) return 0; 1169 buf[0] = (char)(0xe0 + (c >> 12)); 1170 buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); 1171 buf[2] = (char)(0x80 + ((c ) & 0x3f)); 1172 return 3; 1173 } 1174 } 1175 1176 static inline int ImTextCountUtf8BytesFromChar(unsigned int c) 1177 { 1178 if (c < 0x80) return 1; 1179 if (c < 0x800) return 2; 1180 if (c >= 0xdc00 && c < 0xe000) return 0; 1181 if (c >= 0xd800 && c < 0xdc00) return 4; 1182 return 3; 1183 } 1184 1185 int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) 1186 { 1187 char* buf_out = buf; 1188 const char* buf_end = buf + buf_size; 1189 while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) 1190 { 1191 unsigned int c = (unsigned int)(*in_text++); 1192 if (c < 0x80) 1193 *buf_out++ = (char)c; 1194 else 1195 buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); 1196 } 1197 *buf_out = 0; 1198 return (int)(buf_out - buf); 1199 } 1200 1201 int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) 1202 { 1203 int bytes_count = 0; 1204 while ((!in_text_end || in_text < in_text_end) && *in_text) 1205 { 1206 unsigned int c = (unsigned int)(*in_text++); 1207 if (c < 0x80) 1208 bytes_count++; 1209 else 1210 bytes_count += ImTextCountUtf8BytesFromChar(c); 1211 } 1212 return bytes_count; 1213 } 1214 1215 ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) 1216 { 1217 float s = 1.0f/255.0f; 1218 return ImVec4( 1219 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, 1220 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, 1221 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, 1222 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); 1223 } 1224 1225 ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) 1226 { 1227 ImU32 out; 1228 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; 1229 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; 1230 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; 1231 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; 1232 return out; 1233 } 1234 1235 ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) 1236 { 1237 ImVec4 c = GImGui->Style.Colors[idx]; 1238 c.w *= GImGui->Style.Alpha * alpha_mul; 1239 return ColorConvertFloat4ToU32(c); 1240 } 1241 1242 ImU32 ImGui::GetColorU32(const ImVec4& col) 1243 { 1244 ImVec4 c = col; 1245 c.w *= GImGui->Style.Alpha; 1246 return ColorConvertFloat4ToU32(c); 1247 } 1248 1249 // Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 1250 // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv 1251 void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) 1252 { 1253 float K = 0.f; 1254 if (g < b) 1255 { 1256 const float tmp = g; g = b; b = tmp; 1257 K = -1.f; 1258 } 1259 if (r < g) 1260 { 1261 const float tmp = r; r = g; g = tmp; 1262 K = -2.f / 6.f - K; 1263 } 1264 1265 const float chroma = r - (g < b ? g : b); 1266 out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); 1267 out_s = chroma / (r + 1e-20f); 1268 out_v = r; 1269 } 1270 1271 // Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 1272 // also http://en.wikipedia.org/wiki/HSL_and_HSV 1273 void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) 1274 { 1275 if (s == 0.0f) 1276 { 1277 // gray 1278 out_r = out_g = out_b = v; 1279 return; 1280 } 1281 1282 h = fmodf(h, 1.0f) / (60.0f/360.0f); 1283 int i = (int)h; 1284 float f = h - (float)i; 1285 float p = v * (1.0f - s); 1286 float q = v * (1.0f - s * f); 1287 float t = v * (1.0f - s * (1.0f - f)); 1288 1289 switch (i) 1290 { 1291 case 0: out_r = v; out_g = t; out_b = p; break; 1292 case 1: out_r = q; out_g = v; out_b = p; break; 1293 case 2: out_r = p; out_g = v; out_b = t; break; 1294 case 3: out_r = p; out_g = q; out_b = v; break; 1295 case 4: out_r = t; out_g = p; out_b = v; break; 1296 case 5: default: out_r = v; out_g = p; out_b = q; break; 1297 } 1298 } 1299 1300 FILE* ImFileOpen(const char* filename, const char* mode) 1301 { 1302 #if defined(_WIN32) && !defined(__CYGWIN__) 1303 // 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) 1304 const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; 1305 const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; 1306 ImVector<ImWchar> buf; 1307 buf.resize(filename_wsize + mode_wsize); 1308 ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); 1309 ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); 1310 return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); 1311 #else 1312 return fopen(filename, mode); 1313 #endif 1314 } 1315 1316 // Load file content into memory 1317 // Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() 1318 void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) 1319 { 1320 IM_ASSERT(filename && file_open_mode); 1321 if (out_file_size) 1322 *out_file_size = 0; 1323 1324 FILE* f; 1325 if ((f = ImFileOpen(filename, file_open_mode)) == NULL) 1326 return NULL; 1327 1328 long file_size_signed; 1329 if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) 1330 { 1331 fclose(f); 1332 return NULL; 1333 } 1334 1335 int file_size = (int)file_size_signed; 1336 void* file_data = ImGui::MemAlloc(file_size + padding_bytes); 1337 if (file_data == NULL) 1338 { 1339 fclose(f); 1340 return NULL; 1341 } 1342 if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) 1343 { 1344 fclose(f); 1345 ImGui::MemFree(file_data); 1346 return NULL; 1347 } 1348 if (padding_bytes > 0) 1349 memset((void *)(((char*)file_data) + file_size), 0, padding_bytes); 1350 1351 fclose(f); 1352 if (out_file_size) 1353 *out_file_size = file_size; 1354 1355 return file_data; 1356 } 1357 1358 //----------------------------------------------------------------------------- 1359 // ImGuiStorage 1360 //----------------------------------------------------------------------------- 1361 1362 // Helper: Key->value storage 1363 void ImGuiStorage::Clear() 1364 { 1365 Data.clear(); 1366 } 1367 1368 // std::lower_bound but without the bullshit 1369 static ImVector<ImGuiStorage::Pair>::iterator LowerBound(ImVector<ImGuiStorage::Pair>& data, ImGuiID key) 1370 { 1371 ImVector<ImGuiStorage::Pair>::iterator first = data.begin(); 1372 ImVector<ImGuiStorage::Pair>::iterator last = data.end(); 1373 int count = (int)(last - first); 1374 while (count > 0) 1375 { 1376 int count2 = count / 2; 1377 ImVector<ImGuiStorage::Pair>::iterator mid = first + count2; 1378 if (mid->key < key) 1379 { 1380 first = ++mid; 1381 count -= count2 + 1; 1382 } 1383 else 1384 { 1385 count = count2; 1386 } 1387 } 1388 return first; 1389 } 1390 1391 int ImGuiStorage::GetInt(ImGuiID key, int default_val) const 1392 { 1393 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key); 1394 if (it == Data.end() || it->key != key) 1395 return default_val; 1396 return it->val_i; 1397 } 1398 1399 bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const 1400 { 1401 return GetInt(key, default_val ? 1 : 0) != 0; 1402 } 1403 1404 float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const 1405 { 1406 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key); 1407 if (it == Data.end() || it->key != key) 1408 return default_val; 1409 return it->val_f; 1410 } 1411 1412 void* ImGuiStorage::GetVoidPtr(ImGuiID key) const 1413 { 1414 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key); 1415 if (it == Data.end() || it->key != key) 1416 return NULL; 1417 return it->val_p; 1418 } 1419 1420 // 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. 1421 int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) 1422 { 1423 ImVector<Pair>::iterator it = LowerBound(Data, key); 1424 if (it == Data.end() || it->key != key) 1425 it = Data.insert(it, Pair(key, default_val)); 1426 return &it->val_i; 1427 } 1428 1429 bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) 1430 { 1431 return (bool*)GetIntRef(key, default_val ? 1 : 0); 1432 } 1433 1434 float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) 1435 { 1436 ImVector<Pair>::iterator it = LowerBound(Data, key); 1437 if (it == Data.end() || it->key != key) 1438 it = Data.insert(it, Pair(key, default_val)); 1439 return &it->val_f; 1440 } 1441 1442 void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) 1443 { 1444 ImVector<Pair>::iterator it = LowerBound(Data, key); 1445 if (it == Data.end() || it->key != key) 1446 it = Data.insert(it, Pair(key, default_val)); 1447 return &it->val_p; 1448 } 1449 1450 // 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) 1451 void ImGuiStorage::SetInt(ImGuiID key, int val) 1452 { 1453 ImVector<Pair>::iterator it = LowerBound(Data, key); 1454 if (it == Data.end() || it->key != key) 1455 { 1456 Data.insert(it, Pair(key, val)); 1457 return; 1458 } 1459 it->val_i = val; 1460 } 1461 1462 void ImGuiStorage::SetBool(ImGuiID key, bool val) 1463 { 1464 SetInt(key, val ? 1 : 0); 1465 } 1466 1467 void ImGuiStorage::SetFloat(ImGuiID key, float val) 1468 { 1469 ImVector<Pair>::iterator it = LowerBound(Data, key); 1470 if (it == Data.end() || it->key != key) 1471 { 1472 Data.insert(it, Pair(key, val)); 1473 return; 1474 } 1475 it->val_f = val; 1476 } 1477 1478 void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) 1479 { 1480 ImVector<Pair>::iterator it = LowerBound(Data, key); 1481 if (it == Data.end() || it->key != key) 1482 { 1483 Data.insert(it, Pair(key, val)); 1484 return; 1485 } 1486 it->val_p = val; 1487 } 1488 1489 void ImGuiStorage::SetAllInt(int v) 1490 { 1491 for (int i = 0; i < Data.Size; i++) 1492 Data[i].val_i = v; 1493 } 1494 1495 //----------------------------------------------------------------------------- 1496 // ImGuiTextFilter 1497 //----------------------------------------------------------------------------- 1498 1499 // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" 1500 ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) 1501 { 1502 if (default_filter) 1503 { 1504 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); 1505 Build(); 1506 } 1507 else 1508 { 1509 InputBuf[0] = 0; 1510 CountGrep = 0; 1511 } 1512 } 1513 1514 bool ImGuiTextFilter::Draw(const char* label, float width) 1515 { 1516 if (width != 0.0f) 1517 ImGui::PushItemWidth(width); 1518 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); 1519 if (width != 0.0f) 1520 ImGui::PopItemWidth(); 1521 if (value_changed) 1522 Build(); 1523 return value_changed; 1524 } 1525 1526 void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>& out) 1527 { 1528 out.resize(0); 1529 const char* wb = b; 1530 const char* we = wb; 1531 while (we < e) 1532 { 1533 if (*we == separator) 1534 { 1535 out.push_back(TextRange(wb, we)); 1536 wb = we + 1; 1537 } 1538 we++; 1539 } 1540 if (wb != we) 1541 out.push_back(TextRange(wb, we)); 1542 } 1543 1544 void ImGuiTextFilter::Build() 1545 { 1546 Filters.resize(0); 1547 TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); 1548 input_range.split(',', Filters); 1549 1550 CountGrep = 0; 1551 for (int i = 0; i != Filters.Size; i++) 1552 { 1553 Filters[i].trim_blanks(); 1554 if (Filters[i].empty()) 1555 continue; 1556 if (Filters[i].front() != '-') 1557 CountGrep += 1; 1558 } 1559 } 1560 1561 bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const 1562 { 1563 if (Filters.empty()) 1564 return true; 1565 1566 if (text == NULL) 1567 text = ""; 1568 1569 for (int i = 0; i != Filters.Size; i++) 1570 { 1571 const TextRange& f = Filters[i]; 1572 if (f.empty()) 1573 continue; 1574 if (f.front() == '-') 1575 { 1576 // Subtract 1577 if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) 1578 return false; 1579 } 1580 else 1581 { 1582 // Grep 1583 if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) 1584 return true; 1585 } 1586 } 1587 1588 // Implicit * grep 1589 if (CountGrep == 0) 1590 return true; 1591 1592 return false; 1593 } 1594 1595 //----------------------------------------------------------------------------- 1596 // ImGuiTextBuffer 1597 //----------------------------------------------------------------------------- 1598 1599 // On some platform vsnprintf() takes va_list by reference and modifies it. 1600 // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. 1601 #ifndef va_copy 1602 #define va_copy(dest, src) (dest = src) 1603 #endif 1604 1605 // Helper: Text buffer for logging/accumulating text 1606 void ImGuiTextBuffer::appendv(const char* fmt, va_list args) 1607 { 1608 va_list args_copy; 1609 va_copy(args_copy, args); 1610 1611 int len = vsnprintf(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. 1612 if (len <= 0) 1613 return; 1614 1615 const int write_off = Buf.Size; 1616 const int needed_sz = write_off + len; 1617 if (write_off + len >= Buf.Capacity) 1618 { 1619 int double_capacity = Buf.Capacity * 2; 1620 Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); 1621 } 1622 1623 Buf.resize(needed_sz); 1624 ImFormatStringV(&Buf[write_off] - 1, len+1, fmt, args_copy); 1625 } 1626 1627 void ImGuiTextBuffer::append(const char* fmt, ...) 1628 { 1629 va_list args; 1630 va_start(args, fmt); 1631 appendv(fmt, args); 1632 va_end(args); 1633 } 1634 1635 //----------------------------------------------------------------------------- 1636 // ImGuiSimpleColumns 1637 //----------------------------------------------------------------------------- 1638 1639 ImGuiSimpleColumns::ImGuiSimpleColumns() 1640 { 1641 Count = 0; 1642 Spacing = Width = NextWidth = 0.0f; 1643 memset(Pos, 0, sizeof(Pos)); 1644 memset(NextWidths, 0, sizeof(NextWidths)); 1645 } 1646 1647 void ImGuiSimpleColumns::Update(int count, float spacing, bool clear) 1648 { 1649 IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); 1650 Count = count; 1651 Width = NextWidth = 0.0f; 1652 Spacing = spacing; 1653 if (clear) memset(NextWidths, 0, sizeof(NextWidths)); 1654 for (int i = 0; i < Count; i++) 1655 { 1656 if (i > 0 && NextWidths[i] > 0.0f) 1657 Width += Spacing; 1658 Pos[i] = (float)(int)Width; 1659 Width += NextWidths[i]; 1660 NextWidths[i] = 0.0f; 1661 } 1662 } 1663 1664 float ImGuiSimpleColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double 1665 { 1666 NextWidth = 0.0f; 1667 NextWidths[0] = ImMax(NextWidths[0], w0); 1668 NextWidths[1] = ImMax(NextWidths[1], w1); 1669 NextWidths[2] = ImMax(NextWidths[2], w2); 1670 for (int i = 0; i < 3; i++) 1671 NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); 1672 return ImMax(Width, NextWidth); 1673 } 1674 1675 float ImGuiSimpleColumns::CalcExtraSpace(float avail_w) 1676 { 1677 return ImMax(0.0f, avail_w - Width); 1678 } 1679 1680 //----------------------------------------------------------------------------- 1681 // ImGuiListClipper 1682 //----------------------------------------------------------------------------- 1683 1684 static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) 1685 { 1686 // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. 1687 // 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? 1688 ImGui::SetCursorPosY(pos_y); 1689 ImGuiWindow* window = ImGui::GetCurrentWindow(); 1690 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. 1691 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. 1692 if (window->DC.ColumnsCount > 1) 1693 window->DC.ColumnsCellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly 1694 } 1695 1696 // Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 1697 // Use case B: Begin() called from constructor with items_height>0 1698 // 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. 1699 void ImGuiListClipper::Begin(int count, float items_height) 1700 { 1701 StartPosY = ImGui::GetCursorPosY(); 1702 ItemsHeight = items_height; 1703 ItemsCount = count; 1704 StepNo = 0; 1705 DisplayEnd = DisplayStart = -1; 1706 if (ItemsHeight > 0.0f) 1707 { 1708 ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display 1709 if (DisplayStart > 0) 1710 SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor 1711 StepNo = 2; 1712 } 1713 } 1714 1715 void ImGuiListClipper::End() 1716 { 1717 if (ItemsCount < 0) 1718 return; 1719 // 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. 1720 if (ItemsCount < INT_MAX) 1721 SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor 1722 ItemsCount = -1; 1723 StepNo = 3; 1724 } 1725 1726 bool ImGuiListClipper::Step() 1727 { 1728 if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) 1729 { 1730 ItemsCount = -1; 1731 return false; 1732 } 1733 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. 1734 { 1735 DisplayStart = 0; 1736 DisplayEnd = 1; 1737 StartPosY = ImGui::GetCursorPosY(); 1738 StepNo = 1; 1739 return true; 1740 } 1741 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. 1742 { 1743 if (ItemsCount == 1) { ItemsCount = -1; return false; } 1744 float items_height = ImGui::GetCursorPosY() - StartPosY; 1745 IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically 1746 Begin(ItemsCount-1, items_height); 1747 DisplayStart++; 1748 DisplayEnd++; 1749 StepNo = 3; 1750 return true; 1751 } 1752 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. 1753 { 1754 IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); 1755 StepNo = 3; 1756 return true; 1757 } 1758 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. 1759 End(); 1760 return false; 1761 } 1762 1763 //----------------------------------------------------------------------------- 1764 // ImGuiWindow 1765 //----------------------------------------------------------------------------- 1766 1767 ImGuiWindow::ImGuiWindow(const char* name) 1768 { 1769 Name = ImStrdup(name); 1770 ID = ImHash(name, 0); 1771 IDStack.push_back(ID); 1772 MoveId = GetID("#MOVE"); 1773 1774 Flags = 0; 1775 IndexWithinParent = 0; 1776 PosFloat = Pos = ImVec2(0.0f, 0.0f); 1777 Size = SizeFull = ImVec2(0.0f, 0.0f); 1778 SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); 1779 WindowPadding = ImVec2(0.0f, 0.0f); 1780 Scroll = ImVec2(0.0f, 0.0f); 1781 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); 1782 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); 1783 ScrollbarX = ScrollbarY = false; 1784 ScrollbarSizes = ImVec2(0.0f, 0.0f); 1785 BorderSize = 0.0f; 1786 Active = WasActive = false; 1787 Accessed = false; 1788 Collapsed = false; 1789 SkipItems = false; 1790 BeginCount = 0; 1791 PopupId = 0; 1792 AutoFitFramesX = AutoFitFramesY = -1; 1793 AutoFitOnlyGrows = false; 1794 AutoPosLastDirection = -1; 1795 HiddenFrames = 0; 1796 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiSetCond_Always | ImGuiSetCond_Once | ImGuiSetCond_FirstUseEver | ImGuiSetCond_Appearing; 1797 SetWindowPosCenterWanted = false; 1798 1799 LastFrameActive = -1; 1800 ItemWidthDefault = 0.0f; 1801 FontWindowScale = 1.0f; 1802 1803 DrawList = (ImDrawList*)ImGui::MemAlloc(sizeof(ImDrawList)); 1804 IM_PLACEMENT_NEW(DrawList) ImDrawList(); 1805 DrawList->_OwnerName = Name; 1806 RootWindow = NULL; 1807 RootNonPopupWindow = NULL; 1808 ParentWindow = NULL; 1809 1810 FocusIdxAllCounter = FocusIdxTabCounter = -1; 1811 FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; 1812 FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; 1813 } 1814 1815 ImGuiWindow::~ImGuiWindow() 1816 { 1817 DrawList->~ImDrawList(); 1818 ImGui::MemFree(DrawList); 1819 DrawList = NULL; 1820 ImGui::MemFree(Name); 1821 Name = NULL; 1822 } 1823 1824 ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) 1825 { 1826 ImGuiID seed = IDStack.back(); 1827 ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); 1828 ImGui::KeepAliveID(id); 1829 return id; 1830 } 1831 1832 ImGuiID ImGuiWindow::GetID(const void* ptr) 1833 { 1834 ImGuiID seed = IDStack.back(); 1835 ImGuiID id = ImHash(&ptr, sizeof(void*), seed); 1836 ImGui::KeepAliveID(id); 1837 return id; 1838 } 1839 1840 ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) 1841 { 1842 ImGuiID seed = IDStack.back(); 1843 return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); 1844 } 1845 1846 //----------------------------------------------------------------------------- 1847 // Internal API exposed in imgui_internal.h 1848 //----------------------------------------------------------------------------- 1849 1850 static void SetCurrentWindow(ImGuiWindow* window) 1851 { 1852 ImGuiContext& g = *GImGui; 1853 g.CurrentWindow = window; 1854 if (window) 1855 g.FontSize = window->CalcFontSize(); 1856 } 1857 1858 ImGuiWindow* ImGui::GetParentWindow() 1859 { 1860 ImGuiContext& g = *GImGui; 1861 IM_ASSERT(g.CurrentWindowStack.Size >= 2); 1862 return g.CurrentWindowStack[(unsigned int)g.CurrentWindowStack.Size - 2]; 1863 } 1864 1865 void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) 1866 { 1867 ImGuiContext& g = *GImGui; 1868 g.ActiveId = id; 1869 g.ActiveIdAllowOverlap = false; 1870 g.ActiveIdIsJustActivated = true; 1871 if (id) 1872 g.ActiveIdIsAlive = true; 1873 g.ActiveIdWindow = window; 1874 } 1875 1876 void ImGui::ClearActiveID() 1877 { 1878 SetActiveID(0, NULL); 1879 } 1880 1881 void ImGui::SetHoveredID(ImGuiID id) 1882 { 1883 ImGuiContext& g = *GImGui; 1884 g.HoveredId = id; 1885 g.HoveredIdAllowOverlap = false; 1886 } 1887 1888 void ImGui::KeepAliveID(ImGuiID id) 1889 { 1890 ImGuiContext& g = *GImGui; 1891 if (g.ActiveId == id) 1892 g.ActiveIdIsAlive = true; 1893 } 1894 1895 // Advance cursor given item size for layout. 1896 void ImGui::ItemSize(const ImVec2& size, float text_offset_y) 1897 { 1898 ImGuiWindow* window = GetCurrentWindow(); 1899 if (window->SkipItems) 1900 return; 1901 1902 // Always align ourselves on pixel boundaries 1903 ImGuiContext& g = *GImGui; 1904 const float line_height = ImMax(window->DC.CurrentLineHeight, size.y); 1905 const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); 1906 window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); 1907 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)); 1908 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); 1909 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); 1910 1911 //window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // Debug 1912 1913 window->DC.PrevLineHeight = line_height; 1914 window->DC.PrevLineTextBaseOffset = text_base_offset; 1915 window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f; 1916 } 1917 1918 void ImGui::ItemSize(const ImRect& bb, float text_offset_y) 1919 { 1920 ItemSize(bb.GetSize(), text_offset_y); 1921 } 1922 1923 // Declare item bounding box for clipping and interaction. 1924 // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface 1925 // declares their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). 1926 bool ImGui::ItemAdd(const ImRect& bb, const ImGuiID* id) 1927 { 1928 ImGuiWindow* window = GetCurrentWindow(); 1929 window->DC.LastItemId = id ? *id : 0; 1930 window->DC.LastItemRect = bb; 1931 window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = false; 1932 if (IsClippedEx(bb, id, false)) 1933 return false; 1934 1935 // This is a sensible default, but widgets are free to override it after calling ItemAdd() 1936 ImGuiContext& g = *GImGui; 1937 if (IsMouseHoveringRect(bb.Min, bb.Max)) 1938 { 1939 // Matching the behavior of IsHovered() but allow if ActiveId==window->MoveID (we clicked on the window background) 1940 // So that clicking on items with no active id such as Text() still returns true with IsItemHovered() 1941 window->DC.LastItemHoveredRect = true; 1942 if (g.HoveredRootWindow == window->RootWindow) 1943 if (g.ActiveId == 0 || (id && g.ActiveId == *id) || g.ActiveIdAllowOverlap || (g.ActiveId == window->MoveId)) 1944 if (IsWindowContentHoverable(window)) 1945 window->DC.LastItemHoveredAndUsable = true; 1946 } 1947 1948 return true; 1949 } 1950 1951 bool ImGui::IsClippedEx(const ImRect& bb, const ImGuiID* id, bool clip_even_when_logged) 1952 { 1953 ImGuiContext& g = *GImGui; 1954 ImGuiWindow* window = GetCurrentWindowRead(); 1955 1956 if (!bb.Overlaps(window->ClipRect)) 1957 if (!id || *id != GImGui->ActiveId) 1958 if (clip_even_when_logged || !g.LogEnabled) 1959 return true; 1960 return false; 1961 } 1962 1963 // NB: This is an internal helper. The user-facing IsItemHovered() is using data emitted from ItemAdd(), with a slightly different logic. 1964 bool ImGui::IsHovered(const ImRect& bb, ImGuiID id, bool flatten_childs) 1965 { 1966 ImGuiContext& g = *GImGui; 1967 if (g.HoveredId == 0 || g.HoveredId == id || g.HoveredIdAllowOverlap) 1968 { 1969 ImGuiWindow* window = GetCurrentWindowRead(); 1970 if (g.HoveredWindow == window || (flatten_childs && g.HoveredRootWindow == window->RootWindow)) 1971 if ((g.ActiveId == 0 || g.ActiveId == id || g.ActiveIdAllowOverlap) && IsMouseHoveringRect(bb.Min, bb.Max)) 1972 if (IsWindowContentHoverable(g.HoveredRootWindow)) 1973 return true; 1974 } 1975 return false; 1976 } 1977 1978 bool ImGui::FocusableItemRegister(ImGuiWindow* window, bool is_active, bool tab_stop) 1979 { 1980 ImGuiContext& g = *GImGui; 1981 1982 const bool allow_keyboard_focus = window->DC.AllowKeyboardFocus; 1983 window->FocusIdxAllCounter++; 1984 if (allow_keyboard_focus) 1985 window->FocusIdxTabCounter++; 1986 1987 // Process keyboard input at this point: TAB, Shift-TAB switch focus 1988 // We can always TAB out of a widget that doesn't allow tabbing in. 1989 if (tab_stop && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && is_active && IsKeyPressedMap(ImGuiKey_Tab)) 1990 { 1991 // Modulo on index will be applied at the end of frame once we've got the total counter of items. 1992 window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); 1993 } 1994 1995 if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) 1996 return true; 1997 1998 if (allow_keyboard_focus) 1999 if (window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) 2000 return true; 2001 2002 return false; 2003 } 2004 2005 void ImGui::FocusableItemUnregister(ImGuiWindow* window) 2006 { 2007 window->FocusIdxAllCounter--; 2008 window->FocusIdxTabCounter--; 2009 } 2010 2011 ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) 2012 { 2013 ImGuiContext& g = *GImGui; 2014 ImVec2 content_max; 2015 if (size.x < 0.0f || size.y < 0.0f) 2016 content_max = g.CurrentWindow->Pos + GetContentRegionMax(); 2017 if (size.x <= 0.0f) 2018 size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; 2019 if (size.y <= 0.0f) 2020 size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; 2021 return size; 2022 } 2023 2024 float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) 2025 { 2026 if (wrap_pos_x < 0.0f) 2027 return 0.0f; 2028 2029 ImGuiWindow* window = GetCurrentWindowRead(); 2030 if (wrap_pos_x == 0.0f) 2031 wrap_pos_x = GetContentRegionMax().x + window->Pos.x; 2032 else if (wrap_pos_x > 0.0f) 2033 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space 2034 2035 return ImMax(wrap_pos_x - pos.x, 1.0f); 2036 } 2037 2038 //----------------------------------------------------------------------------- 2039 2040 void* ImGui::MemAlloc(size_t sz) 2041 { 2042 GImGui->IO.MetricsAllocs++; 2043 return GImGui->IO.MemAllocFn(sz); 2044 } 2045 2046 void ImGui::MemFree(void* ptr) 2047 { 2048 if (ptr) GImGui->IO.MetricsAllocs--; 2049 return GImGui->IO.MemFreeFn(ptr); 2050 } 2051 2052 const char* ImGui::GetClipboardText() 2053 { 2054 return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; 2055 } 2056 2057 void ImGui::SetClipboardText(const char* text) 2058 { 2059 if (GImGui->IO.SetClipboardTextFn) 2060 GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); 2061 } 2062 2063 const char* ImGui::GetVersion() 2064 { 2065 return IMGUI_VERSION; 2066 } 2067 2068 // Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself 2069 // 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 2070 ImGuiContext* ImGui::GetCurrentContext() 2071 { 2072 return GImGui; 2073 } 2074 2075 void ImGui::SetCurrentContext(ImGuiContext* ctx) 2076 { 2077 #ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC 2078 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. 2079 #else 2080 GImGui = ctx; 2081 #endif 2082 } 2083 2084 ImGuiContext* ImGui::CreateContext(void* (*malloc_fn)(size_t), void (*free_fn)(void*)) 2085 { 2086 if (!malloc_fn) malloc_fn = malloc; 2087 ImGuiContext* ctx = (ImGuiContext*)malloc_fn(sizeof(ImGuiContext)); 2088 IM_PLACEMENT_NEW(ctx) ImGuiContext(); 2089 ctx->IO.MemAllocFn = malloc_fn; 2090 ctx->IO.MemFreeFn = free_fn ? free_fn : free; 2091 return ctx; 2092 } 2093 2094 void ImGui::DestroyContext(ImGuiContext* ctx) 2095 { 2096 void (*free_fn)(void*) = ctx->IO.MemFreeFn; 2097 ctx->~ImGuiContext(); 2098 free_fn(ctx); 2099 if (GImGui == ctx) 2100 SetCurrentContext(NULL); 2101 } 2102 2103 ImGuiIO& ImGui::GetIO() 2104 { 2105 return GImGui->IO; 2106 } 2107 2108 ImGuiStyle& ImGui::GetStyle() 2109 { 2110 return GImGui->Style; 2111 } 2112 2113 // Same value as passed to your RenderDrawListsFn() function. valid after Render() and until the next call to NewFrame() 2114 ImDrawData* ImGui::GetDrawData() 2115 { 2116 return GImGui->RenderDrawData.Valid ? &GImGui->RenderDrawData : NULL; 2117 } 2118 2119 float ImGui::GetTime() 2120 { 2121 return GImGui->Time; 2122 } 2123 2124 int ImGui::GetFrameCount() 2125 { 2126 return GImGui->FrameCount; 2127 } 2128 2129 void ImGui::NewFrame() 2130 { 2131 ImGuiContext& g = *GImGui; 2132 2133 // Check user data 2134 IM_ASSERT(g.IO.DeltaTime >= 0.0f); // Need a positive DeltaTime (zero is tolerated but will cause some timing issues) 2135 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f); 2136 IM_ASSERT(g.IO.Fonts->Fonts.Size > 0); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? 2137 IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? 2138 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f); // Invalid style setting 2139 2140 if (!g.Initialized) 2141 { 2142 // Initialize on first frame 2143 g.LogClipboard = (ImGuiTextBuffer*)ImGui::MemAlloc(sizeof(ImGuiTextBuffer)); 2144 IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer(); 2145 2146 IM_ASSERT(g.Settings.empty()); 2147 LoadIniSettingsFromDisk(g.IO.IniFilename); 2148 g.Initialized = true; 2149 } 2150 2151 SetCurrentFont(GetDefaultFont()); 2152 IM_ASSERT(g.Font->IsLoaded()); 2153 2154 g.Time += g.IO.DeltaTime; 2155 g.FrameCount += 1; 2156 g.Tooltip[0] = '\0'; 2157 g.OverlayDrawList.Clear(); 2158 g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); 2159 g.OverlayDrawList.PushClipRectFullScreen(); 2160 2161 // Mark rendering data as invalid to prevent user who may have a handle on it to use it 2162 g.RenderDrawData.Valid = false; 2163 g.RenderDrawData.CmdLists = NULL; 2164 g.RenderDrawData.CmdListsCount = g.RenderDrawData.TotalVtxCount = g.RenderDrawData.TotalIdxCount = 0; 2165 2166 // Update inputs state 2167 if (g.IO.MousePos.x < 0 && g.IO.MousePos.y < 0) 2168 g.IO.MousePos = ImVec2(-9999.0f, -9999.0f); 2169 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 2170 g.IO.MouseDelta = ImVec2(0.0f, 0.0f); 2171 else 2172 g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; 2173 g.IO.MousePosPrev = g.IO.MousePos; 2174 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) 2175 { 2176 g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; 2177 g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; 2178 g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; 2179 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; 2180 g.IO.MouseDoubleClicked[i] = false; 2181 if (g.IO.MouseClicked[i]) 2182 { 2183 if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime) 2184 { 2185 if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) 2186 g.IO.MouseDoubleClicked[i] = true; 2187 g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click 2188 } 2189 else 2190 { 2191 g.IO.MouseClickedTime[i] = g.Time; 2192 } 2193 g.IO.MouseClickedPos[i] = g.IO.MousePos; 2194 g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; 2195 } 2196 else if (g.IO.MouseDown[i]) 2197 { 2198 g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i])); 2199 } 2200 } 2201 memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); 2202 for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) 2203 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; 2204 2205 // Calculate frame-rate for the user, as a purely luxurious feature 2206 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; 2207 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; 2208 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); 2209 g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); 2210 2211 // Clear reference to active widget if the widget isn't alive anymore 2212 g.HoveredIdPreviousFrame = g.HoveredId; 2213 g.HoveredId = 0; 2214 g.HoveredIdAllowOverlap = false; 2215 if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) 2216 ClearActiveID(); 2217 g.ActiveIdPreviousFrame = g.ActiveId; 2218 g.ActiveIdIsAlive = false; 2219 g.ActiveIdIsJustActivated = false; 2220 2221 // Handle user moving window (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows. 2222 if (g.MovedWindowMoveId && g.MovedWindowMoveId == g.ActiveId) 2223 { 2224 KeepAliveID(g.MovedWindowMoveId); 2225 IM_ASSERT(g.MovedWindow && g.MovedWindow->RootWindow); 2226 IM_ASSERT(g.MovedWindow->RootWindow->MoveId == g.MovedWindowMoveId); 2227 if (g.IO.MouseDown[0]) 2228 { 2229 if (!(g.MovedWindow->Flags & ImGuiWindowFlags_NoMove)) 2230 { 2231 g.MovedWindow->PosFloat += g.IO.MouseDelta; 2232 if (!(g.MovedWindow->Flags & ImGuiWindowFlags_NoSavedSettings) && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)) 2233 MarkIniSettingsDirty(); 2234 } 2235 FocusWindow(g.MovedWindow); 2236 } 2237 else 2238 { 2239 ClearActiveID(); 2240 g.MovedWindow = NULL; 2241 g.MovedWindowMoveId = 0; 2242 } 2243 } 2244 else 2245 { 2246 g.MovedWindow = NULL; 2247 g.MovedWindowMoveId = 0; 2248 } 2249 2250 // Delay saving settings so we don't spam disk too much 2251 if (g.SettingsDirtyTimer > 0.0f) 2252 { 2253 g.SettingsDirtyTimer -= g.IO.DeltaTime; 2254 if (g.SettingsDirtyTimer <= 0.0f) 2255 SaveIniSettingsToDisk(g.IO.IniFilename); 2256 } 2257 2258 // Find the window we are hovering. Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow 2259 g.HoveredWindow = g.MovedWindow ? g.MovedWindow : FindHoveredWindow(g.IO.MousePos, false); 2260 if (g.HoveredWindow && (g.HoveredWindow->Flags & ImGuiWindowFlags_ChildWindow)) 2261 g.HoveredRootWindow = g.HoveredWindow->RootWindow; 2262 else 2263 g.HoveredRootWindow = g.MovedWindow ? g.MovedWindow->RootWindow : FindHoveredWindow(g.IO.MousePos, true); 2264 2265 if (ImGuiWindow* modal_window = GetFrontMostModalRootWindow()) 2266 { 2267 g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f); 2268 ImGuiWindow* window = g.HoveredRootWindow; 2269 while (window && window != modal_window) 2270 window = window->ParentWindow; 2271 if (!window) 2272 g.HoveredRootWindow = g.HoveredWindow = NULL; 2273 } 2274 else 2275 { 2276 g.ModalWindowDarkeningRatio = 0.0f; 2277 } 2278 2279 // Are we using inputs? Tell user so they can capture/discard the inputs away from the rest of their application. 2280 // 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. 2281 int mouse_earliest_button_down = -1; 2282 bool mouse_any_down = false; 2283 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) 2284 { 2285 if (g.IO.MouseClicked[i]) 2286 g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); 2287 mouse_any_down |= g.IO.MouseDown[i]; 2288 if (g.IO.MouseDown[i]) 2289 if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[mouse_earliest_button_down] > g.IO.MouseClickedTime[i]) 2290 mouse_earliest_button_down = i; 2291 } 2292 bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; 2293 if (g.CaptureMouseNextFrame != -1) 2294 g.IO.WantCaptureMouse = (g.CaptureMouseNextFrame != 0); 2295 else 2296 g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.ActiveId != 0) || (!g.OpenPopupStack.empty()); 2297 g.IO.WantCaptureKeyboard = (g.CaptureKeyboardNextFrame != -1) ? (g.CaptureKeyboardNextFrame != 0) : (g.ActiveId != 0); 2298 g.IO.WantTextInput = (g.ActiveId != 0 && g.InputTextState.Id == g.ActiveId); 2299 g.MouseCursor = ImGuiMouseCursor_Arrow; 2300 g.CaptureMouseNextFrame = g.CaptureKeyboardNextFrame = -1; 2301 g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default 2302 2303 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. 2304 if (!mouse_avail_to_imgui) 2305 g.HoveredWindow = g.HoveredRootWindow = NULL; 2306 2307 // Scale & Scrolling 2308 if (g.HoveredWindow && g.IO.MouseWheel != 0.0f && !g.HoveredWindow->Collapsed) 2309 { 2310 ImGuiWindow* window = g.HoveredWindow; 2311 if (g.IO.KeyCtrl) 2312 { 2313 if (g.IO.FontAllowUserScaling) 2314 { 2315 // Zoom / Scale window 2316 float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); 2317 float scale = new_font_scale / window->FontWindowScale; 2318 window->FontWindowScale = new_font_scale; 2319 2320 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; 2321 window->Pos += offset; 2322 window->PosFloat += offset; 2323 window->Size *= scale; 2324 window->SizeFull *= scale; 2325 } 2326 } 2327 else if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) 2328 { 2329 // Scroll 2330 const int scroll_lines = (window->Flags & ImGuiWindowFlags_ComboBox) ? 3 : 5; 2331 SetWindowScrollY(window, window->Scroll.y - g.IO.MouseWheel * window->CalcFontSize() * scroll_lines); 2332 } 2333 } 2334 2335 // Pressing TAB activate widget focus 2336 // 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. 2337 if (g.ActiveId == 0 && g.FocusedWindow != NULL && g.FocusedWindow->Active && IsKeyPressedMap(ImGuiKey_Tab, false)) 2338 g.FocusedWindow->FocusIdxTabRequestNext = 0; 2339 2340 // Mark all windows as not visible 2341 for (int i = 0; i != g.Windows.Size; i++) 2342 { 2343 ImGuiWindow* window = g.Windows[i]; 2344 window->WasActive = window->Active; 2345 window->Active = false; 2346 window->Accessed = false; 2347 } 2348 2349 // Closing the focused window restore focus to the first active root window in descending z-order 2350 if (g.FocusedWindow && !g.FocusedWindow->WasActive) 2351 for (int i = g.Windows.Size-1; i >= 0; i--) 2352 if (g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) 2353 { 2354 FocusWindow(g.Windows[i]); 2355 break; 2356 } 2357 2358 // No window should be open at the beginning of the frame. 2359 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. 2360 g.CurrentWindowStack.resize(0); 2361 g.CurrentPopupStack.resize(0); 2362 CloseInactivePopups(); 2363 2364 // Create implicit window - we will only render it if the user has added something to it. 2365 ImGui::SetNextWindowSize(ImVec2(400,400), ImGuiSetCond_FirstUseEver); 2366 ImGui::Begin("Debug"); 2367 } 2368 2369 // NB: behavior of ImGui after Shutdown() is not tested/guaranteed at the moment. This function is merely here to free heap allocations. 2370 void ImGui::Shutdown() 2371 { 2372 ImGuiContext& g = *GImGui; 2373 2374 // 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) 2375 if (g.IO.Fonts) // Testing for NULL to allow user to NULLify in case of running Shutdown() on multiple contexts. Bit hacky. 2376 g.IO.Fonts->Clear(); 2377 2378 // Cleanup of other data are conditional on actually having used ImGui. 2379 if (!g.Initialized) 2380 return; 2381 2382 SaveIniSettingsToDisk(g.IO.IniFilename); 2383 2384 for (int i = 0; i < g.Windows.Size; i++) 2385 { 2386 g.Windows[i]->~ImGuiWindow(); 2387 ImGui::MemFree(g.Windows[i]); 2388 } 2389 g.Windows.clear(); 2390 g.WindowsSortBuffer.clear(); 2391 g.CurrentWindow = NULL; 2392 g.CurrentWindowStack.clear(); 2393 g.FocusedWindow = NULL; 2394 g.HoveredWindow = NULL; 2395 g.HoveredRootWindow = NULL; 2396 g.ActiveIdWindow = NULL; 2397 g.MovedWindow = NULL; 2398 for (int i = 0; i < g.Settings.Size; i++) 2399 ImGui::MemFree(g.Settings[i].Name); 2400 g.Settings.clear(); 2401 g.ColorModifiers.clear(); 2402 g.StyleModifiers.clear(); 2403 g.FontStack.clear(); 2404 g.OpenPopupStack.clear(); 2405 g.CurrentPopupStack.clear(); 2406 g.SetNextWindowSizeConstraintCallback = NULL; 2407 g.SetNextWindowSizeConstraintCallbackUserData = NULL; 2408 for (int i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) 2409 g.RenderDrawLists[i].clear(); 2410 g.OverlayDrawList.ClearFreeMemory(); 2411 g.ColorEditModeStorage.Clear(); 2412 if (g.PrivateClipboard) 2413 { 2414 ImGui::MemFree(g.PrivateClipboard); 2415 g.PrivateClipboard = NULL; 2416 } 2417 g.InputTextState.Text.clear(); 2418 g.InputTextState.InitialText.clear(); 2419 g.InputTextState.TempTextBuffer.clear(); 2420 2421 if (g.LogFile && g.LogFile != stdout) 2422 { 2423 fclose(g.LogFile); 2424 g.LogFile = NULL; 2425 } 2426 if (g.LogClipboard) 2427 { 2428 g.LogClipboard->~ImGuiTextBuffer(); 2429 ImGui::MemFree(g.LogClipboard); 2430 } 2431 2432 g.Initialized = false; 2433 } 2434 2435 static ImGuiIniData* FindWindowSettings(const char* name) 2436 { 2437 ImGuiContext& g = *GImGui; 2438 ImGuiID id = ImHash(name, 0); 2439 for (int i = 0; i != g.Settings.Size; i++) 2440 { 2441 ImGuiIniData* ini = &g.Settings[i]; 2442 if (ini->Id == id) 2443 return ini; 2444 } 2445 return NULL; 2446 } 2447 2448 static ImGuiIniData* AddWindowSettings(const char* name) 2449 { 2450 GImGui->Settings.resize(GImGui->Settings.Size + 1); 2451 ImGuiIniData* ini = &GImGui->Settings.back(); 2452 ini->Name = ImStrdup(name); 2453 ini->Id = ImHash(name, 0); 2454 ini->Collapsed = false; 2455 ini->Pos = ImVec2(FLT_MAX,FLT_MAX); 2456 ini->Size = ImVec2(0,0); 2457 return ini; 2458 } 2459 2460 // Zero-tolerance, poor-man .ini parsing 2461 // FIXME: Write something less rubbish 2462 static void LoadIniSettingsFromDisk(const char* ini_filename) 2463 { 2464 ImGuiContext& g = *GImGui; 2465 if (!ini_filename) 2466 return; 2467 2468 int file_size; 2469 char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_size, 1); 2470 if (!file_data) 2471 return; 2472 2473 ImGuiIniData* settings = NULL; 2474 const char* buf_end = file_data + file_size; 2475 for (const char* line_start = file_data; line_start < buf_end; ) 2476 { 2477 const char* line_end = line_start; 2478 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') 2479 line_end++; 2480 2481 if (line_start[0] == '[' && line_end > line_start && line_end[-1] == ']') 2482 { 2483 char name[64]; 2484 ImFormatString(name, IM_ARRAYSIZE(name), "%.*s", (int)(line_end-line_start-2), line_start+1); 2485 settings = FindWindowSettings(name); 2486 if (!settings) 2487 settings = AddWindowSettings(name); 2488 } 2489 else if (settings) 2490 { 2491 float x, y; 2492 int i; 2493 if (sscanf(line_start, "Pos=%f,%f", &x, &y) == 2) 2494 settings->Pos = ImVec2(x, y); 2495 else if (sscanf(line_start, "Size=%f,%f", &x, &y) == 2) 2496 settings->Size = ImMax(ImVec2(x, y), g.Style.WindowMinSize); 2497 else if (sscanf(line_start, "Collapsed=%d", &i) == 1) 2498 settings->Collapsed = (i != 0); 2499 } 2500 2501 line_start = line_end+1; 2502 } 2503 2504 ImGui::MemFree(file_data); 2505 } 2506 2507 static void SaveIniSettingsToDisk(const char* ini_filename) 2508 { 2509 ImGuiContext& g = *GImGui; 2510 g.SettingsDirtyTimer = 0.0f; 2511 if (!ini_filename) 2512 return; 2513 2514 // Gather data from windows that were active during this session 2515 for (int i = 0; i != g.Windows.Size; i++) 2516 { 2517 ImGuiWindow* window = g.Windows[i]; 2518 if (window->Flags & ImGuiWindowFlags_NoSavedSettings) 2519 continue; 2520 ImGuiIniData* settings = FindWindowSettings(window->Name); 2521 settings->Pos = window->Pos; 2522 settings->Size = window->SizeFull; 2523 settings->Collapsed = window->Collapsed; 2524 } 2525 2526 // Write .ini file 2527 // If a window wasn't opened in this session we preserve its settings 2528 FILE* f = ImFileOpen(ini_filename, "wt"); 2529 if (!f) 2530 return; 2531 for (int i = 0; i != g.Settings.Size; i++) 2532 { 2533 const ImGuiIniData* settings = &g.Settings[i]; 2534 if (settings->Pos.x == FLT_MAX) 2535 continue; 2536 const char* name = settings->Name; 2537 if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() 2538 name = p; 2539 fprintf(f, "[%s]\n", name); 2540 fprintf(f, "Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); 2541 fprintf(f, "Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); 2542 fprintf(f, "Collapsed=%d\n", settings->Collapsed); 2543 fprintf(f, "\n"); 2544 } 2545 2546 fclose(f); 2547 } 2548 2549 static void MarkIniSettingsDirty() 2550 { 2551 ImGuiContext& g = *GImGui; 2552 if (g.SettingsDirtyTimer <= 0.0f) 2553 g.SettingsDirtyTimer = g.IO.IniSavingRate; 2554 } 2555 2556 // FIXME: Add a more explicit sort order in the window structure. 2557 static int ChildWindowComparer(const void* lhs, const void* rhs) 2558 { 2559 const ImGuiWindow* a = *(const ImGuiWindow**)lhs; 2560 const ImGuiWindow* b = *(const ImGuiWindow**)rhs; 2561 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) 2562 return d; 2563 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) 2564 return d; 2565 if (int d = (a->Flags & ImGuiWindowFlags_ComboBox) - (b->Flags & ImGuiWindowFlags_ComboBox)) 2566 return d; 2567 return (a->IndexWithinParent - b->IndexWithinParent); 2568 } 2569 2570 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>& out_sorted_windows, ImGuiWindow* window) 2571 { 2572 out_sorted_windows.push_back(window); 2573 if (window->Active) 2574 { 2575 int count = window->DC.ChildWindows.Size; 2576 if (count > 1) 2577 qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); 2578 for (int i = 0; i < count; i++) 2579 { 2580 ImGuiWindow* child = window->DC.ChildWindows[i]; 2581 if (child->Active) 2582 AddWindowToSortedBuffer(out_sorted_windows, child); 2583 } 2584 } 2585 } 2586 2587 static void AddDrawListToRenderList(ImVector<ImDrawList*>& out_render_list, ImDrawList* draw_list) 2588 { 2589 if (draw_list->CmdBuffer.empty()) 2590 return; 2591 2592 // Remove trailing command if unused 2593 ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); 2594 if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) 2595 { 2596 draw_list->CmdBuffer.pop_back(); 2597 if (draw_list->CmdBuffer.empty()) 2598 return; 2599 } 2600 2601 // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. 2602 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); 2603 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); 2604 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); 2605 2606 // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = 2 bytes = 64K vertices) 2607 // If this assert triggers because you are drawing lots of stuff manually, A) workaround by calling BeginChild()/EndChild() to put your draw commands in multiple draw lists, B) #define ImDrawIdx to a 'unsigned int' in imconfig.h and render accordingly. 2608 IM_ASSERT((int64_t)draw_list->_VtxCurrentIdx <= ((int64_t)1L << (sizeof(ImDrawIdx)*8))); // Too many vertices in same ImDrawList. See comment above. 2609 2610 out_render_list.push_back(draw_list); 2611 GImGui->IO.MetricsRenderVertices += draw_list->VtxBuffer.Size; 2612 GImGui->IO.MetricsRenderIndices += draw_list->IdxBuffer.Size; 2613 } 2614 2615 static void AddWindowToRenderList(ImVector<ImDrawList*>& out_render_list, ImGuiWindow* window) 2616 { 2617 AddDrawListToRenderList(out_render_list, window->DrawList); 2618 for (int i = 0; i < window->DC.ChildWindows.Size; i++) 2619 { 2620 ImGuiWindow* child = window->DC.ChildWindows[i]; 2621 if (!child->Active) // clipped children may have been marked not active 2622 continue; 2623 if ((child->Flags & ImGuiWindowFlags_Popup) && child->HiddenFrames > 0) 2624 continue; 2625 AddWindowToRenderList(out_render_list, child); 2626 } 2627 } 2628 2629 // 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. 2630 void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) 2631 { 2632 ImGuiWindow* window = GetCurrentWindow(); 2633 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); 2634 window->ClipRect = window->DrawList->_ClipRectStack.back(); 2635 } 2636 2637 void ImGui::PopClipRect() 2638 { 2639 ImGuiWindow* window = GetCurrentWindow(); 2640 window->DrawList->PopClipRect(); 2641 window->ClipRect = window->DrawList->_ClipRectStack.back(); 2642 } 2643 2644 // 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. 2645 void ImGui::EndFrame() 2646 { 2647 ImGuiContext& g = *GImGui; 2648 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() 2649 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // ImGui::EndFrame() called multiple times, or forgot to call ImGui::NewFrame() again 2650 2651 // Render tooltip 2652 if (g.Tooltip[0]) 2653 { 2654 ImGui::BeginTooltip(); 2655 ImGui::TextUnformatted(g.Tooltip); 2656 ImGui::EndTooltip(); 2657 } 2658 2659 // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) 2660 if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f) 2661 { 2662 g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y); 2663 g.OsImePosSet = g.OsImePosRequest; 2664 } 2665 2666 // Hide implicit "Debug" window if it hasn't been used 2667 IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls 2668 if (g.CurrentWindow && !g.CurrentWindow->Accessed) 2669 g.CurrentWindow->Active = false; 2670 ImGui::End(); 2671 2672 // Click to focus window and start moving (after we're done with all our widgets) 2673 if (g.ActiveId == 0 && g.HoveredId == 0 && g.IO.MouseClicked[0]) 2674 { 2675 if (!(g.FocusedWindow && !g.FocusedWindow->WasActive && g.FocusedWindow->Active)) // Unless we just made a popup appear 2676 { 2677 if (g.HoveredRootWindow != NULL) 2678 { 2679 FocusWindow(g.HoveredWindow); 2680 if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove)) 2681 { 2682 g.MovedWindow = g.HoveredWindow; 2683 g.MovedWindowMoveId = g.HoveredRootWindow->MoveId; 2684 SetActiveID(g.MovedWindowMoveId, g.HoveredRootWindow); 2685 } 2686 } 2687 else if (g.FocusedWindow != NULL && GetFrontMostModalRootWindow() == NULL) 2688 { 2689 // Clicking on void disable focus 2690 FocusWindow(NULL); 2691 } 2692 } 2693 } 2694 2695 // Sort the window list so that all child windows are after their parent 2696 // We cannot do that on FocusWindow() because childs may not exist yet 2697 g.WindowsSortBuffer.resize(0); 2698 g.WindowsSortBuffer.reserve(g.Windows.Size); 2699 for (int i = 0; i != g.Windows.Size; i++) 2700 { 2701 ImGuiWindow* window = g.Windows[i]; 2702 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it 2703 continue; 2704 AddWindowToSortedBuffer(g.WindowsSortBuffer, window); 2705 } 2706 IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong 2707 g.Windows.swap(g.WindowsSortBuffer); 2708 2709 // Clear Input data for next frame 2710 g.IO.MouseWheel = 0.0f; 2711 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); 2712 2713 g.FrameCountEnded = g.FrameCount; 2714 } 2715 2716 void ImGui::Render() 2717 { 2718 ImGuiContext& g = *GImGui; 2719 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() 2720 2721 if (g.FrameCountEnded != g.FrameCount) 2722 ImGui::EndFrame(); 2723 g.FrameCountRendered = g.FrameCount; 2724 2725 // Skip render altogether if alpha is 0.0 2726 // 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. 2727 if (g.Style.Alpha > 0.0f) 2728 { 2729 // Gather windows to render 2730 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0; 2731 for (int i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) 2732 g.RenderDrawLists[i].resize(0); 2733 for (int i = 0; i != g.Windows.Size; i++) 2734 { 2735 ImGuiWindow* window = g.Windows[i]; 2736 if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0) 2737 { 2738 // FIXME: Generalize this with a proper layering system so e.g. user can draw in specific layers, below text, .. 2739 g.IO.MetricsActiveWindows++; 2740 if (window->Flags & ImGuiWindowFlags_Popup) 2741 AddWindowToRenderList(g.RenderDrawLists[1], window); 2742 else if (window->Flags & ImGuiWindowFlags_Tooltip) 2743 AddWindowToRenderList(g.RenderDrawLists[2], window); 2744 else 2745 AddWindowToRenderList(g.RenderDrawLists[0], window); 2746 } 2747 } 2748 2749 // Flatten layers 2750 int n = g.RenderDrawLists[0].Size; 2751 int flattened_size = n; 2752 for (int i = 1; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) 2753 flattened_size += g.RenderDrawLists[i].Size; 2754 g.RenderDrawLists[0].resize(flattened_size); 2755 for (int i = 1; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) 2756 { 2757 ImVector<ImDrawList*>& layer = g.RenderDrawLists[i]; 2758 if (layer.empty()) 2759 continue; 2760 memcpy(&g.RenderDrawLists[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); 2761 n += layer.Size; 2762 } 2763 2764 // Draw software mouse cursor if requested 2765 if (g.IO.MouseDrawCursor) 2766 { 2767 const ImGuiMouseCursorData& cursor_data = g.MouseCursorData[g.MouseCursor]; 2768 const ImVec2 pos = g.IO.MousePos - cursor_data.HotOffset; 2769 const ImVec2 size = cursor_data.Size; 2770 const ImTextureID tex_id = g.IO.Fonts->TexID; 2771 g.OverlayDrawList.PushTextureID(tex_id); 2772 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 2773 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 2774 g.OverlayDrawList.AddImage(tex_id, pos, pos + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0,0,0,255)); // Black border 2775 g.OverlayDrawList.AddImage(tex_id, pos, pos + size, cursor_data.TexUvMin[0], cursor_data.TexUvMax[0], IM_COL32(255,255,255,255)); // White fill 2776 g.OverlayDrawList.PopTextureID(); 2777 } 2778 if (!g.OverlayDrawList.VtxBuffer.empty()) 2779 AddDrawListToRenderList(g.RenderDrawLists[0], &g.OverlayDrawList); 2780 2781 // Setup draw data 2782 g.RenderDrawData.Valid = true; 2783 g.RenderDrawData.CmdLists = (g.RenderDrawLists[0].Size > 0) ? &g.RenderDrawLists[0][0] : NULL; 2784 g.RenderDrawData.CmdListsCount = g.RenderDrawLists[0].Size; 2785 g.RenderDrawData.TotalVtxCount = g.IO.MetricsRenderVertices; 2786 g.RenderDrawData.TotalIdxCount = g.IO.MetricsRenderIndices; 2787 2788 // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() 2789 if (g.RenderDrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) 2790 g.IO.RenderDrawListsFn(&g.RenderDrawData); 2791 } 2792 } 2793 2794 const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) 2795 { 2796 const char* text_display_end = text; 2797 if (!text_end) 2798 text_end = (const char*)-1; 2799 2800 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) 2801 text_display_end++; 2802 return text_display_end; 2803 } 2804 2805 // Pass text data straight to log (without being displayed) 2806 void ImGui::LogText(const char* fmt, ...) 2807 { 2808 ImGuiContext& g = *GImGui; 2809 if (!g.LogEnabled) 2810 return; 2811 2812 va_list args; 2813 va_start(args, fmt); 2814 if (g.LogFile) 2815 { 2816 vfprintf(g.LogFile, fmt, args); 2817 } 2818 else 2819 { 2820 g.LogClipboard->appendv(fmt, args); 2821 } 2822 va_end(args); 2823 } 2824 2825 // Internal version that takes a position to decide on newline placement and pad items according to their depth. 2826 // We split text into individual lines to add current tree level padding 2827 static void LogRenderedText(const ImVec2& ref_pos, const char* text, const char* text_end) 2828 { 2829 ImGuiContext& g = *GImGui; 2830 ImGuiWindow* window = ImGui::GetCurrentWindowRead(); 2831 2832 if (!text_end) 2833 text_end = ImGui::FindRenderedTextEnd(text, text_end); 2834 2835 const bool log_new_line = ref_pos.y > window->DC.LogLinePosY+1; 2836 window->DC.LogLinePosY = ref_pos.y; 2837 2838 const char* text_remaining = text; 2839 if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth 2840 g.LogStartDepth = window->DC.TreeDepth; 2841 const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); 2842 for (;;) 2843 { 2844 // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. 2845 const char* line_end = text_remaining; 2846 while (line_end < text_end) 2847 if (*line_end == '\n') 2848 break; 2849 else 2850 line_end++; 2851 if (line_end >= text_end) 2852 line_end = NULL; 2853 2854 const bool is_first_line = (text == text_remaining); 2855 bool is_last_line = false; 2856 if (line_end == NULL) 2857 { 2858 is_last_line = true; 2859 line_end = text_end; 2860 } 2861 if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0)) 2862 { 2863 const int char_count = (int)(line_end - text_remaining); 2864 if (log_new_line || !is_first_line) 2865 ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining); 2866 else 2867 ImGui::LogText(" %.*s", char_count, text_remaining); 2868 } 2869 2870 if (is_last_line) 2871 break; 2872 text_remaining = line_end + 1; 2873 } 2874 } 2875 2876 // Internal ImGui functions to render text 2877 // RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() 2878 void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) 2879 { 2880 ImGuiContext& g = *GImGui; 2881 ImGuiWindow* window = GetCurrentWindow(); 2882 2883 // Hide anything after a '##' string 2884 const char* text_display_end; 2885 if (hide_text_after_hash) 2886 { 2887 text_display_end = FindRenderedTextEnd(text, text_end); 2888 } 2889 else 2890 { 2891 if (!text_end) 2892 text_end = text + strlen(text); // FIXME-OPT 2893 text_display_end = text_end; 2894 } 2895 2896 const int text_len = (int)(text_display_end - text); 2897 if (text_len > 0) 2898 { 2899 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); 2900 if (g.LogEnabled) 2901 LogRenderedText(pos, text, text_display_end); 2902 } 2903 } 2904 2905 void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) 2906 { 2907 ImGuiContext& g = *GImGui; 2908 ImGuiWindow* window = GetCurrentWindow(); 2909 2910 if (!text_end) 2911 text_end = text + strlen(text); // FIXME-OPT 2912 2913 const int text_len = (int)(text_end - text); 2914 if (text_len > 0) 2915 { 2916 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); 2917 if (g.LogEnabled) 2918 LogRenderedText(pos, text, text_end); 2919 } 2920 } 2921 2922 // Default clip_rect uses (pos_min,pos_max) 2923 // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) 2924 void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) 2925 { 2926 // Hide anything after a '##' string 2927 const char* text_display_end = FindRenderedTextEnd(text, text_end); 2928 const int text_len = (int)(text_display_end - text); 2929 if (text_len == 0) 2930 return; 2931 2932 ImGuiContext& g = *GImGui; 2933 ImGuiWindow* window = GetCurrentWindow(); 2934 2935 // Perform CPU side clipping for single clipped element to avoid using scissor state 2936 ImVec2 pos = pos_min; 2937 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); 2938 2939 const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; 2940 const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; 2941 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); 2942 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min 2943 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); 2944 2945 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. 2946 if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); 2947 if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); 2948 2949 // Render 2950 if (need_clipping) 2951 { 2952 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); 2953 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); 2954 } 2955 else 2956 { 2957 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); 2958 } 2959 if (g.LogEnabled) 2960 LogRenderedText(pos, text, text_display_end); 2961 } 2962 2963 // Render a rectangle shaped with optional rounding and borders 2964 void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) 2965 { 2966 ImGuiWindow* window = GetCurrentWindow(); 2967 2968 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); 2969 if (border && (window->Flags & ImGuiWindowFlags_ShowBorders)) 2970 { 2971 window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding); 2972 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding); 2973 } 2974 } 2975 2976 // Render a triangle to denote expanded/collapsed state 2977 void ImGui::RenderCollapseTriangle(ImVec2 p_min, bool is_open, float scale) 2978 { 2979 ImGuiContext& g = *GImGui; 2980 ImGuiWindow* window = GetCurrentWindow(); 2981 2982 const float h = g.FontSize * 1.00f; 2983 const float r = h * 0.40f * scale; 2984 ImVec2 center = p_min + ImVec2(h*0.50f, h*0.50f*scale); 2985 2986 ImVec2 a, b, c; 2987 if (is_open) 2988 { 2989 center.y -= r*0.25f; 2990 a = center + ImVec2(0,1)*r; 2991 b = center + ImVec2(-0.866f,-0.5f)*r; 2992 c = center + ImVec2(0.866f,-0.5f)*r; 2993 } 2994 else 2995 { 2996 a = center + ImVec2(1,0)*r; 2997 b = center + ImVec2(-0.500f,0.866f)*r; 2998 c = center + ImVec2(-0.500f,-0.866f)*r; 2999 } 3000 3001 window->DrawList->AddTriangleFilled(a, b, c, GetColorU32(ImGuiCol_Text)); 3002 } 3003 3004 void ImGui::RenderBullet(ImVec2 pos) 3005 { 3006 ImGuiWindow* window = GetCurrentWindow(); 3007 window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); 3008 } 3009 3010 void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col) 3011 { 3012 ImGuiContext& g = *GImGui; 3013 ImGuiWindow* window = GetCurrentWindow(); 3014 3015 ImVec2 a, b, c; 3016 float start_x = (float)(int)(g.FontSize * 0.307f + 0.5f); 3017 float rem_third = (float)(int)((g.FontSize - start_x) / 3.0f); 3018 a.x = pos.x + 0.5f + start_x; 3019 b.x = a.x + rem_third; 3020 c.x = a.x + rem_third * 3.0f; 3021 b.y = pos.y - 1.0f + (float)(int)(g.Font->Ascent * (g.FontSize / g.Font->FontSize) + 0.5f) + (float)(int)(g.Font->DisplayOffset.y); 3022 a.y = b.y - rem_third; 3023 c.y = b.y - rem_third * 2.0f; 3024 3025 window->DrawList->PathLineTo(a); 3026 window->DrawList->PathLineTo(b); 3027 window->DrawList->PathLineTo(c); 3028 window->DrawList->PathStroke(col, false); 3029 } 3030 3031 // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. 3032 // CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) 3033 ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) 3034 { 3035 ImGuiContext& g = *GImGui; 3036 3037 const char* text_display_end; 3038 if (hide_text_after_double_hash) 3039 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string 3040 else 3041 text_display_end = text_end; 3042 3043 ImFont* font = g.Font; 3044 const float font_size = g.FontSize; 3045 if (text == text_display_end) 3046 return ImVec2(0.0f, font_size); 3047 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); 3048 3049 // Cancel out character spacing for the last character of a line (it is baked into glyph->XAdvance field) 3050 const float font_scale = font_size / font->FontSize; 3051 const float character_spacing_x = 1.0f * font_scale; 3052 if (text_size.x > 0.0f) 3053 text_size.x -= character_spacing_x; 3054 text_size.x = (float)(int)(text_size.x + 0.95f); 3055 3056 return text_size; 3057 } 3058 3059 // Helper to calculate coarse clipping of large list of evenly sized items. 3060 // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. 3061 // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX 3062 void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) 3063 { 3064 ImGuiContext& g = *GImGui; 3065 ImGuiWindow* window = GetCurrentWindowRead(); 3066 if (g.LogEnabled) 3067 { 3068 // If logging is active, do not perform any clipping 3069 *out_items_display_start = 0; 3070 *out_items_display_end = items_count; 3071 return; 3072 } 3073 if (window->SkipItems) 3074 { 3075 *out_items_display_start = *out_items_display_end = 0; 3076 return; 3077 } 3078 3079 const ImVec2 pos = window->DC.CursorPos; 3080 int start = (int)((window->ClipRect.Min.y - pos.y) / items_height); 3081 int end = (int)((window->ClipRect.Max.y - pos.y) / items_height); 3082 start = ImClamp(start, 0, items_count); 3083 end = ImClamp(end + 1, start, items_count); 3084 *out_items_display_start = start; 3085 *out_items_display_end = end; 3086 } 3087 3088 // Find window given position, search front-to-back 3089 // 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. 3090 static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs) 3091 { 3092 ImGuiContext& g = *GImGui; 3093 for (int i = g.Windows.Size-1; i >= 0; i--) 3094 { 3095 ImGuiWindow* window = g.Windows[i]; 3096 if (!window->Active) 3097 continue; 3098 if (window->Flags & ImGuiWindowFlags_NoInputs) 3099 continue; 3100 if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0) 3101 continue; 3102 3103 // Using the clipped AABB so a child window will typically be clipped by its parent. 3104 ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding); 3105 if (bb.Contains(pos)) 3106 return window; 3107 } 3108 return NULL; 3109 } 3110 3111 // Test if mouse cursor is hovering given rectangle 3112 // NB- Rectangle is clipped by our current clip setting 3113 // NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) 3114 bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) 3115 { 3116 ImGuiContext& g = *GImGui; 3117 ImGuiWindow* window = GetCurrentWindowRead(); 3118 3119 // Clip 3120 ImRect rect_clipped(r_min, r_max); 3121 if (clip) 3122 rect_clipped.Clip(window->ClipRect); 3123 3124 // Expand for touch input 3125 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); 3126 return rect_for_touch.Contains(g.IO.MousePos); 3127 } 3128 3129 bool ImGui::IsMouseHoveringWindow() 3130 { 3131 ImGuiContext& g = *GImGui; 3132 return g.HoveredWindow == g.CurrentWindow; 3133 } 3134 3135 bool ImGui::IsMouseHoveringAnyWindow() 3136 { 3137 ImGuiContext& g = *GImGui; 3138 return g.HoveredWindow != NULL; 3139 } 3140 3141 bool ImGui::IsPosHoveringAnyWindow(const ImVec2& pos) 3142 { 3143 return FindHoveredWindow(pos, false) != NULL; 3144 } 3145 3146 static bool IsKeyPressedMap(ImGuiKey key, bool repeat) 3147 { 3148 const int key_index = GImGui->IO.KeyMap[key]; 3149 return ImGui::IsKeyPressed(key_index, repeat); 3150 } 3151 3152 int ImGui::GetKeyIndex(ImGuiKey imgui_key) 3153 { 3154 IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); 3155 return GImGui->IO.KeyMap[imgui_key]; 3156 } 3157 3158 // 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[]! 3159 bool ImGui::IsKeyDown(int user_key_index) 3160 { 3161 if (user_key_index < 0) return false; 3162 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); 3163 return GImGui->IO.KeysDown[user_key_index]; 3164 } 3165 3166 bool ImGui::IsKeyPressed(int user_key_index, bool repeat) 3167 { 3168 ImGuiContext& g = *GImGui; 3169 if (user_key_index < 0) return false; 3170 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); 3171 const float t = g.IO.KeysDownDuration[user_key_index]; 3172 if (t == 0.0f) 3173 return true; 3174 3175 if (repeat && t > g.IO.KeyRepeatDelay) 3176 { 3177 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; 3178 if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) 3179 return true; 3180 } 3181 return false; 3182 } 3183 3184 bool ImGui::IsKeyReleased(int user_key_index) 3185 { 3186 ImGuiContext& g = *GImGui; 3187 if (user_key_index < 0) return false; 3188 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); 3189 if (g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]) 3190 return true; 3191 return false; 3192 } 3193 3194 bool ImGui::IsMouseDown(int button) 3195 { 3196 ImGuiContext& g = *GImGui; 3197 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3198 return g.IO.MouseDown[button]; 3199 } 3200 3201 bool ImGui::IsMouseClicked(int button, bool repeat) 3202 { 3203 ImGuiContext& g = *GImGui; 3204 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3205 const float t = g.IO.MouseDownDuration[button]; 3206 if (t == 0.0f) 3207 return true; 3208 3209 if (repeat && t > g.IO.KeyRepeatDelay) 3210 { 3211 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; 3212 if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) 3213 return true; 3214 } 3215 3216 return false; 3217 } 3218 3219 bool ImGui::IsMouseReleased(int button) 3220 { 3221 ImGuiContext& g = *GImGui; 3222 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3223 return g.IO.MouseReleased[button]; 3224 } 3225 3226 bool ImGui::IsMouseDoubleClicked(int button) 3227 { 3228 ImGuiContext& g = *GImGui; 3229 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3230 return g.IO.MouseDoubleClicked[button]; 3231 } 3232 3233 bool ImGui::IsMouseDragging(int button, float lock_threshold) 3234 { 3235 ImGuiContext& g = *GImGui; 3236 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3237 if (!g.IO.MouseDown[button]) 3238 return false; 3239 if (lock_threshold < 0.0f) 3240 lock_threshold = g.IO.MouseDragThreshold; 3241 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; 3242 } 3243 3244 ImVec2 ImGui::GetMousePos() 3245 { 3246 return GImGui->IO.MousePos; 3247 } 3248 3249 // NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! 3250 ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() 3251 { 3252 ImGuiContext& g = *GImGui; 3253 if (g.CurrentPopupStack.Size > 0) 3254 return g.OpenPopupStack[g.CurrentPopupStack.Size-1].MousePosOnOpen; 3255 return g.IO.MousePos; 3256 } 3257 3258 ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) 3259 { 3260 ImGuiContext& g = *GImGui; 3261 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3262 if (lock_threshold < 0.0f) 3263 lock_threshold = g.IO.MouseDragThreshold; 3264 if (g.IO.MouseDown[button]) 3265 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) 3266 return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). 3267 return ImVec2(0.0f, 0.0f); 3268 } 3269 3270 void ImGui::ResetMouseDragDelta(int button) 3271 { 3272 ImGuiContext& g = *GImGui; 3273 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); 3274 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr 3275 g.IO.MouseClickedPos[button] = g.IO.MousePos; 3276 } 3277 3278 ImGuiMouseCursor ImGui::GetMouseCursor() 3279 { 3280 return GImGui->MouseCursor; 3281 } 3282 3283 void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) 3284 { 3285 GImGui->MouseCursor = cursor_type; 3286 } 3287 3288 void ImGui::CaptureKeyboardFromApp(bool capture) 3289 { 3290 GImGui->CaptureKeyboardNextFrame = capture ? 1 : 0; 3291 } 3292 3293 void ImGui::CaptureMouseFromApp(bool capture) 3294 { 3295 GImGui->CaptureMouseNextFrame = capture ? 1 : 0; 3296 } 3297 3298 bool ImGui::IsItemHovered() 3299 { 3300 ImGuiWindow* window = GetCurrentWindowRead(); 3301 return window->DC.LastItemHoveredAndUsable; 3302 } 3303 3304 bool ImGui::IsItemHoveredRect() 3305 { 3306 ImGuiWindow* window = GetCurrentWindowRead(); 3307 return window->DC.LastItemHoveredRect; 3308 } 3309 3310 bool ImGui::IsItemActive() 3311 { 3312 ImGuiContext& g = *GImGui; 3313 if (g.ActiveId) 3314 { 3315 ImGuiWindow* window = GetCurrentWindowRead(); 3316 return g.ActiveId == window->DC.LastItemId; 3317 } 3318 return false; 3319 } 3320 3321 bool ImGui::IsItemClicked(int mouse_button) 3322 { 3323 return IsMouseClicked(mouse_button) && IsItemHovered(); 3324 } 3325 3326 bool ImGui::IsAnyItemHovered() 3327 { 3328 return GImGui->HoveredId != 0 || GImGui->HoveredIdPreviousFrame != 0; 3329 } 3330 3331 bool ImGui::IsAnyItemActive() 3332 { 3333 return GImGui->ActiveId != 0; 3334 } 3335 3336 bool ImGui::IsItemVisible() 3337 { 3338 ImGuiWindow* window = GetCurrentWindowRead(); 3339 ImRect r(window->ClipRect); 3340 return r.Overlaps(window->DC.LastItemRect); 3341 } 3342 3343 // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. 3344 void ImGui::SetItemAllowOverlap() 3345 { 3346 ImGuiContext& g = *GImGui; 3347 if (g.HoveredId == g.CurrentWindow->DC.LastItemId) 3348 g.HoveredIdAllowOverlap = true; 3349 if (g.ActiveId == g.CurrentWindow->DC.LastItemId) 3350 g.ActiveIdAllowOverlap = true; 3351 } 3352 3353 ImVec2 ImGui::GetItemRectMin() 3354 { 3355 ImGuiWindow* window = GetCurrentWindowRead(); 3356 return window->DC.LastItemRect.Min; 3357 } 3358 3359 ImVec2 ImGui::GetItemRectMax() 3360 { 3361 ImGuiWindow* window = GetCurrentWindowRead(); 3362 return window->DC.LastItemRect.Max; 3363 } 3364 3365 ImVec2 ImGui::GetItemRectSize() 3366 { 3367 ImGuiWindow* window = GetCurrentWindowRead(); 3368 return window->DC.LastItemRect.GetSize(); 3369 } 3370 3371 ImVec2 ImGui::CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge, float outward) 3372 { 3373 ImGuiWindow* window = GetCurrentWindowRead(); 3374 ImRect rect = window->DC.LastItemRect; 3375 rect.Expand(outward); 3376 return rect.GetClosestPoint(pos, on_edge); 3377 } 3378 3379 // Tooltip is stored and turned into a BeginTooltip()/EndTooltip() sequence at the end of the frame. Each call override previous value. 3380 void ImGui::SetTooltipV(const char* fmt, va_list args) 3381 { 3382 ImGuiContext& g = *GImGui; 3383 ImFormatStringV(g.Tooltip, IM_ARRAYSIZE(g.Tooltip), fmt, args); 3384 } 3385 3386 void ImGui::SetTooltip(const char* fmt, ...) 3387 { 3388 va_list args; 3389 va_start(args, fmt); 3390 SetTooltipV(fmt, args); 3391 va_end(args); 3392 } 3393 3394 static ImRect GetVisibleRect() 3395 { 3396 ImGuiContext& g = *GImGui; 3397 if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) 3398 return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); 3399 return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); 3400 } 3401 3402 void ImGui::BeginTooltip() 3403 { 3404 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; 3405 ImGui::Begin("##Tooltip", NULL, flags); 3406 } 3407 3408 void ImGui::EndTooltip() 3409 { 3410 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls 3411 ImGui::End(); 3412 } 3413 3414 static bool IsPopupOpen(ImGuiID id) 3415 { 3416 ImGuiContext& g = *GImGui; 3417 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; 3418 } 3419 3420 // Mark popup as open (toggle toward open state). 3421 // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. 3422 // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). 3423 // One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) 3424 void ImGui::OpenPopupEx(const char* str_id, bool reopen_existing) 3425 { 3426 ImGuiContext& g = *GImGui; 3427 ImGuiWindow* window = g.CurrentWindow; 3428 ImGuiID id = window->GetID(str_id); 3429 int current_stack_size = g.CurrentPopupStack.Size; 3430 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) 3431 if (g.OpenPopupStack.Size < current_stack_size + 1) 3432 g.OpenPopupStack.push_back(popup_ref); 3433 else if (reopen_existing || g.OpenPopupStack[current_stack_size].PopupId != id) 3434 { 3435 g.OpenPopupStack.resize(current_stack_size+1); 3436 g.OpenPopupStack[current_stack_size] = popup_ref; 3437 } 3438 } 3439 3440 void ImGui::OpenPopup(const char* str_id) 3441 { 3442 ImGui::OpenPopupEx(str_id, false); 3443 } 3444 3445 static void CloseInactivePopups() 3446 { 3447 ImGuiContext& g = *GImGui; 3448 if (g.OpenPopupStack.empty()) 3449 return; 3450 3451 // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. 3452 // Don't close our own child popup windows 3453 int n = 0; 3454 if (g.FocusedWindow) 3455 { 3456 for (n = 0; n < g.OpenPopupStack.Size; n++) 3457 { 3458 ImGuiPopupRef& popup = g.OpenPopupStack[n]; 3459 if (!popup.Window) 3460 continue; 3461 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); 3462 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) 3463 continue; 3464 3465 bool has_focus = false; 3466 for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) 3467 has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == g.FocusedWindow->RootWindow); 3468 if (!has_focus) 3469 break; 3470 } 3471 } 3472 if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a useful breakpoint on the line below 3473 g.OpenPopupStack.resize(n); 3474 } 3475 3476 static ImGuiWindow* GetFrontMostModalRootWindow() 3477 { 3478 ImGuiContext& g = *GImGui; 3479 for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) 3480 if (ImGuiWindow* front_most_popup = g.OpenPopupStack.Data[n].Window) 3481 if (front_most_popup->Flags & ImGuiWindowFlags_Modal) 3482 return front_most_popup; 3483 return NULL; 3484 } 3485 3486 static void ClosePopupToLevel(int remaining) 3487 { 3488 ImGuiContext& g = *GImGui; 3489 if (remaining > 0) 3490 ImGui::FocusWindow(g.OpenPopupStack[remaining-1].Window); 3491 else 3492 ImGui::FocusWindow(g.OpenPopupStack[0].ParentWindow); 3493 g.OpenPopupStack.resize(remaining); 3494 } 3495 3496 static void ClosePopup(ImGuiID id) 3497 { 3498 if (!IsPopupOpen(id)) 3499 return; 3500 ImGuiContext& g = *GImGui; 3501 ClosePopupToLevel(g.OpenPopupStack.Size - 1); 3502 } 3503 3504 // Close the popup we have begin-ed into. 3505 void ImGui::CloseCurrentPopup() 3506 { 3507 ImGuiContext& g = *GImGui; 3508 int popup_idx = g.CurrentPopupStack.Size - 1; 3509 if (popup_idx < 0 || popup_idx > g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) 3510 return; 3511 while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) 3512 popup_idx--; 3513 ClosePopupToLevel(popup_idx); 3514 } 3515 3516 static inline void ClearSetNextWindowData() 3517 { 3518 ImGuiContext& g = *GImGui; 3519 g.SetNextWindowPosCond = g.SetNextWindowSizeCond = g.SetNextWindowContentSizeCond = g.SetNextWindowCollapsedCond = 0; 3520 g.SetNextWindowSizeConstraint = g.SetNextWindowFocus = false; 3521 } 3522 3523 static bool BeginPopupEx(const char* str_id, ImGuiWindowFlags extra_flags) 3524 { 3525 ImGuiContext& g = *GImGui; 3526 ImGuiWindow* window = g.CurrentWindow; 3527 const ImGuiID id = window->GetID(str_id); 3528 if (!IsPopupOpen(id)) 3529 { 3530 ClearSetNextWindowData(); // We behave like Begin() and need to consume those values 3531 return false; 3532 } 3533 3534 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 3535 ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; 3536 3537 char name[20]; 3538 if (flags & ImGuiWindowFlags_ChildMenu) 3539 ImFormatString(name, IM_ARRAYSIZE(name), "##menu_%d", g.CurrentPopupStack.Size); // Recycle windows based on depth 3540 else 3541 ImFormatString(name, IM_ARRAYSIZE(name), "##popup_%08x", id); // Not recycling, so we can close/open during the same frame 3542 3543 bool is_open = ImGui::Begin(name, NULL, flags); 3544 if (!(window->Flags & ImGuiWindowFlags_ShowBorders)) 3545 g.CurrentWindow->Flags &= ~ImGuiWindowFlags_ShowBorders; 3546 if (!is_open) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) 3547 ImGui::EndPopup(); 3548 3549 return is_open; 3550 } 3551 3552 bool ImGui::BeginPopup(const char* str_id) 3553 { 3554 if (GImGui->OpenPopupStack.Size <= GImGui->CurrentPopupStack.Size) // Early out for performance 3555 { 3556 ClearSetNextWindowData(); // We behave like Begin() and need to consume those values 3557 return false; 3558 } 3559 return BeginPopupEx(str_id, ImGuiWindowFlags_ShowBorders); 3560 } 3561 3562 bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags extra_flags) 3563 { 3564 ImGuiContext& g = *GImGui; 3565 ImGuiWindow* window = g.CurrentWindow; 3566 const ImGuiID id = window->GetID(name); 3567 if (!IsPopupOpen(id)) 3568 { 3569 ClearSetNextWindowData(); // We behave like Begin() and need to consume those values 3570 return false; 3571 } 3572 3573 ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoSavedSettings; 3574 bool is_open = ImGui::Begin(name, p_open, flags); 3575 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) 3576 { 3577 ImGui::EndPopup(); 3578 if (is_open) 3579 ClosePopup(id); 3580 return false; 3581 } 3582 3583 return is_open; 3584 } 3585 3586 void ImGui::EndPopup() 3587 { 3588 ImGuiWindow* window = GetCurrentWindow(); 3589 IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls 3590 IM_ASSERT(GImGui->CurrentPopupStack.Size > 0); 3591 ImGui::End(); 3592 if (!(window->Flags & ImGuiWindowFlags_Modal)) 3593 ImGui::PopStyleVar(); 3594 } 3595 3596 // This is a helper to handle the most simple case of associating one named popup to one given widget. 3597 // 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 3598 // this yourself so you can store data relative to the widget that opened the popup instead of choosing different popup identifiers. 3599 // 2. If you want right-clicking on the same item to reopen the popup at new location, use the same code replacing IsItemHovered() with IsItemHoveredRect() 3600 // and passing true to the OpenPopupEx(). 3601 // 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 3602 // the item isn't interactable (because it is blocked by the active popup) may useful in some situation when e.g. large canvas as one item, content of menu 3603 // driven by click position. 3604 bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) 3605 { 3606 if (IsItemHovered() && IsMouseClicked(mouse_button)) 3607 OpenPopupEx(str_id, false); 3608 return BeginPopup(str_id); 3609 } 3610 3611 bool ImGui::BeginPopupContextWindow(bool also_over_items, const char* str_id, int mouse_button) 3612 { 3613 if (!str_id) str_id = "window_context_menu"; 3614 if (IsMouseHoveringWindow() && IsMouseClicked(mouse_button)) 3615 if (also_over_items || !IsAnyItemHovered()) 3616 OpenPopupEx(str_id, true); 3617 return BeginPopup(str_id); 3618 } 3619 3620 bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) 3621 { 3622 if (!str_id) str_id = "void_context_menu"; 3623 if (!IsMouseHoveringAnyWindow() && IsMouseClicked(mouse_button)) 3624 OpenPopupEx(str_id, true); 3625 return BeginPopup(str_id); 3626 } 3627 3628 static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) 3629 { 3630 ImGuiWindow* window = ImGui::GetCurrentWindow(); 3631 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; 3632 3633 const ImVec2 content_avail = ImGui::GetContentRegionAvail(); 3634 ImVec2 size = ImFloor(size_arg); 3635 if (size.x <= 0.0f) 3636 { 3637 if (size.x == 0.0f) 3638 flags |= ImGuiWindowFlags_ChildWindowAutoFitX; 3639 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) 3640 } 3641 if (size.y <= 0.0f) 3642 { 3643 if (size.y == 0.0f) 3644 flags |= ImGuiWindowFlags_ChildWindowAutoFitY; 3645 size.y = ImMax(content_avail.y, 4.0f) - fabsf(size.y); 3646 } 3647 if (border) 3648 flags |= ImGuiWindowFlags_ShowBorders; 3649 flags |= extra_flags; 3650 3651 char title[256]; 3652 if (name) 3653 ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s.%08X", window->Name, name, id); 3654 else 3655 ImFormatString(title, IM_ARRAYSIZE(title), "%s.%08X", window->Name, id); 3656 3657 bool ret = ImGui::Begin(title, NULL, size, -1.0f, flags); 3658 3659 if (!(window->Flags & ImGuiWindowFlags_ShowBorders)) 3660 ImGui::GetCurrentWindow()->Flags &= ~ImGuiWindowFlags_ShowBorders; 3661 3662 return ret; 3663 } 3664 3665 bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) 3666 { 3667 ImGuiWindow* window = GetCurrentWindow(); 3668 return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); 3669 } 3670 3671 bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) 3672 { 3673 return BeginChildEx(NULL, id, size_arg, border, extra_flags); 3674 } 3675 3676 void ImGui::EndChild() 3677 { 3678 ImGuiWindow* window = GetCurrentWindow(); 3679 3680 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss 3681 if ((window->Flags & ImGuiWindowFlags_ComboBox) || window->BeginCount > 1) 3682 { 3683 ImGui::End(); 3684 } 3685 else 3686 { 3687 // 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. 3688 ImVec2 sz = GetWindowSize(); 3689 if (window->Flags & ImGuiWindowFlags_ChildWindowAutoFitX) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f 3690 sz.x = ImMax(4.0f, sz.x); 3691 if (window->Flags & ImGuiWindowFlags_ChildWindowAutoFitY) 3692 sz.y = ImMax(4.0f, sz.y); 3693 3694 ImGui::End(); 3695 3696 window = GetCurrentWindow(); 3697 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + sz); 3698 ItemSize(sz); 3699 ItemAdd(bb, NULL); 3700 } 3701 } 3702 3703 // Helper to create a child window / scrolling region that looks like a normal widget frame. 3704 bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) 3705 { 3706 ImGuiContext& g = *GImGui; 3707 const ImGuiStyle& style = g.Style; 3708 ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, style.Colors[ImGuiCol_FrameBg]); 3709 ImGui::PushStyleVar(ImGuiStyleVar_ChildWindowRounding, style.FrameRounding); 3710 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); 3711 return ImGui::BeginChild(id, size, (g.CurrentWindow->Flags & ImGuiWindowFlags_ShowBorders) ? true : false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); 3712 } 3713 3714 void ImGui::EndChildFrame() 3715 { 3716 ImGui::EndChild(); 3717 ImGui::PopStyleVar(2); 3718 ImGui::PopStyleColor(); 3719 } 3720 3721 // Save and compare stack sizes on Begin()/End() to detect usage errors 3722 static void CheckStacksSize(ImGuiWindow* window, bool write) 3723 { 3724 // 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) 3725 ImGuiContext& g = *GImGui; 3726 int* p_backup = &window->DC.StackSizesBackup[0]; 3727 { 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() 3728 { 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() 3729 { 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() 3730 { 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() 3731 { 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() 3732 { 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() 3733 IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); 3734 } 3735 3736 static ImVec2 FindBestPopupWindowPos(const ImVec2& base_pos, const ImVec2& size, int* last_dir, const ImRect& r_inner) 3737 { 3738 const ImGuiStyle& style = GImGui->Style; 3739 3740 // Clamp into visible area while not overlapping the cursor. Safety padding is optional if our popup size won't fit without it. 3741 ImVec2 safe_padding = style.DisplaySafeAreaPadding; 3742 ImRect r_outer(GetVisibleRect()); 3743 r_outer.Reduce(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)); 3744 ImVec2 base_pos_clamped = ImClamp(base_pos, r_outer.Min, r_outer.Max - size); 3745 3746 for (int n = (*last_dir != -1) ? -1 : 0; n < 4; n++) // Last, Right, down, up, left. (Favor last used direction). 3747 { 3748 const int dir = (n == -1) ? *last_dir : n; 3749 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); 3750 if (rect.GetWidth() < size.x || rect.GetHeight() < size.y) 3751 continue; 3752 *last_dir = dir; 3753 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); 3754 } 3755 3756 // Fallback, try to keep within display 3757 *last_dir = -1; 3758 ImVec2 pos = base_pos; 3759 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); 3760 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); 3761 return pos; 3762 } 3763 3764 ImGuiWindow* ImGui::FindWindowByName(const char* name) 3765 { 3766 // FIXME-OPT: Store sorted hashes -> pointers so we can do a bissection in a contiguous block 3767 ImGuiContext& g = *GImGui; 3768 ImGuiID id = ImHash(name, 0); 3769 for (int i = 0; i < g.Windows.Size; i++) 3770 if (g.Windows[i]->ID == id) 3771 return g.Windows[i]; 3772 return NULL; 3773 } 3774 3775 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) 3776 { 3777 ImGuiContext& g = *GImGui; 3778 3779 // Create window the first time 3780 ImGuiWindow* window = (ImGuiWindow*)ImGui::MemAlloc(sizeof(ImGuiWindow)); 3781 IM_PLACEMENT_NEW(window) ImGuiWindow(name); 3782 window->Flags = flags; 3783 3784 if (flags & ImGuiWindowFlags_NoSavedSettings) 3785 { 3786 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. 3787 window->Size = window->SizeFull = size; 3788 } 3789 else 3790 { 3791 // Retrieve settings from .ini file 3792 // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. 3793 window->PosFloat = ImVec2(60, 60); 3794 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); 3795 3796 ImGuiIniData* settings = FindWindowSettings(name); 3797 if (!settings) 3798 { 3799 settings = AddWindowSettings(name); 3800 } 3801 else 3802 { 3803 window->SetWindowPosAllowFlags &= ~ImGuiSetCond_FirstUseEver; 3804 window->SetWindowSizeAllowFlags &= ~ImGuiSetCond_FirstUseEver; 3805 window->SetWindowCollapsedAllowFlags &= ~ImGuiSetCond_FirstUseEver; 3806 } 3807 3808 if (settings->Pos.x != FLT_MAX) 3809 { 3810 window->PosFloat = settings->Pos; 3811 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); 3812 window->Collapsed = settings->Collapsed; 3813 } 3814 3815 if (ImLengthSqr(settings->Size) > 0.00001f && !(flags & ImGuiWindowFlags_NoResize)) 3816 size = settings->Size; 3817 window->Size = window->SizeFull = size; 3818 } 3819 3820 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) 3821 { 3822 window->AutoFitFramesX = window->AutoFitFramesY = 2; 3823 window->AutoFitOnlyGrows = false; 3824 } 3825 else 3826 { 3827 if (window->Size.x <= 0.0f) 3828 window->AutoFitFramesX = 2; 3829 if (window->Size.y <= 0.0f) 3830 window->AutoFitFramesY = 2; 3831 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); 3832 } 3833 3834 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) 3835 g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once 3836 else 3837 g.Windows.push_back(window); 3838 return window; 3839 } 3840 3841 static void ApplySizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) 3842 { 3843 ImGuiContext& g = *GImGui; 3844 if (g.SetNextWindowSizeConstraint) 3845 { 3846 // Using -1,-1 on either X/Y axis to preserve the current size. 3847 ImRect cr = g.SetNextWindowSizeConstraintRect; 3848 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; 3849 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; 3850 if (g.SetNextWindowSizeConstraintCallback) 3851 { 3852 ImGuiSizeConstraintCallbackData data; 3853 data.UserData = g.SetNextWindowSizeConstraintCallbackUserData; 3854 data.Pos = window->Pos; 3855 data.CurrentSize = window->SizeFull; 3856 data.DesiredSize = new_size; 3857 g.SetNextWindowSizeConstraintCallback(&data); 3858 new_size = data.DesiredSize; 3859 } 3860 } 3861 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) 3862 new_size = ImMax(new_size, g.Style.WindowMinSize); 3863 window->SizeFull = new_size; 3864 } 3865 3866 // Push a new ImGui window to add widgets to. 3867 // - 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. 3868 // - Begin/End can be called multiple times during the frame with the same window name to append content. 3869 // - '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. 3870 // - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). 3871 // 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. 3872 // - 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. 3873 // - 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. 3874 // - Passing non-zero 'size' is roughly equivalent to calling SetNextWindowSize(size, ImGuiSetCond_FirstUseEver) prior to calling Begin(). 3875 bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) 3876 { 3877 return ImGui::Begin(name, p_open, ImVec2(0.f, 0.f), -1.0f, flags); 3878 } 3879 3880 bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha, ImGuiWindowFlags flags) 3881 { 3882 ImGuiContext& g = *GImGui; 3883 const ImGuiStyle& style = g.Style; 3884 IM_ASSERT(name != NULL); // Window name required 3885 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() 3886 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet 3887 3888 if (flags & ImGuiWindowFlags_NoInputs) 3889 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; 3890 3891 // Find or create 3892 bool window_is_new = false; 3893 ImGuiWindow* window = FindWindowByName(name); 3894 if (!window) 3895 { 3896 window = CreateNewWindow(name, size_on_first_use, flags); 3897 window_is_new = true; 3898 } 3899 3900 const int current_frame = ImGui::GetFrameCount(); 3901 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); 3902 if (first_begin_of_the_frame) 3903 window->Flags = (ImGuiWindowFlags)flags; 3904 else 3905 flags = window->Flags; 3906 3907 // Add to stack 3908 ImGuiWindow* parent_window = !g.CurrentWindowStack.empty() ? g.CurrentWindowStack.back() : NULL; 3909 g.CurrentWindowStack.push_back(window); 3910 SetCurrentWindow(window); 3911 CheckStacksSize(window, true); 3912 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); 3913 3914 bool window_was_active = (window->LastFrameActive == current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on 3915 if (flags & ImGuiWindowFlags_Popup) 3916 { 3917 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; 3918 window_was_active &= (window->PopupId == popup_ref.PopupId); 3919 window_was_active &= (window == popup_ref.Window); 3920 popup_ref.Window = window; 3921 g.CurrentPopupStack.push_back(popup_ref); 3922 window->PopupId = popup_ref.PopupId; 3923 } 3924 3925 const bool window_appearing_after_being_hidden = (window->HiddenFrames == 1); 3926 3927 // Process SetNextWindow***() calls 3928 bool window_pos_set_by_api = false, window_size_set_by_api = false; 3929 if (g.SetNextWindowPosCond) 3930 { 3931 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. 3932 if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowPosAllowFlags |= ImGuiSetCond_Appearing; 3933 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) != 0; 3934 if (window_pos_set_by_api && ImLengthSqr(g.SetNextWindowPosVal - ImVec2(-FLT_MAX,-FLT_MAX)) < 0.001f) 3935 { 3936 window->SetWindowPosCenterWanted = true; // May be processed on the next frame if this is our first frame and we are measuring size 3937 window->SetWindowPosAllowFlags &= ~(ImGuiSetCond_Once | ImGuiSetCond_FirstUseEver | ImGuiSetCond_Appearing); 3938 } 3939 else 3940 { 3941 SetWindowPos(window, g.SetNextWindowPosVal, g.SetNextWindowPosCond); 3942 } 3943 window->DC.CursorPos = backup_cursor_pos; 3944 g.SetNextWindowPosCond = 0; 3945 } 3946 if (g.SetNextWindowSizeCond) 3947 { 3948 if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowSizeAllowFlags |= ImGuiSetCond_Appearing; 3949 window_size_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0; 3950 SetWindowSize(window, g.SetNextWindowSizeVal, g.SetNextWindowSizeCond); 3951 g.SetNextWindowSizeCond = 0; 3952 } 3953 if (g.SetNextWindowContentSizeCond) 3954 { 3955 window->SizeContentsExplicit = g.SetNextWindowContentSizeVal; 3956 g.SetNextWindowContentSizeCond = 0; 3957 } 3958 else if (first_begin_of_the_frame) 3959 { 3960 window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); 3961 } 3962 if (g.SetNextWindowCollapsedCond) 3963 { 3964 if (!window_was_active || window_appearing_after_being_hidden) window->SetWindowCollapsedAllowFlags |= ImGuiSetCond_Appearing; 3965 SetWindowCollapsed(window, g.SetNextWindowCollapsedVal, g.SetNextWindowCollapsedCond); 3966 g.SetNextWindowCollapsedCond = 0; 3967 } 3968 if (g.SetNextWindowFocus) 3969 { 3970 ImGui::SetWindowFocus(); 3971 g.SetNextWindowFocus = false; 3972 } 3973 3974 // Update known root window (if we are a child window, otherwise window == window->RootWindow) 3975 int root_idx, root_non_popup_idx; 3976 for (root_idx = g.CurrentWindowStack.Size - 1; root_idx > 0; root_idx--) 3977 if (!(g.CurrentWindowStack[root_idx]->Flags & ImGuiWindowFlags_ChildWindow)) 3978 break; 3979 for (root_non_popup_idx = root_idx; root_non_popup_idx > 0; root_non_popup_idx--) 3980 if (!(g.CurrentWindowStack[root_non_popup_idx]->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) 3981 break; 3982 window->ParentWindow = parent_window; 3983 window->RootWindow = g.CurrentWindowStack[root_idx]; 3984 window->RootNonPopupWindow = g.CurrentWindowStack[root_non_popup_idx]; // This is merely for displaying the TitleBgActive color. 3985 3986 // When reusing window again multiple times a frame, just append content (don't need to setup again) 3987 if (first_begin_of_the_frame) 3988 { 3989 window->Active = true; 3990 window->IndexWithinParent = 0; 3991 window->BeginCount = 0; 3992 window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); 3993 window->LastFrameActive = current_frame; 3994 window->IDStack.resize(1); 3995 3996 // Clear draw list, setup texture, outer clipping rectangle 3997 window->DrawList->Clear(); 3998 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); 3999 ImRect fullscreen_rect(GetVisibleRect()); 4000 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_ComboBox|ImGuiWindowFlags_Popup))) 4001 PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); 4002 else 4003 PushClipRect(fullscreen_rect.Min, fullscreen_rect.Max, true); 4004 4005 if (!window_was_active) 4006 { 4007 // Popup first latch mouse position, will position itself when it appears next frame 4008 window->AutoPosLastDirection = -1; 4009 if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) 4010 window->PosFloat = g.IO.MousePos; 4011 } 4012 4013 // Collapse window by double-clicking on title bar 4014 // 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 4015 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) 4016 { 4017 ImRect title_bar_rect = window->TitleBarRect(); 4018 if (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) 4019 { 4020 window->Collapsed = !window->Collapsed; 4021 if (!(flags & ImGuiWindowFlags_NoSavedSettings)) 4022 MarkIniSettingsDirty(); 4023 FocusWindow(window); 4024 } 4025 } 4026 else 4027 { 4028 window->Collapsed = false; 4029 } 4030 4031 // SIZE 4032 4033 // Save contents size from last frame for auto-fitting (unless explicitly specified) 4034 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)); 4035 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)); 4036 4037 // Hide popup/tooltip window when first appearing while we measure size (because we recycle them) 4038 if (window->HiddenFrames > 0) 4039 window->HiddenFrames--; 4040 if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && !window_was_active) 4041 { 4042 window->HiddenFrames = 1; 4043 if (flags & ImGuiWindowFlags_AlwaysAutoResize) 4044 { 4045 if (!window_size_set_by_api) 4046 window->Size = window->SizeFull = ImVec2(0.f, 0.f); 4047 window->SizeContents = ImVec2(0.f, 0.f); 4048 } 4049 } 4050 4051 // Lock window padding so that altering the ShowBorders flag for children doesn't have side-effects. 4052 window->WindowPadding = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_ComboBox | ImGuiWindowFlags_Popup))) ? ImVec2(0,0) : style.WindowPadding; 4053 4054 // Calculate auto-fit size 4055 ImVec2 size_auto_fit; 4056 if ((flags & ImGuiWindowFlags_Tooltip) != 0) 4057 { 4058 // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. 4059 size_auto_fit = window->SizeContents + window->WindowPadding - ImVec2(0.0f, style.ItemSpacing.y); 4060 } 4061 else 4062 { 4063 size_auto_fit = ImClamp(window->SizeContents + window->WindowPadding, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); 4064 4065 // 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. 4066 if (size_auto_fit.x < window->SizeContents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) 4067 size_auto_fit.y += style.ScrollbarSize; 4068 if (size_auto_fit.y < window->SizeContents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) 4069 size_auto_fit.x += style.ScrollbarSize; 4070 size_auto_fit.y = ImMax(size_auto_fit.y - style.ItemSpacing.y, 0.0f); 4071 } 4072 4073 // Handle automatic resize 4074 if (window->Collapsed) 4075 { 4076 // We still process initial auto-fit on collapsed windows to get a window width, 4077 // But otherwise we don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. 4078 if (window->AutoFitFramesX > 0) 4079 window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; 4080 if (window->AutoFitFramesY > 0) 4081 window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; 4082 } 4083 else 4084 { 4085 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window_size_set_by_api) 4086 { 4087 window->SizeFull = size_auto_fit; 4088 } 4089 else if ((window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) && !window_size_set_by_api) 4090 { 4091 // Auto-fit only grows during the first few frames 4092 if (window->AutoFitFramesX > 0) 4093 window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; 4094 if (window->AutoFitFramesY > 0) 4095 window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; 4096 if (!(flags & ImGuiWindowFlags_NoSavedSettings)) 4097 MarkIniSettingsDirty(); 4098 } 4099 } 4100 4101 // Apply minimum/maximum window size constraints and final size 4102 ApplySizeFullWithConstraint(window, window->SizeFull); 4103 window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull; 4104 4105 // POSITION 4106 4107 // Position child window 4108 if (flags & ImGuiWindowFlags_ChildWindow) 4109 { 4110 window->IndexWithinParent = parent_window->DC.ChildWindows.Size; 4111 parent_window->DC.ChildWindows.push_back(window); 4112 } 4113 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) 4114 { 4115 window->Pos = window->PosFloat = parent_window->DC.CursorPos; 4116 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(). 4117 } 4118 4119 bool window_pos_center = false; 4120 window_pos_center |= (window->SetWindowPosCenterWanted && window->HiddenFrames == 0); 4121 window_pos_center |= ((flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api && window_appearing_after_being_hidden); 4122 if (window_pos_center) 4123 { 4124 // Center (any sort of window) 4125 SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, fullscreen_rect.GetCenter() - window->SizeFull * 0.5f), 0); 4126 } 4127 else if (flags & ImGuiWindowFlags_ChildMenu) 4128 { 4129 // 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. 4130 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. 4131 IM_ASSERT(window_pos_set_by_api); 4132 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). 4133 ImRect rect_to_avoid; 4134 if (parent_window->DC.MenuBarAppending) 4135 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()); 4136 else 4137 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); 4138 window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); 4139 } 4140 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_appearing_after_being_hidden) 4141 { 4142 ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); 4143 window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); 4144 } 4145 4146 // Position tooltip (always follows mouse) 4147 if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api) 4148 { 4149 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? 4150 window->PosFloat = FindBestPopupWindowPos(g.IO.MousePos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); 4151 if (window->AutoPosLastDirection == -1) 4152 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. 4153 } 4154 4155 // Clamp position so it stays visible 4156 if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) 4157 { 4158 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. 4159 { 4160 ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); 4161 window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size; 4162 window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding); 4163 } 4164 } 4165 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); 4166 4167 // Default item width. Make it proportional to window size if window manually resizes 4168 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) 4169 window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); 4170 else 4171 window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); 4172 4173 // Prepare for focus requests 4174 window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); 4175 window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); 4176 window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; 4177 window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; 4178 4179 // Apply scrolling 4180 if (window->ScrollTarget.x < FLT_MAX) 4181 { 4182 window->Scroll.x = window->ScrollTarget.x; 4183 window->ScrollTarget.x = FLT_MAX; 4184 } 4185 if (window->ScrollTarget.y < FLT_MAX) 4186 { 4187 float center_ratio = window->ScrollTargetCenterRatio.y; 4188 window->Scroll.y = window->ScrollTarget.y - ((1.0f - center_ratio) * (window->TitleBarHeight() + window->MenuBarHeight())) - (center_ratio * window->SizeFull.y); 4189 window->ScrollTarget.y = FLT_MAX; 4190 } 4191 window->Scroll = ImMax(window->Scroll, ImVec2(0.0f, 0.0f)); 4192 if (!window->Collapsed && !window->SkipItems) 4193 window->Scroll = ImMin(window->Scroll, ImMax(ImVec2(0.0f, 0.0f), window->SizeContents - window->SizeFull + window->ScrollbarSizes)); 4194 4195 // Modal window darkens what is behind them 4196 if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) 4197 window->DrawList->AddRectFilled(fullscreen_rect.Min, fullscreen_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio)); 4198 4199 // Draw window + handle manual resize 4200 ImRect title_bar_rect = window->TitleBarRect(); 4201 const float window_rounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding; 4202 if (window->Collapsed) 4203 { 4204 // Draw title bar only 4205 RenderFrame(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding); 4206 } 4207 else 4208 { 4209 ImU32 resize_col = 0; 4210 const float resize_corner_size = ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f); 4211 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && !(flags & ImGuiWindowFlags_NoResize)) 4212 { 4213 // Manual resize 4214 const ImVec2 br = window->Rect().GetBR(); 4215 const ImRect resize_rect(br - ImVec2(resize_corner_size * 0.75f, resize_corner_size * 0.75f), br); 4216 const ImGuiID resize_id = window->GetID("#RESIZE"); 4217 bool hovered, held; 4218 ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds); 4219 resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); 4220 4221 if (hovered || held) 4222 g.MouseCursor = ImGuiMouseCursor_ResizeNWSE; 4223 4224 if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0]) 4225 { 4226 // Manual auto-fit when double-clicking 4227 ApplySizeFullWithConstraint(window, size_auto_fit); 4228 if (!(flags & ImGuiWindowFlags_NoSavedSettings)) 4229 MarkIniSettingsDirty(); 4230 ClearActiveID(); 4231 } 4232 else if (held) 4233 { 4234 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position 4235 ApplySizeFullWithConstraint(window, (g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize()) - window->Pos); 4236 if (!(flags & ImGuiWindowFlags_NoSavedSettings)) 4237 MarkIniSettingsDirty(); 4238 } 4239 4240 window->Size = window->SizeFull; 4241 title_bar_rect = window->TitleBarRect(); 4242 } 4243 4244 // Scrollbars 4245 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > window->Size.y + style.ItemSpacing.y) && !(flags & ImGuiWindowFlags_NoScrollbar)); 4246 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)); 4247 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); 4248 window->BorderSize = (flags & ImGuiWindowFlags_ShowBorders) ? 1.0f : 0.0f; 4249 4250 // Window background, Default Alpha 4251 ImGuiCol bg_color_idx = ImGuiCol_WindowBg; 4252 if ((flags & ImGuiWindowFlags_ComboBox) != 0) 4253 bg_color_idx = ImGuiCol_ComboBg; 4254 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 || (flags & ImGuiWindowFlags_Popup) != 0) 4255 bg_color_idx = ImGuiCol_PopupBg; 4256 else if ((flags & ImGuiWindowFlags_ChildWindow) != 0) 4257 bg_color_idx = ImGuiCol_ChildWindowBg; 4258 ImVec4 bg_color = style.Colors[bg_color_idx]; 4259 if (bg_alpha >= 0.0f) 4260 bg_color.w = bg_alpha; 4261 bg_color.w *= style.Alpha; 4262 if (bg_color.w > 0.0f) 4263 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); 4264 4265 // Title bar 4266 if (!(flags & ImGuiWindowFlags_NoTitleBar)) 4267 window->DrawList->AddRectFilled(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32((g.FocusedWindow && window->RootNonPopupWindow == g.FocusedWindow->RootNonPopupWindow) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImGuiCorner_TopLeft|ImGuiCorner_TopRight); 4268 4269 // Menu bar 4270 if (flags & ImGuiWindowFlags_MenuBar) 4271 { 4272 ImRect menu_bar_rect = window->MenuBarRect(); 4273 if (flags & ImGuiWindowFlags_ShowBorders) 4274 window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border)); 4275 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); 4276 } 4277 4278 // Scrollbars 4279 if (window->ScrollbarX) 4280 Scrollbar(window, true); 4281 if (window->ScrollbarY) 4282 Scrollbar(window, false); 4283 4284 // Render resize grip 4285 // (after the input handling so we don't have a frame of latency) 4286 if (!(flags & ImGuiWindowFlags_NoResize)) 4287 { 4288 const ImVec2 br = window->Rect().GetBR(); 4289 window->DrawList->PathLineTo(br + ImVec2(-resize_corner_size, -window->BorderSize)); 4290 window->DrawList->PathLineTo(br + ImVec2(-window->BorderSize, -resize_corner_size)); 4291 window->DrawList->PathArcToFast(ImVec2(br.x - window_rounding - window->BorderSize, br.y - window_rounding - window->BorderSize), window_rounding, 0, 3); 4292 window->DrawList->PathFillConvex(resize_col); 4293 } 4294 4295 // Borders 4296 if (flags & ImGuiWindowFlags_ShowBorders) 4297 { 4298 window->DrawList->AddRect(window->Pos+ImVec2(1,1), window->Pos+window->Size+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), window_rounding); 4299 window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding); 4300 if (!(flags & ImGuiWindowFlags_NoTitleBar)) 4301 window->DrawList->AddLine(title_bar_rect.GetBL()+ImVec2(1,0), title_bar_rect.GetBR()-ImVec2(1,0), GetColorU32(ImGuiCol_Border)); 4302 } 4303 } 4304 4305 // Update ContentsRegionMax. All the variable it depends on are set above in this function. 4306 window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; 4307 window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); 4308 window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); 4309 window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); 4310 4311 // Setup drawing context 4312 window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x; 4313 window->DC.GroupOffsetX = 0.0f; 4314 window->DC.ColumnsOffsetX = 0.0f; 4315 window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); 4316 window->DC.CursorPos = window->DC.CursorStartPos; 4317 window->DC.CursorPosPrevLine = window->DC.CursorPos; 4318 window->DC.CursorMaxPos = window->DC.CursorStartPos; 4319 window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f; 4320 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; 4321 window->DC.MenuBarAppending = false; 4322 window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x); 4323 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; 4324 window->DC.ChildWindows.resize(0); 4325 window->DC.LayoutType = ImGuiLayoutType_Vertical; 4326 window->DC.ItemWidth = window->ItemWidthDefault; 4327 window->DC.TextWrapPos = -1.0f; // disabled 4328 window->DC.AllowKeyboardFocus = true; 4329 window->DC.ButtonRepeat = false; 4330 window->DC.ItemWidthStack.resize(0); 4331 window->DC.AllowKeyboardFocusStack.resize(0); 4332 window->DC.ButtonRepeatStack.resize(0); 4333 window->DC.TextWrapPosStack.resize(0); 4334 window->DC.ColumnsCurrent = 0; 4335 window->DC.ColumnsCount = 1; 4336 window->DC.ColumnsStartPosY = window->DC.CursorPos.y; 4337 window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY = window->DC.ColumnsStartPosY; 4338 window->DC.TreeDepth = 0; 4339 window->DC.StateStorage = &window->StateStorage; 4340 window->DC.GroupStack.resize(0); 4341 window->DC.ColorEditMode = ImGuiColorEditMode_UserSelect; 4342 window->MenuColumns.Update(3, style.ItemSpacing.x, !window_was_active); 4343 4344 if (window->AutoFitFramesX > 0) 4345 window->AutoFitFramesX--; 4346 if (window->AutoFitFramesY > 0) 4347 window->AutoFitFramesY--; 4348 4349 // New windows appears in front (we need to do that AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) 4350 if (!window_was_active && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) 4351 if (!(flags & (ImGuiWindowFlags_ChildWindow|ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) 4352 FocusWindow(window); 4353 4354 // Title bar 4355 if (!(flags & ImGuiWindowFlags_NoTitleBar)) 4356 { 4357 if (p_open != NULL) 4358 { 4359 const float pad = 2.0f; 4360 const float rad = (window->TitleBarHeight() - pad*2.0f) * 0.5f; 4361 if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad)) 4362 *p_open = false; 4363 } 4364 4365 const ImVec2 text_size = CalcTextSize(name, NULL, true); 4366 if (!(flags & ImGuiWindowFlags_NoCollapse)) 4367 RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f); 4368 4369 ImVec2 text_min = window->Pos; 4370 ImVec2 text_max = window->Pos + ImVec2(window->Size.x, style.FramePadding.y*2 + text_size.y); 4371 ImRect clip_rect; 4372 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() 4373 float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; 4374 float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; 4375 if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); 4376 text_min.x += pad_left; 4377 text_max.x -= pad_right; 4378 clip_rect.Min = ImVec2(text_min.x, window->Pos.y); 4379 RenderTextClipped(text_min, text_max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); 4380 } 4381 4382 // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() 4383 window->WindowRectClipped = window->Rect(); 4384 window->WindowRectClipped.Clip(window->ClipRect); 4385 4386 // Pressing CTRL+C while holding on a window copy its content to the clipboard 4387 // 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. 4388 // Maybe we can support CTRL+C on every element? 4389 /* 4390 if (g.ActiveId == move_id) 4391 if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) 4392 ImGui::LogToClipboard(); 4393 */ 4394 } 4395 4396 // Inner clipping rectangle 4397 // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame 4398 // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. 4399 const ImRect title_bar_rect = window->TitleBarRect(); 4400 const float border_size = window->BorderSize; 4401 ImRect clip_rect; // Force round to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. 4402 clip_rect.Min.x = ImFloor(0.5f + title_bar_rect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); 4403 clip_rect.Min.y = ImFloor(0.5f + title_bar_rect.Max.y + window->MenuBarHeight() + border_size); 4404 clip_rect.Max.x = ImFloor(0.5f + window->Pos.x + window->Size.x - window->ScrollbarSizes.x - ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); 4405 clip_rect.Max.y = ImFloor(0.5f + window->Pos.y + window->Size.y - window->ScrollbarSizes.y - border_size); 4406 PushClipRect(clip_rect.Min, clip_rect.Max, true); 4407 4408 // Clear 'accessed' flag last thing 4409 if (first_begin_of_the_frame) 4410 window->Accessed = false; 4411 window->BeginCount++; 4412 g.SetNextWindowSizeConstraint = false; 4413 4414 // Child window can be out of sight and have "negative" clip windows. 4415 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar). 4416 if (flags & ImGuiWindowFlags_ChildWindow) 4417 { 4418 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); 4419 window->Collapsed = parent_window && parent_window->Collapsed; 4420 4421 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) 4422 window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y); 4423 4424 // We also hide the window from rendering because we've already added its border to the command list. 4425 // (we could perform the check earlier in the function but it is simpler at this point) 4426 if (window->Collapsed) 4427 window->Active = false; 4428 } 4429 if (style.Alpha <= 0.0f) 4430 window->Active = false; 4431 4432 // Return false if we don't intend to display anything to allow user to perform an early out optimization 4433 window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0; 4434 return !window->SkipItems; 4435 } 4436 4437 void ImGui::End() 4438 { 4439 ImGuiContext& g = *GImGui; 4440 ImGuiWindow* window = g.CurrentWindow; 4441 4442 if (window->DC.ColumnsCount != 1) // close columns set if any is open 4443 Columns(1, "#CLOSECOLUMNS"); 4444 PopClipRect(); // inner window clip rectangle 4445 4446 // Stop logging 4447 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging 4448 LogFinish(); 4449 4450 // Pop 4451 // NB: we don't clear 'window->RootWindow'. The pointer is allowed to live until the next call to Begin(). 4452 g.CurrentWindowStack.pop_back(); 4453 if (window->Flags & ImGuiWindowFlags_Popup) 4454 g.CurrentPopupStack.pop_back(); 4455 CheckStacksSize(window, false); 4456 SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); 4457 } 4458 4459 // Vertical scrollbar 4460 // The entire piece of code below is rather confusing because: 4461 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) 4462 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar 4463 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. 4464 static void Scrollbar(ImGuiWindow* window, bool horizontal) 4465 { 4466 ImGuiContext& g = *GImGui; 4467 const ImGuiStyle& style = g.Style; 4468 const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); 4469 4470 // Render background 4471 bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); 4472 float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; 4473 const ImRect window_rect = window->Rect(); 4474 const float border_size = window->BorderSize; 4475 ImRect bb = horizontal 4476 ? 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) 4477 : 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); 4478 if (!horizontal) 4479 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); 4480 if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) 4481 return; 4482 4483 float window_rounding = (window->Flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding; 4484 int window_rounding_corners; 4485 if (horizontal) 4486 window_rounding_corners = ImGuiCorner_BottomLeft | (other_scrollbar ? 0 : ImGuiCorner_BottomRight); 4487 else 4488 window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImGuiCorner_TopRight : 0) | (other_scrollbar ? 0 : ImGuiCorner_BottomRight); 4489 window->DrawList->AddRectFilled(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_ScrollbarBg), window_rounding, window_rounding_corners); 4490 bb.Reduce(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))); 4491 4492 // V denote the main axis of the scrollbar 4493 float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); 4494 float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; 4495 float win_size_avail_v = (horizontal ? window->Size.x : window->Size.y) - other_scrollbar_size_w; 4496 float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; 4497 4498 // The grabable box size generally represent the amount visible (vs the total scrollable amount) 4499 // But we maintain a minimum size in pixel to allow for the user to still aim inside. 4500 const float grab_h_pixels = ImMin(ImMax(scrollbar_size_v * ImSaturate(win_size_avail_v / ImMax(win_size_contents_v, win_size_avail_v)), style.GrabMinSize), scrollbar_size_v); 4501 const float grab_h_norm = grab_h_pixels / scrollbar_size_v; 4502 4503 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). 4504 bool held = false; 4505 bool hovered = false; 4506 const bool previously_held = (g.ActiveId == id); 4507 ImGui::ButtonBehavior(bb, id, &hovered, &held); 4508 4509 float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); 4510 float scroll_ratio = ImSaturate(scroll_v / scroll_max); 4511 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; 4512 if (held && grab_h_norm < 1.0f) 4513 { 4514 float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; 4515 float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; 4516 float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; 4517 4518 // Click position in scrollbar normalized space (0.0f->1.0f) 4519 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); 4520 ImGui::SetHoveredID(id); 4521 4522 bool seek_absolute = false; 4523 if (!previously_held) 4524 { 4525 // On initial click calculate the distance between mouse and the center of the grab 4526 if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) 4527 { 4528 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; 4529 } 4530 else 4531 { 4532 seek_absolute = true; 4533 *click_delta_to_grab_center_v = 0.0f; 4534 } 4535 } 4536 4537 // Apply scroll 4538 // 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 4539 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)); 4540 scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); 4541 if (horizontal) 4542 window->Scroll.x = scroll_v; 4543 else 4544 window->Scroll.y = scroll_v; 4545 4546 // Update values for rendering 4547 scroll_ratio = ImSaturate(scroll_v / scroll_max); 4548 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; 4549 4550 // Update distance to grab now that we have seeked and saturated 4551 if (seek_absolute) 4552 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; 4553 } 4554 4555 // Render 4556 const ImU32 grab_col = ImGui::GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); 4557 if (horizontal) 4558 window->DrawList->AddRectFilled(ImVec2(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y), ImVec2(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y), grab_col, style.ScrollbarRounding); 4559 else 4560 window->DrawList->AddRectFilled(ImVec2(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm)), ImVec2(bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels), grab_col, style.ScrollbarRounding); 4561 } 4562 4563 // Moving window to front of display (which happens to be back of our sorted list) 4564 void ImGui::FocusWindow(ImGuiWindow* window) 4565 { 4566 ImGuiContext& g = *GImGui; 4567 4568 // Always mark the window we passed as focused. This is used for keyboard interactions such as tabbing. 4569 g.FocusedWindow = window; 4570 4571 // Passing NULL allow to disable keyboard focus 4572 if (!window) 4573 return; 4574 4575 // And move its root window to the top of the pile 4576 if (window->RootWindow) 4577 window = window->RootWindow; 4578 4579 // Steal focus on active widgets 4580 if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. 4581 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) 4582 ClearActiveID(); 4583 4584 // Bring to front 4585 if ((window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) || g.Windows.back() == window) 4586 return; 4587 for (int i = 0; i < g.Windows.Size; i++) 4588 if (g.Windows[i] == window) 4589 { 4590 g.Windows.erase(g.Windows.begin() + i); 4591 break; 4592 } 4593 g.Windows.push_back(window); 4594 } 4595 4596 void ImGui::PushItemWidth(float item_width) 4597 { 4598 ImGuiWindow* window = GetCurrentWindow(); 4599 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); 4600 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); 4601 } 4602 4603 static void PushMultiItemsWidths(int components, float w_full) 4604 { 4605 ImGuiWindow* window = ImGui::GetCurrentWindow(); 4606 const ImGuiStyle& style = GImGui->Style; 4607 if (w_full <= 0.0f) 4608 w_full = ImGui::CalcItemWidth(); 4609 const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); 4610 const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); 4611 window->DC.ItemWidthStack.push_back(w_item_last); 4612 for (int i = 0; i < components-1; i++) 4613 window->DC.ItemWidthStack.push_back(w_item_one); 4614 window->DC.ItemWidth = window->DC.ItemWidthStack.back(); 4615 } 4616 4617 void ImGui::PopItemWidth() 4618 { 4619 ImGuiWindow* window = GetCurrentWindow(); 4620 window->DC.ItemWidthStack.pop_back(); 4621 window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); 4622 } 4623 4624 float ImGui::CalcItemWidth() 4625 { 4626 ImGuiWindow* window = GetCurrentWindowRead(); 4627 float w = window->DC.ItemWidth; 4628 if (w < 0.0f) 4629 { 4630 // 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. 4631 float width_to_right_edge = GetContentRegionAvail().x; 4632 w = ImMax(1.0f, width_to_right_edge + w); 4633 } 4634 w = (float)(int)w; 4635 return w; 4636 } 4637 4638 static ImFont* GetDefaultFont() 4639 { 4640 ImGuiContext& g = *GImGui; 4641 return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; 4642 } 4643 4644 static void SetCurrentFont(ImFont* font) 4645 { 4646 ImGuiContext& g = *GImGui; 4647 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? 4648 IM_ASSERT(font->Scale > 0.0f); 4649 g.Font = font; 4650 g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; 4651 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; 4652 g.FontTexUvWhitePixel = g.Font->ContainerAtlas->TexUvWhitePixel; 4653 } 4654 4655 void ImGui::PushFont(ImFont* font) 4656 { 4657 ImGuiContext& g = *GImGui; 4658 if (!font) 4659 font = GetDefaultFont(); 4660 SetCurrentFont(font); 4661 g.FontStack.push_back(font); 4662 g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); 4663 } 4664 4665 void ImGui::PopFont() 4666 { 4667 ImGuiContext& g = *GImGui; 4668 g.CurrentWindow->DrawList->PopTextureID(); 4669 g.FontStack.pop_back(); 4670 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); 4671 } 4672 4673 void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) 4674 { 4675 ImGuiWindow* window = GetCurrentWindow(); 4676 window->DC.AllowKeyboardFocus = allow_keyboard_focus; 4677 window->DC.AllowKeyboardFocusStack.push_back(allow_keyboard_focus); 4678 } 4679 4680 void ImGui::PopAllowKeyboardFocus() 4681 { 4682 ImGuiWindow* window = GetCurrentWindow(); 4683 window->DC.AllowKeyboardFocusStack.pop_back(); 4684 window->DC.AllowKeyboardFocus = window->DC.AllowKeyboardFocusStack.empty() ? true : window->DC.AllowKeyboardFocusStack.back(); 4685 } 4686 4687 void ImGui::PushButtonRepeat(bool repeat) 4688 { 4689 ImGuiWindow* window = GetCurrentWindow(); 4690 window->DC.ButtonRepeat = repeat; 4691 window->DC.ButtonRepeatStack.push_back(repeat); 4692 } 4693 4694 void ImGui::PopButtonRepeat() 4695 { 4696 ImGuiWindow* window = GetCurrentWindow(); 4697 window->DC.ButtonRepeatStack.pop_back(); 4698 window->DC.ButtonRepeat = window->DC.ButtonRepeatStack.empty() ? false : window->DC.ButtonRepeatStack.back(); 4699 } 4700 4701 void ImGui::PushTextWrapPos(float wrap_pos_x) 4702 { 4703 ImGuiWindow* window = GetCurrentWindow(); 4704 window->DC.TextWrapPos = wrap_pos_x; 4705 window->DC.TextWrapPosStack.push_back(wrap_pos_x); 4706 } 4707 4708 void ImGui::PopTextWrapPos() 4709 { 4710 ImGuiWindow* window = GetCurrentWindow(); 4711 window->DC.TextWrapPosStack.pop_back(); 4712 window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); 4713 } 4714 4715 void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) 4716 { 4717 ImGuiContext& g = *GImGui; 4718 ImGuiColMod backup; 4719 backup.Col = idx; 4720 backup.BackupValue = g.Style.Colors[idx]; 4721 g.ColorModifiers.push_back(backup); 4722 g.Style.Colors[idx] = col; 4723 } 4724 4725 void ImGui::PopStyleColor(int count) 4726 { 4727 ImGuiContext& g = *GImGui; 4728 while (count > 0) 4729 { 4730 ImGuiColMod& backup = g.ColorModifiers.back(); 4731 g.Style.Colors[backup.Col] = backup.BackupValue; 4732 g.ColorModifiers.pop_back(); 4733 count--; 4734 } 4735 } 4736 4737 struct ImGuiStyleVarInfo 4738 { 4739 ImGuiDataType Type; 4740 ImU32 Offset; 4741 void* GetVarPtr() const { return (void*)((unsigned char*)&GImGui->Style + Offset); } 4742 }; 4743 4744 static const ImGuiStyleVarInfo GStyleVarInfo[ImGuiStyleVar_Count_] = 4745 { 4746 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, 4747 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, 4748 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, 4749 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, 4750 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildWindowRounding) }, 4751 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, 4752 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, 4753 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, 4754 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, 4755 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, 4756 { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, 4757 { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, 4758 }; 4759 4760 static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) 4761 { 4762 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_); 4763 return &GStyleVarInfo[idx]; 4764 } 4765 4766 void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) 4767 { 4768 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); 4769 if (var_info->Type == ImGuiDataType_Float) 4770 { 4771 float* pvar = (float*)var_info->GetVarPtr(); 4772 GImGui->StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); 4773 *pvar = val; 4774 return; 4775 } 4776 IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. 4777 } 4778 4779 void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) 4780 { 4781 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); 4782 if (var_info->Type == ImGuiDataType_Float2) 4783 { 4784 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(); 4785 GImGui->StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); 4786 *pvar = val; 4787 return; 4788 } 4789 IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. 4790 } 4791 4792 void ImGui::PopStyleVar(int count) 4793 { 4794 ImGuiContext& g = *GImGui; 4795 while (count > 0) 4796 { 4797 ImGuiStyleMod& backup = g.StyleModifiers.back(); 4798 const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); 4799 if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr()) = backup.BackupFloat[0]; 4800 else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr()) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); 4801 else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr()) = backup.BackupInt[0]; 4802 g.StyleModifiers.pop_back(); 4803 count--; 4804 } 4805 } 4806 4807 const char* ImGui::GetStyleColName(ImGuiCol idx) 4808 { 4809 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; 4810 switch (idx) 4811 { 4812 case ImGuiCol_Text: return "Text"; 4813 case ImGuiCol_TextDisabled: return "TextDisabled"; 4814 case ImGuiCol_WindowBg: return "WindowBg"; 4815 case ImGuiCol_ChildWindowBg: return "ChildWindowBg"; 4816 case ImGuiCol_PopupBg: return "PopupBg"; 4817 case ImGuiCol_Border: return "Border"; 4818 case ImGuiCol_BorderShadow: return "BorderShadow"; 4819 case ImGuiCol_FrameBg: return "FrameBg"; 4820 case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; 4821 case ImGuiCol_FrameBgActive: return "FrameBgActive"; 4822 case ImGuiCol_TitleBg: return "TitleBg"; 4823 case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; 4824 case ImGuiCol_TitleBgActive: return "TitleBgActive"; 4825 case ImGuiCol_MenuBarBg: return "MenuBarBg"; 4826 case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; 4827 case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; 4828 case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; 4829 case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; 4830 case ImGuiCol_ComboBg: return "ComboBg"; 4831 case ImGuiCol_CheckMark: return "CheckMark"; 4832 case ImGuiCol_SliderGrab: return "SliderGrab"; 4833 case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; 4834 case ImGuiCol_Button: return "Button"; 4835 case ImGuiCol_ButtonHovered: return "ButtonHovered"; 4836 case ImGuiCol_ButtonActive: return "ButtonActive"; 4837 case ImGuiCol_Header: return "Header"; 4838 case ImGuiCol_HeaderHovered: return "HeaderHovered"; 4839 case ImGuiCol_HeaderActive: return "HeaderActive"; 4840 case ImGuiCol_Column: return "Column"; 4841 case ImGuiCol_ColumnHovered: return "ColumnHovered"; 4842 case ImGuiCol_ColumnActive: return "ColumnActive"; 4843 case ImGuiCol_ResizeGrip: return "ResizeGrip"; 4844 case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; 4845 case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; 4846 case ImGuiCol_CloseButton: return "CloseButton"; 4847 case ImGuiCol_CloseButtonHovered: return "CloseButtonHovered"; 4848 case ImGuiCol_CloseButtonActive: return "CloseButtonActive"; 4849 case ImGuiCol_PlotLines: return "PlotLines"; 4850 case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; 4851 case ImGuiCol_PlotHistogram: return "PlotHistogram"; 4852 case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; 4853 case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; 4854 case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening"; 4855 } 4856 IM_ASSERT(0); 4857 return "Unknown"; 4858 } 4859 4860 bool ImGui::IsWindowHovered() 4861 { 4862 ImGuiContext& g = *GImGui; 4863 return g.HoveredWindow == g.CurrentWindow && IsWindowContentHoverable(g.HoveredRootWindow); 4864 } 4865 4866 bool ImGui::IsWindowFocused() 4867 { 4868 ImGuiContext& g = *GImGui; 4869 return g.FocusedWindow == g.CurrentWindow; 4870 } 4871 4872 bool ImGui::IsRootWindowFocused() 4873 { 4874 ImGuiContext& g = *GImGui; 4875 return g.FocusedWindow == g.CurrentWindow->RootWindow; 4876 } 4877 4878 bool ImGui::IsRootWindowOrAnyChildFocused() 4879 { 4880 ImGuiContext& g = *GImGui; 4881 return g.FocusedWindow && g.FocusedWindow->RootWindow == g.CurrentWindow->RootWindow; 4882 } 4883 4884 bool ImGui::IsRootWindowOrAnyChildHovered() 4885 { 4886 ImGuiContext& g = *GImGui; 4887 return g.HoveredRootWindow && (g.HoveredRootWindow == g.CurrentWindow->RootWindow) && IsWindowContentHoverable(g.HoveredRootWindow); 4888 } 4889 4890 float ImGui::GetWindowWidth() 4891 { 4892 ImGuiWindow* window = GImGui->CurrentWindow; 4893 return window->Size.x; 4894 } 4895 4896 float ImGui::GetWindowHeight() 4897 { 4898 ImGuiWindow* window = GImGui->CurrentWindow; 4899 return window->Size.y; 4900 } 4901 4902 ImVec2 ImGui::GetWindowPos() 4903 { 4904 ImGuiContext& g = *GImGui; 4905 ImGuiWindow* window = g.CurrentWindow; 4906 return window->Pos; 4907 } 4908 4909 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) 4910 { 4911 window->DC.CursorMaxPos.y += window->Scroll.y; 4912 window->Scroll.y = new_scroll_y; 4913 window->DC.CursorMaxPos.y -= window->Scroll.y; 4914 } 4915 4916 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiSetCond cond) 4917 { 4918 // Test condition (NB: bit 0 is always true) and clear flags for next time 4919 if (cond && (window->SetWindowPosAllowFlags & cond) == 0) 4920 return; 4921 window->SetWindowPosAllowFlags &= ~(ImGuiSetCond_Once | ImGuiSetCond_FirstUseEver | ImGuiSetCond_Appearing); 4922 window->SetWindowPosCenterWanted = false; 4923 4924 // Set 4925 const ImVec2 old_pos = window->Pos; 4926 window->PosFloat = pos; 4927 window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); 4928 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 4929 window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. 4930 } 4931 4932 void ImGui::SetWindowPos(const ImVec2& pos, ImGuiSetCond cond) 4933 { 4934 ImGuiWindow* window = GetCurrentWindowRead(); 4935 SetWindowPos(window, pos, cond); 4936 } 4937 4938 void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiSetCond cond) 4939 { 4940 if (ImGuiWindow* window = FindWindowByName(name)) 4941 SetWindowPos(window, pos, cond); 4942 } 4943 4944 ImVec2 ImGui::GetWindowSize() 4945 { 4946 ImGuiWindow* window = GetCurrentWindowRead(); 4947 return window->Size; 4948 } 4949 4950 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiSetCond cond) 4951 { 4952 // Test condition (NB: bit 0 is always true) and clear flags for next time 4953 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) 4954 return; 4955 window->SetWindowSizeAllowFlags &= ~(ImGuiSetCond_Once | ImGuiSetCond_FirstUseEver | ImGuiSetCond_Appearing); 4956 4957 // Set 4958 if (size.x > 0.0f) 4959 { 4960 window->AutoFitFramesX = 0; 4961 window->SizeFull.x = size.x; 4962 } 4963 else 4964 { 4965 window->AutoFitFramesX = 2; 4966 window->AutoFitOnlyGrows = false; 4967 } 4968 if (size.y > 0.0f) 4969 { 4970 window->AutoFitFramesY = 0; 4971 window->SizeFull.y = size.y; 4972 } 4973 else 4974 { 4975 window->AutoFitFramesY = 2; 4976 window->AutoFitOnlyGrows = false; 4977 } 4978 } 4979 4980 void ImGui::SetWindowSize(const ImVec2& size, ImGuiSetCond cond) 4981 { 4982 SetWindowSize(GImGui->CurrentWindow, size, cond); 4983 } 4984 4985 void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiSetCond cond) 4986 { 4987 ImGuiWindow* window = FindWindowByName(name); 4988 if (window) 4989 SetWindowSize(window, size, cond); 4990 } 4991 4992 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiSetCond cond) 4993 { 4994 // Test condition (NB: bit 0 is always true) and clear flags for next time 4995 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) 4996 return; 4997 window->SetWindowCollapsedAllowFlags &= ~(ImGuiSetCond_Once | ImGuiSetCond_FirstUseEver | ImGuiSetCond_Appearing); 4998 4999 // Set 5000 window->Collapsed = collapsed; 5001 } 5002 5003 void ImGui::SetWindowCollapsed(bool collapsed, ImGuiSetCond cond) 5004 { 5005 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); 5006 } 5007 5008 bool ImGui::IsWindowCollapsed() 5009 { 5010 return GImGui->CurrentWindow->Collapsed; 5011 } 5012 5013 void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiSetCond cond) 5014 { 5015 ImGuiWindow* window = FindWindowByName(name); 5016 if (window) 5017 SetWindowCollapsed(window, collapsed, cond); 5018 } 5019 5020 void ImGui::SetWindowFocus() 5021 { 5022 FocusWindow(GImGui->CurrentWindow); 5023 } 5024 5025 void ImGui::SetWindowFocus(const char* name) 5026 { 5027 if (name) 5028 { 5029 if (ImGuiWindow* window = FindWindowByName(name)) 5030 FocusWindow(window); 5031 } 5032 else 5033 { 5034 FocusWindow(NULL); 5035 } 5036 } 5037 5038 void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiSetCond cond) 5039 { 5040 ImGuiContext& g = *GImGui; 5041 g.SetNextWindowPosVal = pos; 5042 g.SetNextWindowPosCond = cond ? cond : ImGuiSetCond_Always; 5043 } 5044 5045 void ImGui::SetNextWindowPosCenter(ImGuiSetCond cond) 5046 { 5047 ImGuiContext& g = *GImGui; 5048 g.SetNextWindowPosVal = ImVec2(-FLT_MAX, -FLT_MAX); 5049 g.SetNextWindowPosCond = cond ? cond : ImGuiSetCond_Always; 5050 } 5051 5052 void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiSetCond cond) 5053 { 5054 ImGuiContext& g = *GImGui; 5055 g.SetNextWindowSizeVal = size; 5056 g.SetNextWindowSizeCond = cond ? cond : ImGuiSetCond_Always; 5057 } 5058 5059 void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback, void* custom_callback_user_data) 5060 { 5061 ImGuiContext& g = *GImGui; 5062 g.SetNextWindowSizeConstraint = true; 5063 g.SetNextWindowSizeConstraintRect = ImRect(size_min, size_max); 5064 g.SetNextWindowSizeConstraintCallback = custom_callback; 5065 g.SetNextWindowSizeConstraintCallbackUserData = custom_callback_user_data; 5066 } 5067 5068 void ImGui::SetNextWindowContentSize(const ImVec2& size) 5069 { 5070 ImGuiContext& g = *GImGui; 5071 g.SetNextWindowContentSizeVal = size; 5072 g.SetNextWindowContentSizeCond = ImGuiSetCond_Always; 5073 } 5074 5075 void ImGui::SetNextWindowContentWidth(float width) 5076 { 5077 ImGuiContext& g = *GImGui; 5078 g.SetNextWindowContentSizeVal = ImVec2(width, g.SetNextWindowContentSizeCond ? g.SetNextWindowContentSizeVal.y : 0.0f); 5079 g.SetNextWindowContentSizeCond = ImGuiSetCond_Always; 5080 } 5081 5082 void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiSetCond cond) 5083 { 5084 ImGuiContext& g = *GImGui; 5085 g.SetNextWindowCollapsedVal = collapsed; 5086 g.SetNextWindowCollapsedCond = cond ? cond : ImGuiSetCond_Always; 5087 } 5088 5089 void ImGui::SetNextWindowFocus() 5090 { 5091 ImGuiContext& g = *GImGui; 5092 g.SetNextWindowFocus = true; 5093 } 5094 5095 // In window space (not screen space!) 5096 ImVec2 ImGui::GetContentRegionMax() 5097 { 5098 ImGuiWindow* window = GetCurrentWindowRead(); 5099 ImVec2 mx = window->ContentsRegionRect.Max; 5100 if (window->DC.ColumnsCount != 1) 5101 mx.x = GetColumnOffset(window->DC.ColumnsCurrent + 1) - window->WindowPadding.x; 5102 return mx; 5103 } 5104 5105 ImVec2 ImGui::GetContentRegionAvail() 5106 { 5107 ImGuiWindow* window = GetCurrentWindowRead(); 5108 return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); 5109 } 5110 5111 float ImGui::GetContentRegionAvailWidth() 5112 { 5113 return GetContentRegionAvail().x; 5114 } 5115 5116 // In window space (not screen space!) 5117 ImVec2 ImGui::GetWindowContentRegionMin() 5118 { 5119 ImGuiWindow* window = GetCurrentWindowRead(); 5120 return window->ContentsRegionRect.Min; 5121 } 5122 5123 ImVec2 ImGui::GetWindowContentRegionMax() 5124 { 5125 ImGuiWindow* window = GetCurrentWindowRead(); 5126 return window->ContentsRegionRect.Max; 5127 } 5128 5129 float ImGui::GetWindowContentRegionWidth() 5130 { 5131 ImGuiWindow* window = GetCurrentWindowRead(); 5132 return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x; 5133 } 5134 5135 float ImGui::GetTextLineHeight() 5136 { 5137 ImGuiContext& g = *GImGui; 5138 return g.FontSize; 5139 } 5140 5141 float ImGui::GetTextLineHeightWithSpacing() 5142 { 5143 ImGuiContext& g = *GImGui; 5144 return g.FontSize + g.Style.ItemSpacing.y; 5145 } 5146 5147 float ImGui::GetItemsLineHeightWithSpacing() 5148 { 5149 ImGuiContext& g = *GImGui; 5150 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; 5151 } 5152 5153 ImDrawList* ImGui::GetWindowDrawList() 5154 { 5155 ImGuiWindow* window = GetCurrentWindow(); 5156 return window->DrawList; 5157 } 5158 5159 ImFont* ImGui::GetFont() 5160 { 5161 return GImGui->Font; 5162 } 5163 5164 float ImGui::GetFontSize() 5165 { 5166 return GImGui->FontSize; 5167 } 5168 5169 ImVec2 ImGui::GetFontTexUvWhitePixel() 5170 { 5171 return GImGui->FontTexUvWhitePixel; 5172 } 5173 5174 void ImGui::SetWindowFontScale(float scale) 5175 { 5176 ImGuiContext& g = *GImGui; 5177 ImGuiWindow* window = GetCurrentWindow(); 5178 window->FontWindowScale = scale; 5179 g.FontSize = window->CalcFontSize(); 5180 } 5181 5182 // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. 5183 // 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'. 5184 ImVec2 ImGui::GetCursorPos() 5185 { 5186 ImGuiWindow* window = GetCurrentWindowRead(); 5187 return window->DC.CursorPos - window->Pos + window->Scroll; 5188 } 5189 5190 float ImGui::GetCursorPosX() 5191 { 5192 ImGuiWindow* window = GetCurrentWindowRead(); 5193 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; 5194 } 5195 5196 float ImGui::GetCursorPosY() 5197 { 5198 ImGuiWindow* window = GetCurrentWindowRead(); 5199 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; 5200 } 5201 5202 void ImGui::SetCursorPos(const ImVec2& local_pos) 5203 { 5204 ImGuiWindow* window = GetCurrentWindow(); 5205 window->DC.CursorPos = window->Pos - window->Scroll + local_pos; 5206 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); 5207 } 5208 5209 void ImGui::SetCursorPosX(float x) 5210 { 5211 ImGuiWindow* window = GetCurrentWindow(); 5212 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; 5213 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); 5214 } 5215 5216 void ImGui::SetCursorPosY(float y) 5217 { 5218 ImGuiWindow* window = GetCurrentWindow(); 5219 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; 5220 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); 5221 } 5222 5223 ImVec2 ImGui::GetCursorStartPos() 5224 { 5225 ImGuiWindow* window = GetCurrentWindowRead(); 5226 return window->DC.CursorStartPos - window->Pos; 5227 } 5228 5229 ImVec2 ImGui::GetCursorScreenPos() 5230 { 5231 ImGuiWindow* window = GetCurrentWindowRead(); 5232 return window->DC.CursorPos; 5233 } 5234 5235 void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) 5236 { 5237 ImGuiWindow* window = GetCurrentWindow(); 5238 window->DC.CursorPos = screen_pos; 5239 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); 5240 } 5241 5242 float ImGui::GetScrollX() 5243 { 5244 return GImGui->CurrentWindow->Scroll.x; 5245 } 5246 5247 float ImGui::GetScrollY() 5248 { 5249 return GImGui->CurrentWindow->Scroll.y; 5250 } 5251 5252 float ImGui::GetScrollMaxX() 5253 { 5254 ImGuiWindow* window = GetCurrentWindowRead(); 5255 return window->SizeContents.x - window->SizeFull.x - window->ScrollbarSizes.x; 5256 } 5257 5258 float ImGui::GetScrollMaxY() 5259 { 5260 ImGuiWindow* window = GetCurrentWindowRead(); 5261 return window->SizeContents.y - window->SizeFull.y - window->ScrollbarSizes.y; 5262 } 5263 5264 void ImGui::SetScrollX(float scroll_x) 5265 { 5266 ImGuiWindow* window = GetCurrentWindow(); 5267 window->ScrollTarget.x = scroll_x; 5268 window->ScrollTargetCenterRatio.x = 0.0f; 5269 } 5270 5271 void ImGui::SetScrollY(float scroll_y) 5272 { 5273 ImGuiWindow* window = GetCurrentWindow(); 5274 window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY 5275 window->ScrollTargetCenterRatio.y = 0.0f; 5276 } 5277 5278 void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) 5279 { 5280 // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size 5281 ImGuiWindow* window = GetCurrentWindow(); 5282 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); 5283 window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); 5284 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) 5285 window->ScrollTarget.y = 0.0f; 5286 window->ScrollTargetCenterRatio.y = center_y_ratio; 5287 } 5288 5289 // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. 5290 void ImGui::SetScrollHere(float center_y_ratio) 5291 { 5292 ImGuiWindow* window = GetCurrentWindow(); 5293 float target_y = window->DC.CursorPosPrevLine.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. 5294 SetScrollFromPosY(target_y - window->Pos.y, center_y_ratio); 5295 } 5296 5297 void ImGui::SetKeyboardFocusHere(int offset) 5298 { 5299 ImGuiWindow* window = GetCurrentWindow(); 5300 window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; 5301 window->FocusIdxTabRequestNext = INT_MAX; 5302 } 5303 5304 void ImGui::SetStateStorage(ImGuiStorage* tree) 5305 { 5306 ImGuiWindow* window = GetCurrentWindow(); 5307 window->DC.StateStorage = tree ? tree : &window->StateStorage; 5308 } 5309 5310 ImGuiStorage* ImGui::GetStateStorage() 5311 { 5312 ImGuiWindow* window = GetCurrentWindowRead(); 5313 return window->DC.StateStorage; 5314 } 5315 5316 void ImGui::TextV(const char* fmt, va_list args) 5317 { 5318 ImGuiWindow* window = GetCurrentWindow(); 5319 if (window->SkipItems) 5320 return; 5321 5322 ImGuiContext& g = *GImGui; 5323 const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); 5324 TextUnformatted(g.TempBuffer, text_end); 5325 } 5326 5327 void ImGui::Text(const char* fmt, ...) 5328 { 5329 va_list args; 5330 va_start(args, fmt); 5331 TextV(fmt, args); 5332 va_end(args); 5333 } 5334 5335 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) 5336 { 5337 PushStyleColor(ImGuiCol_Text, col); 5338 TextV(fmt, args); 5339 PopStyleColor(); 5340 } 5341 5342 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) 5343 { 5344 va_list args; 5345 va_start(args, fmt); 5346 TextColoredV(col, fmt, args); 5347 va_end(args); 5348 } 5349 5350 void ImGui::TextDisabledV(const char* fmt, va_list args) 5351 { 5352 PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); 5353 TextV(fmt, args); 5354 PopStyleColor(); 5355 } 5356 5357 void ImGui::TextDisabled(const char* fmt, ...) 5358 { 5359 va_list args; 5360 va_start(args, fmt); 5361 TextDisabledV(fmt, args); 5362 va_end(args); 5363 } 5364 5365 void ImGui::TextWrappedV(const char* fmt, va_list args) 5366 { 5367 bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set 5368 if (need_wrap) PushTextWrapPos(0.0f); 5369 TextV(fmt, args); 5370 if (need_wrap) PopTextWrapPos(); 5371 } 5372 5373 void ImGui::TextWrapped(const char* fmt, ...) 5374 { 5375 va_list args; 5376 va_start(args, fmt); 5377 TextWrappedV(fmt, args); 5378 va_end(args); 5379 } 5380 5381 void ImGui::TextUnformatted(const char* text, const char* text_end) 5382 { 5383 ImGuiWindow* window = GetCurrentWindow(); 5384 if (window->SkipItems) 5385 return; 5386 5387 ImGuiContext& g = *GImGui; 5388 IM_ASSERT(text != NULL); 5389 const char* text_begin = text; 5390 if (text_end == NULL) 5391 text_end = text + strlen(text); // FIXME-OPT 5392 5393 const float wrap_pos_x = window->DC.TextWrapPos; 5394 const bool wrap_enabled = wrap_pos_x >= 0.0f; 5395 if (text_end - text > 2000 && !wrap_enabled) 5396 { 5397 // Long text! 5398 // Perform manual coarse clipping to optimize for long multi-line text 5399 // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. 5400 // 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. 5401 const char* line = text; 5402 const float line_height = GetTextLineHeight(); 5403 const ImVec2 text_pos = window->DC.CursorPos + ImVec2(0.0f, window->DC.CurrentLineTextBaseOffset); 5404 const ImRect clip_rect = window->ClipRect; 5405 ImVec2 text_size(0,0); 5406 5407 if (text_pos.y <= clip_rect.Max.y) 5408 { 5409 ImVec2 pos = text_pos; 5410 5411 // Lines to skip (can't skip when logging text) 5412 if (!g.LogEnabled) 5413 { 5414 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); 5415 if (lines_skippable > 0) 5416 { 5417 int lines_skipped = 0; 5418 while (line < text_end && lines_skipped < lines_skippable) 5419 { 5420 const char* line_end = strchr(line, '\n'); 5421 if (!line_end) 5422 line_end = text_end; 5423 line = line_end + 1; 5424 lines_skipped++; 5425 } 5426 pos.y += lines_skipped * line_height; 5427 } 5428 } 5429 5430 // Lines to render 5431 if (line < text_end) 5432 { 5433 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); 5434 while (line < text_end) 5435 { 5436 const char* line_end = strchr(line, '\n'); 5437 if (IsClippedEx(line_rect, NULL, false)) 5438 break; 5439 5440 const ImVec2 line_size = CalcTextSize(line, line_end, false); 5441 text_size.x = ImMax(text_size.x, line_size.x); 5442 RenderText(pos, line, line_end, false); 5443 if (!line_end) 5444 line_end = text_end; 5445 line = line_end + 1; 5446 line_rect.Min.y += line_height; 5447 line_rect.Max.y += line_height; 5448 pos.y += line_height; 5449 } 5450 5451 // Count remaining lines 5452 int lines_skipped = 0; 5453 while (line < text_end) 5454 { 5455 const char* line_end = strchr(line, '\n'); 5456 if (!line_end) 5457 line_end = text_end; 5458 line = line_end + 1; 5459 lines_skipped++; 5460 } 5461 pos.y += lines_skipped * line_height; 5462 } 5463 5464 text_size.y += (pos - text_pos).y; 5465 } 5466 5467 ImRect bb(text_pos, text_pos + text_size); 5468 ItemSize(bb); 5469 ItemAdd(bb, NULL); 5470 } 5471 else 5472 { 5473 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; 5474 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); 5475 5476 // Account of baseline offset 5477 ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); 5478 ImRect bb(text_pos, text_pos + text_size); 5479 ItemSize(text_size); 5480 if (!ItemAdd(bb, NULL)) 5481 return; 5482 5483 // Render (we don't hide text after ## in this end-user function) 5484 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); 5485 } 5486 } 5487 5488 void ImGui::AlignFirstTextHeightToWidgets() 5489 { 5490 ImGuiWindow* window = GetCurrentWindow(); 5491 if (window->SkipItems) 5492 return; 5493 5494 // Declare a dummy item size to that upcoming items that are smaller will center-align on the newly expanded line height. 5495 ImGuiContext& g = *GImGui; 5496 ItemSize(ImVec2(0, g.FontSize + g.Style.FramePadding.y*2), g.Style.FramePadding.y); 5497 SameLine(0, 0); 5498 } 5499 5500 // Add a label+text combo aligned to other label+value widgets 5501 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) 5502 { 5503 ImGuiWindow* window = GetCurrentWindow(); 5504 if (window->SkipItems) 5505 return; 5506 5507 ImGuiContext& g = *GImGui; 5508 const ImGuiStyle& style = g.Style; 5509 const float w = CalcItemWidth(); 5510 5511 const ImVec2 label_size = CalcTextSize(label, NULL, true); 5512 const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); 5513 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); 5514 ItemSize(total_bb, style.FramePadding.y); 5515 if (!ItemAdd(total_bb, NULL)) 5516 return; 5517 5518 // Render 5519 const char* value_text_begin = &g.TempBuffer[0]; 5520 const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); 5521 RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); 5522 if (label_size.x > 0.0f) 5523 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); 5524 } 5525 5526 void ImGui::LabelText(const char* label, const char* fmt, ...) 5527 { 5528 va_list args; 5529 va_start(args, fmt); 5530 LabelTextV(label, fmt, args); 5531 va_end(args); 5532 } 5533 5534 static inline bool IsWindowContentHoverable(ImGuiWindow* window) 5535 { 5536 // An active popup disable hovering on other windows (apart from its own children) 5537 ImGuiContext& g = *GImGui; 5538 if (ImGuiWindow* focused_window = g.FocusedWindow) 5539 if (ImGuiWindow* focused_root_window = focused_window->RootWindow) 5540 if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) != 0 && focused_root_window->WasActive && focused_root_window != window->RootWindow) 5541 return false; 5542 5543 return true; 5544 } 5545 5546 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) 5547 { 5548 ImGuiContext& g = *GImGui; 5549 ImGuiWindow* window = GetCurrentWindow(); 5550 5551 if (flags & ImGuiButtonFlags_Disabled) 5552 { 5553 if (out_hovered) *out_hovered = false; 5554 if (out_held) *out_held = false; 5555 if (g.ActiveId == id) ClearActiveID(); 5556 return false; 5557 } 5558 5559 if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) 5560 flags |= ImGuiButtonFlags_PressedOnClickRelease; 5561 5562 bool pressed = false; 5563 bool hovered = IsHovered(bb, id, (flags & ImGuiButtonFlags_FlattenChilds) != 0); 5564 if (hovered) 5565 { 5566 SetHoveredID(id); 5567 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) 5568 { 5569 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat 5570 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds 5571 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> .. 5572 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release) 5573 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> .. 5574 if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) 5575 { 5576 SetActiveID(id, window); // Hold on ID 5577 FocusWindow(window); 5578 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; 5579 } 5580 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) 5581 { 5582 pressed = true; 5583 ClearActiveID(); 5584 FocusWindow(window); 5585 } 5586 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) 5587 { 5588 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release> 5589 pressed = true; 5590 ClearActiveID(); 5591 } 5592 5593 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). 5594 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. 5595 if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) 5596 pressed = true; 5597 } 5598 } 5599 5600 bool held = false; 5601 if (g.ActiveId == id) 5602 { 5603 if (g.IO.MouseDown[0]) 5604 { 5605 held = true; 5606 } 5607 else 5608 { 5609 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) 5610 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release> 5611 pressed = true; 5612 ClearActiveID(); 5613 } 5614 } 5615 5616 // 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. 5617 if (hovered && (flags & ImGuiButtonFlags_AllowOverlapMode) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) 5618 hovered = pressed = held = false; 5619 5620 if (out_hovered) *out_hovered = hovered; 5621 if (out_held) *out_held = held; 5622 5623 return pressed; 5624 } 5625 5626 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) 5627 { 5628 ImGuiWindow* window = GetCurrentWindow(); 5629 if (window->SkipItems) 5630 return false; 5631 5632 ImGuiContext& g = *GImGui; 5633 const ImGuiStyle& style = g.Style; 5634 const ImGuiID id = window->GetID(label); 5635 const ImVec2 label_size = CalcTextSize(label, NULL, true); 5636 5637 ImVec2 pos = window->DC.CursorPos; 5638 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) 5639 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; 5640 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); 5641 5642 const ImRect bb(pos, pos + size); 5643 ItemSize(bb, style.FramePadding.y); 5644 if (!ItemAdd(bb, &id)) 5645 return false; 5646 5647 if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; 5648 bool hovered, held; 5649 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); 5650 5651 // Render 5652 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); 5653 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); 5654 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); 5655 5656 // Automatically close popups 5657 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) 5658 // CloseCurrentPopup(); 5659 5660 return pressed; 5661 } 5662 5663 bool ImGui::Button(const char* label, const ImVec2& size_arg) 5664 { 5665 return ButtonEx(label, size_arg, 0); 5666 } 5667 5668 // Small buttons fits within text without additional vertical spacing. 5669 bool ImGui::SmallButton(const char* label) 5670 { 5671 ImGuiContext& g = *GImGui; 5672 float backup_padding_y = g.Style.FramePadding.y; 5673 g.Style.FramePadding.y = 0.0f; 5674 bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine); 5675 g.Style.FramePadding.y = backup_padding_y; 5676 return pressed; 5677 } 5678 5679 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. 5680 // 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) 5681 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) 5682 { 5683 ImGuiWindow* window = GetCurrentWindow(); 5684 if (window->SkipItems) 5685 return false; 5686 5687 const ImGuiID id = window->GetID(str_id); 5688 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); 5689 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); 5690 ItemSize(bb); 5691 if (!ItemAdd(bb, &id)) 5692 return false; 5693 5694 bool hovered, held; 5695 bool pressed = ButtonBehavior(bb, id, &hovered, &held); 5696 5697 return pressed; 5698 } 5699 5700 // Upper-right button to close a window. 5701 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) 5702 { 5703 ImGuiWindow* window = GetCurrentWindow(); 5704 5705 const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); 5706 5707 bool hovered, held; 5708 bool pressed = ButtonBehavior(bb, id, &hovered, &held); 5709 5710 // Render 5711 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton); 5712 const ImVec2 center = bb.GetCenter(); 5713 window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12); 5714 5715 const float cross_extent = (radius * 0.7071f) - 1.0f; 5716 if (hovered) 5717 { 5718 window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), GetColorU32(ImGuiCol_Text)); 5719 window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), GetColorU32(ImGuiCol_Text)); 5720 } 5721 5722 return pressed; 5723 } 5724 5725 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) 5726 { 5727 ImGuiWindow* window = GetCurrentWindow(); 5728 if (window->SkipItems) 5729 return; 5730 5731 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); 5732 if (border_col.w > 0.0f) 5733 bb.Max += ImVec2(2,2); 5734 ItemSize(bb); 5735 if (!ItemAdd(bb, NULL)) 5736 return; 5737 5738 if (border_col.w > 0.0f) 5739 { 5740 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); 5741 window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col)); 5742 } 5743 else 5744 { 5745 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); 5746 } 5747 } 5748 5749 // frame_padding < 0: uses FramePadding from style (default) 5750 // frame_padding = 0: no framing 5751 // frame_padding > 0: set framing size 5752 // The color used are the button colors. 5753 bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) 5754 { 5755 ImGuiWindow* window = GetCurrentWindow(); 5756 if (window->SkipItems) 5757 return false; 5758 5759 ImGuiContext& g = *GImGui; 5760 const ImGuiStyle& style = g.Style; 5761 5762 // Default to using texture ID as ID. User can still push string/integer prefixes. 5763 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. 5764 PushID((void *)user_texture_id); 5765 const ImGuiID id = window->GetID("#image"); 5766 PopID(); 5767 5768 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; 5769 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2); 5770 const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); 5771 ItemSize(bb); 5772 if (!ItemAdd(bb, &id)) 5773 return false; 5774 5775 bool hovered, held; 5776 bool pressed = ButtonBehavior(bb, id, &hovered, &held); 5777 5778 // Render 5779 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); 5780 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); 5781 if (bg_col.w > 0.0f) 5782 window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); 5783 window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); 5784 5785 return pressed; 5786 } 5787 5788 // Start logging ImGui output to TTY 5789 void ImGui::LogToTTY(int max_depth) 5790 { 5791 ImGuiContext& g = *GImGui; 5792 if (g.LogEnabled) 5793 return; 5794 ImGuiWindow* window = GetCurrentWindowRead(); 5795 5796 g.LogEnabled = true; 5797 g.LogFile = stdout; 5798 g.LogStartDepth = window->DC.TreeDepth; 5799 if (max_depth >= 0) 5800 g.LogAutoExpandMaxDepth = max_depth; 5801 } 5802 5803 // Start logging ImGui output to given file 5804 void ImGui::LogToFile(int max_depth, const char* filename) 5805 { 5806 ImGuiContext& g = *GImGui; 5807 if (g.LogEnabled) 5808 return; 5809 ImGuiWindow* window = GetCurrentWindowRead(); 5810 5811 if (!filename) 5812 { 5813 filename = g.IO.LogFilename; 5814 if (!filename) 5815 return; 5816 } 5817 5818 g.LogFile = ImFileOpen(filename, "ab"); 5819 if (!g.LogFile) 5820 { 5821 IM_ASSERT(g.LogFile != NULL); // Consider this an error 5822 return; 5823 } 5824 g.LogEnabled = true; 5825 g.LogStartDepth = window->DC.TreeDepth; 5826 if (max_depth >= 0) 5827 g.LogAutoExpandMaxDepth = max_depth; 5828 } 5829 5830 // Start logging ImGui output to clipboard 5831 void ImGui::LogToClipboard(int max_depth) 5832 { 5833 ImGuiContext& g = *GImGui; 5834 if (g.LogEnabled) 5835 return; 5836 ImGuiWindow* window = GetCurrentWindowRead(); 5837 5838 g.LogEnabled = true; 5839 g.LogFile = NULL; 5840 g.LogStartDepth = window->DC.TreeDepth; 5841 if (max_depth >= 0) 5842 g.LogAutoExpandMaxDepth = max_depth; 5843 } 5844 5845 void ImGui::LogFinish() 5846 { 5847 ImGuiContext& g = *GImGui; 5848 if (!g.LogEnabled) 5849 return; 5850 5851 LogText(IM_NEWLINE); 5852 g.LogEnabled = false; 5853 if (g.LogFile != NULL) 5854 { 5855 if (g.LogFile == stdout) 5856 fflush(g.LogFile); 5857 else 5858 fclose(g.LogFile); 5859 g.LogFile = NULL; 5860 } 5861 if (g.LogClipboard->size() > 1) 5862 { 5863 SetClipboardText(g.LogClipboard->begin()); 5864 g.LogClipboard->clear(); 5865 } 5866 } 5867 5868 // Helper to display logging buttons 5869 void ImGui::LogButtons() 5870 { 5871 ImGuiContext& g = *GImGui; 5872 5873 PushID("LogButtons"); 5874 const bool log_to_tty = Button("Log To TTY"); SameLine(); 5875 const bool log_to_file = Button("Log To File"); SameLine(); 5876 const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); 5877 PushItemWidth(80.0f); 5878 PushAllowKeyboardFocus(false); 5879 SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); 5880 PopAllowKeyboardFocus(); 5881 PopItemWidth(); 5882 PopID(); 5883 5884 // Start logging at the end of the function so that the buttons don't appear in the log 5885 if (log_to_tty) 5886 LogToTTY(g.LogAutoExpandMaxDepth); 5887 if (log_to_file) 5888 LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); 5889 if (log_to_clipboard) 5890 LogToClipboard(g.LogAutoExpandMaxDepth); 5891 } 5892 5893 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) 5894 { 5895 if (flags & ImGuiTreeNodeFlags_Leaf) 5896 return true; 5897 5898 // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions) 5899 ImGuiContext& g = *GImGui; 5900 ImGuiWindow* window = g.CurrentWindow; 5901 ImGuiStorage* storage = window->DC.StateStorage; 5902 5903 bool is_open; 5904 if (g.SetNextTreeNodeOpenCond != 0) 5905 { 5906 if (g.SetNextTreeNodeOpenCond & ImGuiSetCond_Always) 5907 { 5908 is_open = g.SetNextTreeNodeOpenVal; 5909 storage->SetInt(id, is_open); 5910 } 5911 else 5912 { 5913 // We treat ImGuiSetCondition_Once and ImGuiSetCondition_FirstUseEver the same because tree node state are not saved persistently. 5914 const int stored_value = storage->GetInt(id, -1); 5915 if (stored_value == -1) 5916 { 5917 is_open = g.SetNextTreeNodeOpenVal; 5918 storage->SetInt(id, is_open); 5919 } 5920 else 5921 { 5922 is_open = stored_value != 0; 5923 } 5924 } 5925 g.SetNextTreeNodeOpenCond = 0; 5926 } 5927 else 5928 { 5929 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; 5930 } 5931 5932 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). 5933 // NB- If we are above max depth we still allow manually opened nodes to be logged. 5934 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) 5935 is_open = true; 5936 5937 return is_open; 5938 } 5939 5940 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) 5941 { 5942 ImGuiWindow* window = GetCurrentWindow(); 5943 if (window->SkipItems) 5944 return false; 5945 5946 ImGuiContext& g = *GImGui; 5947 const ImGuiStyle& style = g.Style; 5948 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; 5949 const ImVec2 padding = display_frame ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); 5950 5951 if (!label_end) 5952 label_end = FindRenderedTextEnd(label); 5953 const ImVec2 label_size = CalcTextSize(label, label_end, false); 5954 5955 // We vertically grow up to current line height up the typical widget height. 5956 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset - padding.y); // Latch before ItemSize changes it 5957 const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); 5958 ImRect bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); 5959 if (display_frame) 5960 { 5961 // Framed header expand a little outside the default padding 5962 bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; 5963 bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; 5964 } 5965 5966 const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing 5967 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser 5968 ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); 5969 5970 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing 5971 // (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) 5972 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); 5973 bool is_open = TreeNodeBehaviorIsOpen(id, flags); 5974 if (!ItemAdd(interact_bb, &id)) 5975 { 5976 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) 5977 TreePushRawID(id); 5978 return is_open; 5979 } 5980 5981 // Flags that affects opening behavior: 5982 // - 0(default) ..................... single-click anywhere to open 5983 // - OpenOnDoubleClick .............. double-click anywhere to open 5984 // - OpenOnArrow .................... single-click on arrow to open 5985 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open 5986 ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowOverlapMode) ? ImGuiButtonFlags_AllowOverlapMode : 0); 5987 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) 5988 button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); 5989 bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); 5990 if (pressed && !(flags & ImGuiTreeNodeFlags_Leaf)) 5991 { 5992 bool toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)); 5993 if (flags & ImGuiTreeNodeFlags_OpenOnArrow) 5994 toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)); 5995 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) 5996 toggled |= g.IO.MouseDoubleClicked[0]; 5997 if (toggled) 5998 { 5999 is_open = !is_open; 6000 window->DC.StateStorage->SetInt(id, is_open); 6001 } 6002 } 6003 if (flags & ImGuiTreeNodeFlags_AllowOverlapMode) 6004 SetItemAllowOverlap(); 6005 6006 // Render 6007 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); 6008 const ImVec2 text_pos = bb.Min + ImVec2(text_offset_x, padding.y + text_base_offset_y); 6009 if (display_frame) 6010 { 6011 // Framed type 6012 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); 6013 RenderCollapseTriangle(bb.Min + padding + ImVec2(0.0f, text_base_offset_y), is_open, 1.0f); 6014 if (g.LogEnabled) 6015 { 6016 // 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. 6017 const char log_prefix[] = "\n##"; 6018 const char log_suffix[] = "##"; 6019 LogRenderedText(text_pos, log_prefix, log_prefix+3); 6020 RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size); 6021 LogRenderedText(text_pos, log_suffix+1, log_suffix+3); 6022 } 6023 else 6024 { 6025 RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size); 6026 } 6027 } 6028 else 6029 { 6030 // Unframed typed for tree nodes 6031 if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) 6032 RenderFrame(bb.Min, bb.Max, col, false); 6033 6034 if (flags & ImGuiTreeNodeFlags_Bullet) 6035 RenderBullet(bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); 6036 else if (!(flags & ImGuiTreeNodeFlags_Leaf)) 6037 RenderCollapseTriangle(bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open, 0.70f); 6038 if (g.LogEnabled) 6039 LogRenderedText(text_pos, ">"); 6040 RenderText(text_pos, label, label_end, false); 6041 } 6042 6043 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) 6044 TreePushRawID(id); 6045 return is_open; 6046 } 6047 6048 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). 6049 // 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(). 6050 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) 6051 { 6052 ImGuiWindow* window = GetCurrentWindow(); 6053 if (window->SkipItems) 6054 return false; 6055 6056 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label); 6057 } 6058 6059 bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) 6060 { 6061 ImGuiWindow* window = GetCurrentWindow(); 6062 if (window->SkipItems) 6063 return false; 6064 6065 if (p_open && !*p_open) 6066 return false; 6067 6068 ImGuiID id = window->GetID(label); 6069 bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowOverlapMode : 0), label); 6070 if (p_open) 6071 { 6072 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. 6073 ImGuiContext& g = *GImGui; 6074 float button_sz = g.FontSize * 0.5f; 6075 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)) 6076 *p_open = false; 6077 } 6078 6079 return is_open; 6080 } 6081 6082 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) 6083 { 6084 ImGuiWindow* window = GetCurrentWindow(); 6085 if (window->SkipItems) 6086 return false; 6087 6088 return TreeNodeBehavior(window->GetID(label), flags, label, NULL); 6089 } 6090 6091 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) 6092 { 6093 ImGuiWindow* window = GetCurrentWindow(); 6094 if (window->SkipItems) 6095 return false; 6096 6097 ImGuiContext& g = *GImGui; 6098 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); 6099 return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); 6100 } 6101 6102 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) 6103 { 6104 ImGuiWindow* window = GetCurrentWindow(); 6105 if (window->SkipItems) 6106 return false; 6107 6108 ImGuiContext& g = *GImGui; 6109 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); 6110 return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); 6111 } 6112 6113 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) 6114 { 6115 return TreeNodeExV(str_id, 0, fmt, args); 6116 } 6117 6118 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) 6119 { 6120 return TreeNodeExV(ptr_id, 0, fmt, args); 6121 } 6122 6123 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) 6124 { 6125 va_list args; 6126 va_start(args, fmt); 6127 bool is_open = TreeNodeExV(str_id, flags, fmt, args); 6128 va_end(args); 6129 return is_open; 6130 } 6131 6132 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) 6133 { 6134 va_list args; 6135 va_start(args, fmt); 6136 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); 6137 va_end(args); 6138 return is_open; 6139 } 6140 6141 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) 6142 { 6143 va_list args; 6144 va_start(args, fmt); 6145 bool is_open = TreeNodeExV(str_id, 0, fmt, args); 6146 va_end(args); 6147 return is_open; 6148 } 6149 6150 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) 6151 { 6152 va_list args; 6153 va_start(args, fmt); 6154 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); 6155 va_end(args); 6156 return is_open; 6157 } 6158 6159 bool ImGui::TreeNode(const char* label) 6160 { 6161 ImGuiWindow* window = GetCurrentWindow(); 6162 if (window->SkipItems) 6163 return false; 6164 return TreeNodeBehavior(window->GetID(label), 0, label, NULL); 6165 } 6166 6167 void ImGui::TreeAdvanceToLabelPos() 6168 { 6169 ImGuiContext& g = *GImGui; 6170 g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); 6171 } 6172 6173 // Horizontal distance preceding label when using TreeNode() or Bullet() 6174 float ImGui::GetTreeNodeToLabelSpacing() 6175 { 6176 ImGuiContext& g = *GImGui; 6177 return g.FontSize + (g.Style.FramePadding.x * 2.0f); 6178 } 6179 6180 void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiSetCond cond) 6181 { 6182 ImGuiContext& g = *GImGui; 6183 g.SetNextTreeNodeOpenVal = is_open; 6184 g.SetNextTreeNodeOpenCond = cond ? cond : ImGuiSetCond_Always; 6185 } 6186 6187 void ImGui::PushID(const char* str_id) 6188 { 6189 ImGuiWindow* window = GetCurrentWindow(); 6190 window->IDStack.push_back(window->GetID(str_id)); 6191 } 6192 6193 void ImGui::PushID(const char* str_id_begin, const char* str_id_end) 6194 { 6195 ImGuiWindow* window = GetCurrentWindow(); 6196 window->IDStack.push_back(window->GetID(str_id_begin, str_id_end)); 6197 } 6198 6199 void ImGui::PushID(const void* ptr_id) 6200 { 6201 ImGuiWindow* window = GetCurrentWindow(); 6202 window->IDStack.push_back(window->GetID(ptr_id)); 6203 } 6204 6205 void ImGui::PushID(int int_id) 6206 { 6207 const void* ptr_id = (void*)(intptr_t)int_id; 6208 ImGuiWindow* window = GetCurrentWindow(); 6209 window->IDStack.push_back(window->GetID(ptr_id)); 6210 } 6211 6212 void ImGui::PopID() 6213 { 6214 ImGuiWindow* window = GetCurrentWindow(); 6215 window->IDStack.pop_back(); 6216 } 6217 6218 ImGuiID ImGui::GetID(const char* str_id) 6219 { 6220 return GImGui->CurrentWindow->GetID(str_id); 6221 } 6222 6223 ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) 6224 { 6225 return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); 6226 } 6227 6228 ImGuiID ImGui::GetID(const void* ptr_id) 6229 { 6230 return GImGui->CurrentWindow->GetID(ptr_id); 6231 } 6232 6233 void ImGui::Bullet() 6234 { 6235 ImGuiWindow* window = GetCurrentWindow(); 6236 if (window->SkipItems) 6237 return; 6238 6239 ImGuiContext& g = *GImGui; 6240 const ImGuiStyle& style = g.Style; 6241 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); 6242 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); 6243 ItemSize(bb); 6244 if (!ItemAdd(bb, NULL)) 6245 { 6246 SameLine(0, style.FramePadding.x*2); 6247 return; 6248 } 6249 6250 // Render and stay on same line 6251 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); 6252 SameLine(0, style.FramePadding.x*2); 6253 } 6254 6255 // Text with a little bullet aligned to the typical tree node. 6256 void ImGui::BulletTextV(const char* fmt, va_list args) 6257 { 6258 ImGuiWindow* window = GetCurrentWindow(); 6259 if (window->SkipItems) 6260 return; 6261 6262 ImGuiContext& g = *GImGui; 6263 const ImGuiStyle& style = g.Style; 6264 6265 const char* text_begin = g.TempBuffer; 6266 const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); 6267 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); 6268 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it 6269 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); 6270 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 6271 ItemSize(bb); 6272 if (!ItemAdd(bb, NULL)) 6273 return; 6274 6275 // Render 6276 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); 6277 RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); 6278 } 6279 6280 void ImGui::BulletText(const char* fmt, ...) 6281 { 6282 va_list args; 6283 va_start(args, fmt); 6284 BulletTextV(fmt, args); 6285 va_end(args); 6286 } 6287 6288 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size) 6289 { 6290 if (data_type == ImGuiDataType_Int) 6291 ImFormatString(buf, buf_size, display_format, *(int*)data_ptr); 6292 else if (data_type == ImGuiDataType_Float) 6293 ImFormatString(buf, buf_size, display_format, *(float*)data_ptr); 6294 } 6295 6296 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size) 6297 { 6298 if (data_type == ImGuiDataType_Int) 6299 { 6300 if (decimal_precision < 0) 6301 ImFormatString(buf, buf_size, "%d", *(int*)data_ptr); 6302 else 6303 ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr); 6304 } 6305 else if (data_type == ImGuiDataType_Float) 6306 { 6307 if (decimal_precision < 0) 6308 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? 6309 else 6310 ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr); 6311 } 6312 } 6313 6314 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2)// Store into value1 6315 { 6316 if (data_type == ImGuiDataType_Int) 6317 { 6318 if (op == '+') 6319 *(int*)value1 = *(int*)value1 + *(const int*)value2; 6320 else if (op == '-') 6321 *(int*)value1 = *(int*)value1 - *(const int*)value2; 6322 } 6323 else if (data_type == ImGuiDataType_Float) 6324 { 6325 if (op == '+') 6326 *(float*)value1 = *(float*)value1 + *(const float*)value2; 6327 else if (op == '-') 6328 *(float*)value1 = *(float*)value1 - *(const float*)value2; 6329 } 6330 } 6331 6332 // User can input math operators (e.g. +100) to edit a numerical values. 6333 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format) 6334 { 6335 while (ImCharIsSpace(*buf)) 6336 buf++; 6337 6338 // We don't support '-' op because it would conflict with inputing negative value. 6339 // Instead you can use +-100 to subtract from an existing value 6340 char op = buf[0]; 6341 if (op == '+' || op == '*' || op == '/') 6342 { 6343 buf++; 6344 while (ImCharIsSpace(*buf)) 6345 buf++; 6346 } 6347 else 6348 { 6349 op = 0; 6350 } 6351 if (!buf[0]) 6352 return false; 6353 6354 if (data_type == ImGuiDataType_Int) 6355 { 6356 if (!scalar_format) 6357 scalar_format = "%d"; 6358 int* v = (int*)data_ptr; 6359 const int old_v = *v; 6360 int arg0 = *v; 6361 if (op && sscanf(initial_value_buf, scalar_format, &arg0) < 1) 6362 return false; 6363 6364 // 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 6365 float arg1 = 0.0f; 6366 if (op == '+') { if (sscanf(buf, "%f", &arg1) == 1) *v = (int)(arg0 + arg1); } // Add (use "+-" to subtract) 6367 else if (op == '*') { if (sscanf(buf, "%f", &arg1) == 1) *v = (int)(arg0 * arg1); } // Multiply 6368 else if (op == '/') { if (sscanf(buf, "%f", &arg1) == 1 && arg1 != 0.0f) *v = (int)(arg0 / arg1); }// Divide 6369 else { if (sscanf(buf, scalar_format, &arg0) == 1) *v = arg0; } // Assign constant 6370 return (old_v != *v); 6371 } 6372 else if (data_type == ImGuiDataType_Float) 6373 { 6374 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in 6375 scalar_format = "%f"; 6376 float* v = (float*)data_ptr; 6377 const float old_v = *v; 6378 float arg0 = *v; 6379 if (op && sscanf(initial_value_buf, scalar_format, &arg0) < 1) 6380 return false; 6381 6382 float arg1 = 0.0f; 6383 if (sscanf(buf, scalar_format, &arg1) < 1) 6384 return false; 6385 if (op == '+') { *v = arg0 + arg1; } // Add (use "+-" to subtract) 6386 else if (op == '*') { *v = arg0 * arg1; } // Multiply 6387 else if (op == '/') { if (arg1 != 0.0f) *v = arg0 / arg1; } // Divide 6388 else { *v = arg1; } // Assign constant 6389 return (old_v != *v); 6390 } 6391 6392 return false; 6393 } 6394 6395 // Create text input in place of a slider (when CTRL+Clicking on slider) 6396 bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision) 6397 { 6398 ImGuiContext& g = *GImGui; 6399 ImGuiWindow* window = GetCurrentWindow(); 6400 6401 // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) 6402 SetActiveID(g.ScalarAsInputTextId, window); 6403 SetHoveredID(0); 6404 FocusableItemUnregister(window); 6405 6406 char buf[32]; 6407 DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf)); 6408 bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); 6409 if (g.ScalarAsInputTextId == 0) 6410 { 6411 // First frame 6412 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) 6413 g.ScalarAsInputTextId = g.ActiveId; 6414 SetHoveredID(id); 6415 } 6416 else if (g.ActiveId != g.ScalarAsInputTextId) 6417 { 6418 // Release 6419 g.ScalarAsInputTextId = 0; 6420 } 6421 if (text_value_changed) 6422 return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL); 6423 return false; 6424 } 6425 6426 // Parse display precision back from the display format string 6427 int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) 6428 { 6429 int precision = default_precision; 6430 while ((fmt = strchr(fmt, '%')) != NULL) 6431 { 6432 fmt++; 6433 if (fmt[0] == '%') { fmt++; continue; } // Ignore "%%" 6434 while (*fmt >= '0' && *fmt <= '9') 6435 fmt++; 6436 if (*fmt == '.') 6437 { 6438 precision = atoi(fmt + 1); 6439 if (precision < 0 || precision > 10) 6440 precision = default_precision; 6441 } 6442 break; 6443 } 6444 return precision; 6445 } 6446 6447 float ImGui::RoundScalar(float value, int decimal_precision) 6448 { 6449 // Round past decimal precision 6450 // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0 6451 // FIXME: Investigate better rounding methods 6452 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 }; 6453 float min_step = (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision); 6454 bool negative = value < 0.0f; 6455 value = fabsf(value); 6456 float remainder = fmodf(value, min_step); 6457 if (remainder <= min_step*0.5f) 6458 value -= remainder; 6459 else 6460 value += (min_step - remainder); 6461 return negative ? -value : value; 6462 } 6463 6464 static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) 6465 { 6466 if (v_min == v_max) 6467 return 0.0f; 6468 6469 const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); 6470 const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); 6471 if (is_non_linear) 6472 { 6473 if (v_clamped < 0.0f) 6474 { 6475 const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f,v_max) - v_min); 6476 return (1.0f - powf(f, 1.0f/power)) * linear_zero_pos; 6477 } 6478 else 6479 { 6480 const float f = (v_clamped - ImMax(0.0f,v_min)) / (v_max - ImMax(0.0f,v_min)); 6481 return linear_zero_pos + powf(f, 1.0f/power) * (1.0f - linear_zero_pos); 6482 } 6483 } 6484 6485 // Linear slider 6486 return (v_clamped - v_min) / (v_max - v_min); 6487 } 6488 6489 bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags) 6490 { 6491 ImGuiContext& g = *GImGui; 6492 ImGuiWindow* window = GetCurrentWindow(); 6493 const ImGuiStyle& style = g.Style; 6494 6495 // Draw frame 6496 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); 6497 6498 const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); 6499 const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; 6500 6501 const float grab_padding = 2.0f; 6502 const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); 6503 float grab_sz; 6504 if (decimal_precision > 0) 6505 grab_sz = ImMin(style.GrabMinSize, slider_sz); 6506 else 6507 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 6508 const float slider_usable_sz = slider_sz - grab_sz; 6509 const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz*0.5f; 6510 const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz*0.5f; 6511 6512 // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f 6513 float linear_zero_pos = 0.0f; // 0.0->1.0f 6514 if (v_min * v_max < 0.0f) 6515 { 6516 // Different sign 6517 const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); 6518 const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); 6519 linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); 6520 } 6521 else 6522 { 6523 // Same sign 6524 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; 6525 } 6526 6527 // Process clicking on the slider 6528 bool value_changed = false; 6529 if (g.ActiveId == id) 6530 { 6531 if (g.IO.MouseDown[0]) 6532 { 6533 const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; 6534 float clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; 6535 if (!is_horizontal) 6536 clicked_t = 1.0f - clicked_t; 6537 6538 float new_value; 6539 if (is_non_linear) 6540 { 6541 // Account for logarithmic scale on both sides of the zero 6542 if (clicked_t < linear_zero_pos) 6543 { 6544 // Negative: rescale to the negative range before powering 6545 float a = 1.0f - (clicked_t / linear_zero_pos); 6546 a = powf(a, power); 6547 new_value = ImLerp(ImMin(v_max,0.0f), v_min, a); 6548 } 6549 else 6550 { 6551 // Positive: rescale to the positive range before powering 6552 float a; 6553 if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f) 6554 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); 6555 else 6556 a = clicked_t; 6557 a = powf(a, power); 6558 new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); 6559 } 6560 } 6561 else 6562 { 6563 // Linear slider 6564 new_value = ImLerp(v_min, v_max, clicked_t); 6565 } 6566 6567 // Round past decimal precision 6568 new_value = RoundScalar(new_value, decimal_precision); 6569 if (*v != new_value) 6570 { 6571 *v = new_value; 6572 value_changed = true; 6573 } 6574 } 6575 else 6576 { 6577 ClearActiveID(); 6578 } 6579 } 6580 6581 // Calculate slider grab positioning 6582 float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); 6583 6584 // Draw 6585 if (!is_horizontal) 6586 grab_t = 1.0f - grab_t; 6587 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); 6588 ImRect grab_bb; 6589 if (is_horizontal) 6590 grab_bb = ImRect(ImVec2(grab_pos - grab_sz*0.5f, frame_bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz*0.5f, frame_bb.Max.y - grab_padding)); 6591 else 6592 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)); 6593 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); 6594 6595 return value_changed; 6596 } 6597 6598 // Use power!=1.0 for logarithmic sliders. 6599 // Adjust display_format to decorate the value with a prefix or a suffix. 6600 // "%.3f" 1.234 6601 // "%5.2f secs" 01.23 secs 6602 // "Gold: %.0f" Gold: 1 6603 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power) 6604 { 6605 ImGuiWindow* window = GetCurrentWindow(); 6606 if (window->SkipItems) 6607 return false; 6608 6609 ImGuiContext& g = *GImGui; 6610 const ImGuiStyle& style = g.Style; 6611 const ImGuiID id = window->GetID(label); 6612 const float w = CalcItemWidth(); 6613 6614 const ImVec2 label_size = CalcTextSize(label, NULL, true); 6615 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); 6616 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)); 6617 6618 // NB- we don't call ItemSize() yet because we may turn into a text edit box below 6619 if (!ItemAdd(total_bb, &id)) 6620 { 6621 ItemSize(total_bb, style.FramePadding.y); 6622 return false; 6623 } 6624 6625 const bool hovered = IsHovered(frame_bb, id); 6626 if (hovered) 6627 SetHoveredID(id); 6628 6629 if (!display_format) 6630 display_format = "%.3f"; 6631 int decimal_precision = ParseFormatPrecision(display_format, 3); 6632 6633 // Tabbing or CTRL-clicking on Slider turns it into an input box 6634 bool start_text_input = false; 6635 const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id); 6636 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0])) 6637 { 6638 SetActiveID(id, window); 6639 FocusWindow(window); 6640 6641 if (tab_focus_requested || g.IO.KeyCtrl) 6642 { 6643 start_text_input = true; 6644 g.ScalarAsInputTextId = 0; 6645 } 6646 } 6647 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) 6648 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); 6649 6650 ItemSize(total_bb, style.FramePadding.y); 6651 6652 // Actual slider behavior + render grab 6653 const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision); 6654 6655 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. 6656 char value_buf[64]; 6657 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); 6658 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); 6659 6660 if (label_size.x > 0.0f) 6661 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); 6662 6663 return value_changed; 6664 } 6665 6666 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power) 6667 { 6668 ImGuiWindow* window = GetCurrentWindow(); 6669 if (window->SkipItems) 6670 return false; 6671 6672 ImGuiContext& g = *GImGui; 6673 const ImGuiStyle& style = g.Style; 6674 const ImGuiID id = window->GetID(label); 6675 6676 const ImVec2 label_size = CalcTextSize(label, NULL, true); 6677 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); 6678 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)); 6679 6680 ItemSize(bb, style.FramePadding.y); 6681 if (!ItemAdd(frame_bb, &id)) 6682 return false; 6683 6684 const bool hovered = IsHovered(frame_bb, id); 6685 if (hovered) 6686 SetHoveredID(id); 6687 6688 if (!display_format) 6689 display_format = "%.3f"; 6690 int decimal_precision = ParseFormatPrecision(display_format, 3); 6691 6692 if (hovered && g.IO.MouseClicked[0]) 6693 { 6694 SetActiveID(id, window); 6695 FocusWindow(window); 6696 } 6697 6698 // Actual slider behavior + render grab 6699 bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical); 6700 6701 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. 6702 // For the vertical slider we allow centered text to overlap the frame padding 6703 char value_buf[64]; 6704 char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); 6705 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)); 6706 if (label_size.x > 0.0f) 6707 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); 6708 6709 return value_changed; 6710 } 6711 6712 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) 6713 { 6714 float v_deg = (*v_rad) * 360.0f / (2*IM_PI); 6715 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); 6716 *v_rad = v_deg * (2*IM_PI) / 360.0f; 6717 return value_changed; 6718 } 6719 6720 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format) 6721 { 6722 if (!display_format) 6723 display_format = "%.0f"; 6724 float v_f = (float)*v; 6725 bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); 6726 *v = (int)v_f; 6727 return value_changed; 6728 } 6729 6730 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format) 6731 { 6732 if (!display_format) 6733 display_format = "%.0f"; 6734 float v_f = (float)*v; 6735 bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); 6736 *v = (int)v_f; 6737 return value_changed; 6738 } 6739 6740 // Add multiple sliders on 1 line for compact edition of multiple components 6741 bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power) 6742 { 6743 ImGuiWindow* window = GetCurrentWindow(); 6744 if (window->SkipItems) 6745 return false; 6746 6747 ImGuiContext& g = *GImGui; 6748 bool value_changed = false; 6749 BeginGroup(); 6750 PushID(label); 6751 PushMultiItemsWidths(components); 6752 for (int i = 0; i < components; i++) 6753 { 6754 PushID(i); 6755 value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power); 6756 SameLine(0, g.Style.ItemInnerSpacing.x); 6757 PopID(); 6758 PopItemWidth(); 6759 } 6760 PopID(); 6761 6762 TextUnformatted(label, FindRenderedTextEnd(label)); 6763 EndGroup(); 6764 6765 return value_changed; 6766 } 6767 6768 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) 6769 { 6770 return SliderFloatN(label, v, 2, v_min, v_max, display_format, power); 6771 } 6772 6773 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) 6774 { 6775 return SliderFloatN(label, v, 3, v_min, v_max, display_format, power); 6776 } 6777 6778 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power) 6779 { 6780 return SliderFloatN(label, v, 4, v_min, v_max, display_format, power); 6781 } 6782 6783 bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format) 6784 { 6785 ImGuiWindow* window = GetCurrentWindow(); 6786 if (window->SkipItems) 6787 return false; 6788 6789 ImGuiContext& g = *GImGui; 6790 bool value_changed = false; 6791 BeginGroup(); 6792 PushID(label); 6793 PushMultiItemsWidths(components); 6794 for (int i = 0; i < components; i++) 6795 { 6796 PushID(i); 6797 value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format); 6798 SameLine(0, g.Style.ItemInnerSpacing.x); 6799 PopID(); 6800 PopItemWidth(); 6801 } 6802 PopID(); 6803 6804 TextUnformatted(label, FindRenderedTextEnd(label)); 6805 EndGroup(); 6806 6807 return value_changed; 6808 } 6809 6810 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format) 6811 { 6812 return SliderIntN(label, v, 2, v_min, v_max, display_format); 6813 } 6814 6815 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format) 6816 { 6817 return SliderIntN(label, v, 3, v_min, v_max, display_format); 6818 } 6819 6820 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format) 6821 { 6822 return SliderIntN(label, v, 4, v_min, v_max, display_format); 6823 } 6824 6825 bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power) 6826 { 6827 ImGuiContext& g = *GImGui; 6828 const ImGuiStyle& style = g.Style; 6829 6830 // Draw frame 6831 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); 6832 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); 6833 6834 bool value_changed = false; 6835 6836 // Process clicking on the drag 6837 if (g.ActiveId == id) 6838 { 6839 if (g.IO.MouseDown[0]) 6840 { 6841 if (g.ActiveIdIsJustActivated) 6842 { 6843 // Lock current value on click 6844 g.DragCurrentValue = *v; 6845 g.DragLastMouseDelta = ImVec2(0.f, 0.f); 6846 } 6847 6848 float v_cur = g.DragCurrentValue; 6849 const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f); 6850 if (fabsf(mouse_drag_delta.x - g.DragLastMouseDelta.x) > 0.0f) 6851 { 6852 float speed = v_speed; 6853 if (speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX) 6854 speed = (v_max - v_min) * g.DragSpeedDefaultRatio; 6855 if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f) 6856 speed = speed * g.DragSpeedScaleFast; 6857 if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f) 6858 speed = speed * g.DragSpeedScaleSlow; 6859 6860 float delta = (mouse_drag_delta.x - g.DragLastMouseDelta.x) * speed; 6861 if (fabsf(power - 1.0f) > 0.001f) 6862 { 6863 // Logarithmic curve on both side of 0.0 6864 float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur; 6865 float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f; 6866 float v1 = powf(v0_abs, 1.0f / power) + (delta * v0_sign); 6867 float v1_abs = v1 >= 0.0f ? v1 : -v1; 6868 float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line 6869 v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign 6870 } 6871 else 6872 { 6873 v_cur += delta; 6874 } 6875 g.DragLastMouseDelta.x = mouse_drag_delta.x; 6876 6877 // Clamp 6878 if (v_min < v_max) 6879 v_cur = ImClamp(v_cur, v_min, v_max); 6880 g.DragCurrentValue = v_cur; 6881 } 6882 6883 // Round to user desired precision, then apply 6884 v_cur = RoundScalar(v_cur, decimal_precision); 6885 if (*v != v_cur) 6886 { 6887 *v = v_cur; 6888 value_changed = true; 6889 } 6890 } 6891 else 6892 { 6893 ClearActiveID(); 6894 } 6895 } 6896 6897 return value_changed; 6898 } 6899 6900 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) 6901 { 6902 ImGuiWindow* window = GetCurrentWindow(); 6903 if (window->SkipItems) 6904 return false; 6905 6906 ImGuiContext& g = *GImGui; 6907 const ImGuiStyle& style = g.Style; 6908 const ImGuiID id = window->GetID(label); 6909 const float w = CalcItemWidth(); 6910 6911 const ImVec2 label_size = CalcTextSize(label, NULL, true); 6912 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); 6913 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); 6914 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)); 6915 6916 // NB- we don't call ItemSize() yet because we may turn into a text edit box below 6917 if (!ItemAdd(total_bb, &id)) 6918 { 6919 ItemSize(total_bb, style.FramePadding.y); 6920 return false; 6921 } 6922 6923 const bool hovered = IsHovered(frame_bb, id); 6924 if (hovered) 6925 SetHoveredID(id); 6926 6927 if (!display_format) 6928 display_format = "%.3f"; 6929 int decimal_precision = ParseFormatPrecision(display_format, 3); 6930 6931 // Tabbing or CTRL-clicking on Drag turns it into an input box 6932 bool start_text_input = false; 6933 const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id); 6934 if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] | g.IO.MouseDoubleClicked[0]))) 6935 { 6936 SetActiveID(id, window); 6937 FocusWindow(window); 6938 6939 if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0]) 6940 { 6941 start_text_input = true; 6942 g.ScalarAsInputTextId = 0; 6943 } 6944 } 6945 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) 6946 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); 6947 6948 // Actual drag behavior 6949 ItemSize(total_bb, style.FramePadding.y); 6950 const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power); 6951 6952 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. 6953 char value_buf[64]; 6954 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); 6955 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); 6956 6957 if (label_size.x > 0.0f) 6958 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); 6959 6960 return value_changed; 6961 } 6962 6963 bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power) 6964 { 6965 ImGuiWindow* window = GetCurrentWindow(); 6966 if (window->SkipItems) 6967 return false; 6968 6969 ImGuiContext& g = *GImGui; 6970 bool value_changed = false; 6971 BeginGroup(); 6972 PushID(label); 6973 PushMultiItemsWidths(components); 6974 for (int i = 0; i < components; i++) 6975 { 6976 PushID(i); 6977 value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power); 6978 SameLine(0, g.Style.ItemInnerSpacing.x); 6979 PopID(); 6980 PopItemWidth(); 6981 } 6982 PopID(); 6983 6984 TextUnformatted(label, FindRenderedTextEnd(label)); 6985 EndGroup(); 6986 6987 return value_changed; 6988 } 6989 6990 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power) 6991 { 6992 return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power); 6993 } 6994 6995 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power) 6996 { 6997 return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power); 6998 } 6999 7000 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power) 7001 { 7002 return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power); 7003 } 7004 7005 bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* display_format, const char* display_format_max, float power) 7006 { 7007 ImGuiWindow* window = GetCurrentWindow(); 7008 if (window->SkipItems) 7009 return false; 7010 7011 ImGuiContext& g = *GImGui; 7012 PushID(label); 7013 BeginGroup(); 7014 PushMultiItemsWidths(2); 7015 7016 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); 7017 PopItemWidth(); 7018 SameLine(0, g.Style.ItemInnerSpacing.x); 7019 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); 7020 PopItemWidth(); 7021 SameLine(0, g.Style.ItemInnerSpacing.x); 7022 7023 TextUnformatted(label, FindRenderedTextEnd(label)); 7024 EndGroup(); 7025 PopID(); 7026 7027 return value_changed; 7028 } 7029 7030 // NB: v_speed is float to allow adjusting the drag speed with more precision 7031 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) 7032 { 7033 if (!display_format) 7034 display_format = "%.0f"; 7035 float v_f = (float)*v; 7036 bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format); 7037 *v = (int)v_f; 7038 return value_changed; 7039 } 7040 7041 bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format) 7042 { 7043 ImGuiWindow* window = GetCurrentWindow(); 7044 if (window->SkipItems) 7045 return false; 7046 7047 ImGuiContext& g = *GImGui; 7048 bool value_changed = false; 7049 BeginGroup(); 7050 PushID(label); 7051 PushMultiItemsWidths(components); 7052 for (int i = 0; i < components; i++) 7053 { 7054 PushID(i); 7055 value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format); 7056 SameLine(0, g.Style.ItemInnerSpacing.x); 7057 PopID(); 7058 PopItemWidth(); 7059 } 7060 PopID(); 7061 7062 TextUnformatted(label, FindRenderedTextEnd(label)); 7063 EndGroup(); 7064 7065 return value_changed; 7066 } 7067 7068 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format) 7069 { 7070 return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format); 7071 } 7072 7073 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format) 7074 { 7075 return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format); 7076 } 7077 7078 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format) 7079 { 7080 return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format); 7081 } 7082 7083 bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* display_format, const char* display_format_max) 7084 { 7085 ImGuiWindow* window = GetCurrentWindow(); 7086 if (window->SkipItems) 7087 return false; 7088 7089 ImGuiContext& g = *GImGui; 7090 PushID(label); 7091 BeginGroup(); 7092 PushMultiItemsWidths(2); 7093 7094 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); 7095 PopItemWidth(); 7096 SameLine(0, g.Style.ItemInnerSpacing.x); 7097 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); 7098 PopItemWidth(); 7099 SameLine(0, g.Style.ItemInnerSpacing.x); 7100 7101 TextUnformatted(label, FindRenderedTextEnd(label)); 7102 EndGroup(); 7103 PopID(); 7104 7105 return value_changed; 7106 } 7107 7108 void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) 7109 { 7110 ImGuiWindow* window = GetCurrentWindow(); 7111 if (window->SkipItems) 7112 return; 7113 7114 ImGuiContext& g = *GImGui; 7115 const ImGuiStyle& style = g.Style; 7116 7117 const ImVec2 label_size = CalcTextSize(label, NULL, true); 7118 if (graph_size.x == 0.0f) 7119 graph_size.x = CalcItemWidth(); 7120 if (graph_size.y == 0.0f) 7121 graph_size.y = label_size.y + (style.FramePadding.y * 2); 7122 7123 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); 7124 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); 7125 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)); 7126 ItemSize(total_bb, style.FramePadding.y); 7127 if (!ItemAdd(total_bb, NULL)) 7128 return; 7129 7130 // Determine scale from values if not specified 7131 if (scale_min == FLT_MAX || scale_max == FLT_MAX) 7132 { 7133 float v_min = FLT_MAX; 7134 float v_max = -FLT_MAX; 7135 for (int i = 0; i < values_count; i++) 7136 { 7137 const float v = values_getter(data, i); 7138 v_min = ImMin(v_min, v); 7139 v_max = ImMax(v_max, v); 7140 } 7141 if (scale_min == FLT_MAX) 7142 scale_min = v_min; 7143 if (scale_max == FLT_MAX) 7144 scale_max = v_max; 7145 } 7146 7147 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); 7148 7149 if (values_count > 0) 7150 { 7151 int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); 7152 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); 7153 7154 // Tooltip on hover 7155 int v_hovered = -1; 7156 if (IsHovered(inner_bb, 0)) 7157 { 7158 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); 7159 const int v_idx = (int)(t * item_count); 7160 IM_ASSERT(v_idx >= 0 && v_idx < values_count); 7161 7162 const float v0 = values_getter(data, (v_idx + values_offset) % values_count); 7163 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); 7164 if (plot_type == ImGuiPlotType_Lines) 7165 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); 7166 else if (plot_type == ImGuiPlotType_Histogram) 7167 SetTooltip("%d: %8.4g", v_idx, v0); 7168 v_hovered = v_idx; 7169 } 7170 7171 const float t_step = 1.0f / (float)res_w; 7172 7173 float v0 = values_getter(data, (0 + values_offset) % values_count); 7174 float t0 = 0.0f; 7175 ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) / (scale_max - scale_min)) ); // Point in the normalized space of our target rectangle 7176 7177 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); 7178 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); 7179 7180 for (int n = 0; n < res_w; n++) 7181 { 7182 const float t1 = t0 + t_step; 7183 const int v1_idx = (int)(t0 * item_count + 0.5f); 7184 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); 7185 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); 7186 const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) / (scale_max - scale_min)) ); 7187 7188 // 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. 7189 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); 7190 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, 1.0f)); 7191 if (plot_type == ImGuiPlotType_Lines) 7192 { 7193 window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); 7194 } 7195 else if (plot_type == ImGuiPlotType_Histogram) 7196 { 7197 if (pos1.x >= pos0.x + 2.0f) 7198 pos1.x -= 1.0f; 7199 window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); 7200 } 7201 7202 t0 = t1; 7203 tp0 = tp1; 7204 } 7205 } 7206 7207 // Text overlay 7208 if (overlay_text) 7209 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)); 7210 7211 if (label_size.x > 0.0f) 7212 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); 7213 } 7214 7215 struct ImGuiPlotArrayGetterData 7216 { 7217 const float* Values; 7218 int Stride; 7219 7220 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } 7221 }; 7222 7223 static float Plot_ArrayGetter(void* data, int idx) 7224 { 7225 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; 7226 const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); 7227 return v; 7228 } 7229 7230 void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) 7231 { 7232 ImGuiPlotArrayGetterData data(values, stride); 7233 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); 7234 } 7235 7236 void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) 7237 { 7238 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); 7239 } 7240 7241 void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) 7242 { 7243 ImGuiPlotArrayGetterData data(values, stride); 7244 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); 7245 } 7246 7247 void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) 7248 { 7249 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); 7250 } 7251 7252 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size 7253 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) 7254 { 7255 ImGuiWindow* window = GetCurrentWindow(); 7256 if (window->SkipItems) 7257 return; 7258 7259 ImGuiContext& g = *GImGui; 7260 const ImGuiStyle& style = g.Style; 7261 7262 ImVec2 pos = window->DC.CursorPos; 7263 ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); 7264 ItemSize(bb, style.FramePadding.y); 7265 if (!ItemAdd(bb, NULL)) 7266 return; 7267 7268 // Render 7269 fraction = ImSaturate(fraction); 7270 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); 7271 bb.Reduce(ImVec2(window->BorderSize, window->BorderSize)); 7272 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); 7273 RenderFrame(bb.Min, fill_br, GetColorU32(ImGuiCol_PlotHistogram), false, style.FrameRounding); 7274 7275 // Default displaying the fraction as percentage string, but user can override it 7276 char overlay_buf[32]; 7277 if (!overlay) 7278 { 7279 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); 7280 overlay = overlay_buf; 7281 } 7282 7283 ImVec2 overlay_size = CalcTextSize(overlay, NULL); 7284 if (overlay_size.x > 0.0f) 7285 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); 7286 } 7287 7288 bool ImGui::Checkbox(const char* label, bool* v) 7289 { 7290 ImGuiWindow* window = GetCurrentWindow(); 7291 if (window->SkipItems) 7292 return false; 7293 7294 ImGuiContext& g = *GImGui; 7295 const ImGuiStyle& style = g.Style; 7296 const ImGuiID id = window->GetID(label); 7297 const ImVec2 label_size = CalcTextSize(label, NULL, true); 7298 7299 const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); 7300 ItemSize(check_bb, style.FramePadding.y); 7301 7302 ImRect total_bb = check_bb; 7303 if (label_size.x > 0) 7304 SameLine(0, style.ItemInnerSpacing.x); 7305 const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); 7306 if (label_size.x > 0) 7307 { 7308 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); 7309 total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); 7310 } 7311 7312 if (!ItemAdd(total_bb, &id)) 7313 return false; 7314 7315 bool hovered, held; 7316 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); 7317 if (pressed) 7318 *v = !(*v); 7319 7320 RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); 7321 if (*v) 7322 { 7323 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); 7324 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); 7325 window->DrawList->AddRectFilled(check_bb.Min+ImVec2(pad,pad), check_bb.Max-ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), style.FrameRounding); 7326 } 7327 7328 if (g.LogEnabled) 7329 LogRenderedText(text_bb.GetTL(), *v ? "[x]" : "[ ]"); 7330 if (label_size.x > 0.0f) 7331 RenderText(text_bb.GetTL(), label); 7332 7333 return pressed; 7334 } 7335 7336 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) 7337 { 7338 bool v = ((*flags & flags_value) == flags_value); 7339 bool pressed = Checkbox(label, &v); 7340 if (pressed) 7341 { 7342 if (v) 7343 *flags |= flags_value; 7344 else 7345 *flags &= ~flags_value; 7346 } 7347 7348 return pressed; 7349 } 7350 7351 bool ImGui::RadioButton(const char* label, bool active) 7352 { 7353 ImGuiWindow* window = GetCurrentWindow(); 7354 if (window->SkipItems) 7355 return false; 7356 7357 ImGuiContext& g = *GImGui; 7358 const ImGuiStyle& style = g.Style; 7359 const ImGuiID id = window->GetID(label); 7360 const ImVec2 label_size = CalcTextSize(label, NULL, true); 7361 7362 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)); 7363 ItemSize(check_bb, style.FramePadding.y); 7364 7365 ImRect total_bb = check_bb; 7366 if (label_size.x > 0) 7367 SameLine(0, style.ItemInnerSpacing.x); 7368 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); 7369 if (label_size.x > 0) 7370 { 7371 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); 7372 total_bb.Add(text_bb); 7373 } 7374 7375 if (!ItemAdd(total_bb, &id)) 7376 return false; 7377 7378 ImVec2 center = check_bb.GetCenter(); 7379 center.x = (float)(int)center.x + 0.5f; 7380 center.y = (float)(int)center.y + 0.5f; 7381 const float radius = check_bb.GetHeight() * 0.5f; 7382 7383 bool hovered, held; 7384 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); 7385 7386 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); 7387 if (active) 7388 { 7389 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); 7390 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); 7391 window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); 7392 } 7393 7394 if (window->Flags & ImGuiWindowFlags_ShowBorders) 7395 { 7396 window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16); 7397 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16); 7398 } 7399 7400 if (g.LogEnabled) 7401 LogRenderedText(text_bb.GetTL(), active ? "(x)" : "( )"); 7402 if (label_size.x > 0.0f) 7403 RenderText(text_bb.GetTL(), label); 7404 7405 return pressed; 7406 } 7407 7408 bool ImGui::RadioButton(const char* label, int* v, int v_button) 7409 { 7410 const bool pressed = RadioButton(label, *v == v_button); 7411 if (pressed) 7412 { 7413 *v = v_button; 7414 } 7415 return pressed; 7416 } 7417 7418 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) 7419 { 7420 int line_count = 0; 7421 const char* s = text_begin; 7422 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding 7423 if (c == '\n') 7424 line_count++; 7425 s--; 7426 if (s[0] != '\n' && s[0] != '\r') 7427 line_count++; 7428 *out_text_end = s; 7429 return line_count; 7430 } 7431 7432 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) 7433 { 7434 ImFont* font = GImGui->Font; 7435 const float line_height = GImGui->FontSize; 7436 const float scale = line_height / font->FontSize; 7437 7438 ImVec2 text_size = ImVec2(0,0); 7439 float line_width = 0.0f; 7440 7441 const ImWchar* s = text_begin; 7442 while (s < text_end) 7443 { 7444 unsigned int c = (unsigned int)(*s++); 7445 if (c == '\n') 7446 { 7447 text_size.x = ImMax(text_size.x, line_width); 7448 text_size.y += line_height; 7449 line_width = 0.0f; 7450 if (stop_on_new_line) 7451 break; 7452 continue; 7453 } 7454 if (c == '\r') 7455 continue; 7456 7457 const float char_width = font->GetCharAdvance((unsigned short)c) * scale; 7458 line_width += char_width; 7459 } 7460 7461 if (text_size.x < line_width) 7462 text_size.x = line_width; 7463 7464 if (out_offset) 7465 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n 7466 7467 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n 7468 text_size.y += line_height; 7469 7470 if (remaining) 7471 *remaining = s; 7472 7473 return text_size; 7474 } 7475 7476 // 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) 7477 namespace ImGuiStb 7478 { 7479 7480 static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } 7481 static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } 7482 static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } 7483 static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } 7484 static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; 7485 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) 7486 { 7487 const ImWchar* text = obj->Text.Data; 7488 const ImWchar* text_remaining = NULL; 7489 const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); 7490 r->x0 = 0.0f; 7491 r->x1 = size.x; 7492 r->baseline_y_delta = size.y; 7493 r->ymin = 0.0f; 7494 r->ymax = size.y; 7495 r->num_chars = (int)(text_remaining - (text + line_start_idx)); 7496 } 7497 7498 static bool is_separator(unsigned int c) { return ImCharIsSpace(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } 7499 static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->Text[idx-1] ) && !is_separator( obj->Text[idx] ) ) : 1; } 7500 static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } 7501 #ifdef __APPLE__ // FIXME: Move setting to IO structure 7502 static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; } 7503 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } 7504 #else 7505 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } 7506 #endif 7507 #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h 7508 #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL 7509 7510 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) 7511 { 7512 ImWchar* dst = obj->Text.Data + pos; 7513 7514 // We maintain our buffer length in both UTF-8 and wchar formats 7515 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); 7516 obj->CurLenW -= n; 7517 7518 // Offset remaining text 7519 const ImWchar* src = obj->Text.Data + pos + n; 7520 while (ImWchar c = *src++) 7521 *dst++ = c; 7522 *dst = '\0'; 7523 } 7524 7525 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) 7526 { 7527 const int text_len = obj->CurLenW; 7528 IM_ASSERT(pos <= text_len); 7529 if (new_text_len + text_len + 1 > obj->Text.Size) 7530 return false; 7531 7532 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); 7533 if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA) 7534 return false; 7535 7536 ImWchar* text = obj->Text.Data; 7537 if (pos != text_len) 7538 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); 7539 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); 7540 7541 obj->CurLenW += new_text_len; 7542 obj->CurLenA += new_text_len_utf8; 7543 obj->Text[obj->CurLenW] = '\0'; 7544 7545 return true; 7546 } 7547 7548 // 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) 7549 #define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left 7550 #define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right 7551 #define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up 7552 #define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down 7553 #define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line 7554 #define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line 7555 #define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text 7556 #define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text 7557 #define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor 7558 #define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor 7559 #define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo 7560 #define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo 7561 #define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word 7562 #define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word 7563 #define STB_TEXTEDIT_K_SHIFT 0x20000 7564 7565 #define STB_TEXTEDIT_IMPLEMENTATION 7566 #include "stb_textedit.h" 7567 7568 } 7569 7570 void ImGuiTextEditState::OnKeyPressed(int key) 7571 { 7572 stb_textedit_key(this, &StbState, key); 7573 CursorFollow = true; 7574 CursorAnimReset(); 7575 } 7576 7577 // Public API to manipulate UTF-8 text 7578 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) 7579 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance. 7580 void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) 7581 { 7582 IM_ASSERT(pos + bytes_count <= BufTextLen); 7583 char* dst = Buf + pos; 7584 const char* src = Buf + pos + bytes_count; 7585 while (char c = *src++) 7586 *dst++ = c; 7587 *dst = '\0'; 7588 7589 if (CursorPos + bytes_count >= pos) 7590 CursorPos -= bytes_count; 7591 else if (CursorPos >= pos) 7592 CursorPos = pos; 7593 SelectionStart = SelectionEnd = CursorPos; 7594 BufDirty = true; 7595 BufTextLen -= bytes_count; 7596 } 7597 7598 void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) 7599 { 7600 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); 7601 if (new_text_len + BufTextLen + 1 >= BufSize) 7602 return; 7603 7604 if (BufTextLen != pos) 7605 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); 7606 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); 7607 Buf[BufTextLen + new_text_len] = '\0'; 7608 7609 if (CursorPos >= pos) 7610 CursorPos += new_text_len; 7611 SelectionStart = SelectionEnd = CursorPos; 7612 BufDirty = true; 7613 BufTextLen += new_text_len; 7614 } 7615 7616 // Return false to discard a character. 7617 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) 7618 { 7619 unsigned int c = *p_char; 7620 7621 if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) 7622 { 7623 bool pass = false; 7624 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); 7625 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); 7626 if (!pass) 7627 return false; 7628 } 7629 7630 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. 7631 return false; 7632 7633 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank)) 7634 { 7635 if (flags & ImGuiInputTextFlags_CharsDecimal) 7636 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) 7637 return false; 7638 7639 if (flags & ImGuiInputTextFlags_CharsHexadecimal) 7640 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) 7641 return false; 7642 7643 if (flags & ImGuiInputTextFlags_CharsUppercase) 7644 if (c >= 'a' && c <= 'z') 7645 *p_char = (c += (unsigned int)('A'-'a')); 7646 7647 if (flags & ImGuiInputTextFlags_CharsNoBlank) 7648 if (ImCharIsSpace(c)) 7649 return false; 7650 } 7651 7652 if (flags & ImGuiInputTextFlags_CallbackCharFilter) 7653 { 7654 ImGuiTextEditCallbackData callback_data; 7655 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); 7656 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; 7657 callback_data.EventChar = (ImWchar)c; 7658 callback_data.Flags = flags; 7659 callback_data.UserData = user_data; 7660 if (callback(&callback_data) != 0) 7661 return false; 7662 *p_char = callback_data.EventChar; 7663 if (!callback_data.EventChar) 7664 return false; 7665 } 7666 7667 return true; 7668 } 7669 7670 // Edit a string of text 7671 // 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. 7672 // 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 7673 bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) 7674 { 7675 ImGuiWindow* window = GetCurrentWindow(); 7676 if (window->SkipItems) 7677 return false; 7678 7679 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) 7680 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) 7681 7682 ImGuiContext& g = *GImGui; 7683 const ImGuiIO& io = g.IO; 7684 const ImGuiStyle& style = g.Style; 7685 7686 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; 7687 const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; 7688 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; 7689 7690 if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn 7691 BeginGroup(); 7692 const ImGuiID id = window->GetID(label); 7693 const ImVec2 label_size = CalcTextSize(label, NULL, true); 7694 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 7695 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); 7696 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)); 7697 7698 ImGuiWindow* draw_window = window; 7699 if (is_multiline) 7700 { 7701 if (!BeginChildFrame(id, frame_bb.GetSize())) 7702 { 7703 EndChildFrame(); 7704 EndGroup(); 7705 return false; 7706 } 7707 draw_window = GetCurrentWindow(); 7708 size.x -= draw_window->ScrollbarSizes.x; 7709 } 7710 else 7711 { 7712 ItemSize(total_bb, style.FramePadding.y); 7713 if (!ItemAdd(total_bb, &id)) 7714 return false; 7715 } 7716 7717 // Password pushes a temporary font with only a fallback glyph 7718 if (is_password) 7719 { 7720 const ImFont::Glyph* glyph = g.Font->FindGlyph('*'); 7721 ImFont* password_font = &g.InputTextPasswordFont; 7722 password_font->FontSize = g.Font->FontSize; 7723 password_font->Scale = g.Font->Scale; 7724 password_font->DisplayOffset = g.Font->DisplayOffset; 7725 password_font->Ascent = g.Font->Ascent; 7726 password_font->Descent = g.Font->Descent; 7727 password_font->ContainerAtlas = g.Font->ContainerAtlas; 7728 password_font->FallbackGlyph = glyph; 7729 password_font->FallbackXAdvance = glyph->XAdvance; 7730 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexXAdvance.empty() && password_font->IndexLookup.empty()); 7731 PushFont(password_font); 7732 } 7733 7734 // NB: we are only allowed to access 'edit_state' if we are the active widget. 7735 ImGuiTextEditState& edit_state = g.InputTextState; 7736 7737 const bool focus_requested = FocusableItemRegister(window, g.ActiveId == id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing 7738 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); 7739 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; 7740 7741 const bool hovered = IsHovered(frame_bb, id); 7742 if (hovered) 7743 { 7744 SetHoveredID(id); 7745 g.MouseCursor = ImGuiMouseCursor_TextInput; 7746 } 7747 const bool user_clicked = hovered && io.MouseClicked[0]; 7748 const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); 7749 7750 bool select_all = (g.ActiveId != id) && (flags & ImGuiInputTextFlags_AutoSelectAll) != 0; 7751 if (focus_requested || user_clicked || user_scrolled) 7752 { 7753 if (g.ActiveId != id) 7754 { 7755 // Start edition 7756 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) 7757 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) 7758 const int prev_len_w = edit_state.CurLenW; 7759 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. 7760 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. 7761 ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size); 7762 const char* buf_end = NULL; 7763 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); 7764 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. 7765 edit_state.CursorAnimReset(); 7766 7767 // Preserve cursor position and undo/redo stack if we come back to same widget 7768 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). 7769 const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW); 7770 if (recycle_state) 7771 { 7772 // Recycle existing cursor/selection/undo stack but clamp position 7773 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. 7774 edit_state.CursorClamp(); 7775 } 7776 else 7777 { 7778 edit_state.Id = id; 7779 edit_state.ScrollX = 0.0f; 7780 stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); 7781 if (!is_multiline && focus_requested_by_code) 7782 select_all = true; 7783 } 7784 if (flags & ImGuiInputTextFlags_AlwaysInsertMode) 7785 edit_state.StbState.insert_mode = true; 7786 if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) 7787 select_all = true; 7788 } 7789 SetActiveID(id, window); 7790 FocusWindow(window); 7791 } 7792 else if (io.MouseClicked[0]) 7793 { 7794 // Release focus when we click outside 7795 if (g.ActiveId == id) 7796 ClearActiveID(); 7797 } 7798 7799 bool value_changed = false; 7800 bool enter_pressed = false; 7801 7802 if (g.ActiveId == id) 7803 { 7804 if (!is_editable && !g.ActiveIdIsJustActivated) 7805 { 7806 // When read-only we always use the live data passed to the function 7807 edit_state.Text.resize(buf_size+1); 7808 const char* buf_end = NULL; 7809 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); 7810 edit_state.CurLenA = (int)(buf_end - buf); 7811 edit_state.CursorClamp(); 7812 } 7813 7814 edit_state.BufSizeA = buf_size; 7815 7816 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. 7817 // Down the line we should have a cleaner library-wide concept of Selected vs Active. 7818 g.ActiveIdAllowOverlap = !io.MouseDown[0]; 7819 7820 // Edit in progress 7821 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; 7822 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); 7823 7824 const bool osx_double_click_selects_words = io.OSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text 7825 if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0])) 7826 { 7827 edit_state.SelectAll(); 7828 edit_state.SelectedAllMouseLock = true; 7829 } 7830 else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0]) 7831 { 7832 // Select a word only, OS X style (by simulating keystrokes) 7833 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); 7834 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); 7835 } 7836 else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) 7837 { 7838 stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); 7839 edit_state.CursorAnimReset(); 7840 } 7841 else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) 7842 { 7843 stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); 7844 edit_state.CursorAnimReset(); 7845 edit_state.CursorFollow = true; 7846 } 7847 if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) 7848 edit_state.SelectedAllMouseLock = false; 7849 7850 if (io.InputCharacters[0]) 7851 { 7852 // Process text input (before we check for Return because using some IME will effectively send a Return?) 7853 // 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. 7854 if (!(io.KeyCtrl && !io.KeyAlt) && is_editable) 7855 { 7856 for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) 7857 if (unsigned int c = (unsigned int)io.InputCharacters[n]) 7858 { 7859 // Insert character if they pass filtering 7860 if (!InputTextFilterCharacter(&c, flags, callback, user_data)) 7861 continue; 7862 edit_state.OnKeyPressed((int)c); 7863 } 7864 } 7865 7866 // Consume characters 7867 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); 7868 } 7869 7870 // Handle various key-presses 7871 bool cancel_edit = false; 7872 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); 7873 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 7874 const bool is_wordmove_key_down = io.OSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl 7875 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 7876 7877 if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } 7878 else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } 7879 else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } 7880 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } 7881 else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } 7882 else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } 7883 else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } 7884 else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) 7885 { 7886 if (!edit_state.HasSelection()) 7887 { 7888 if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); 7889 else if (io.OSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); 7890 } 7891 edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); 7892 } 7893 else if (IsKeyPressedMap(ImGuiKey_Enter)) 7894 { 7895 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; 7896 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) 7897 { 7898 ClearActiveID(); 7899 enter_pressed = true; 7900 } 7901 else if (is_editable) 7902 { 7903 unsigned int c = '\n'; // Insert new line 7904 if (InputTextFilterCharacter(&c, flags, callback, user_data)) 7905 edit_state.OnKeyPressed((int)c); 7906 } 7907 } 7908 else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) 7909 { 7910 unsigned int c = '\t'; // Insert TAB 7911 if (InputTextFilterCharacter(&c, flags, callback, user_data)) 7912 edit_state.OnKeyPressed((int)c); 7913 } 7914 else if (IsKeyPressedMap(ImGuiKey_Escape)) { ClearActiveID(); cancel_edit = true; } 7915 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } 7916 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } 7917 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } 7918 else if (is_shortcut_key_only && !is_password && ((IsKeyPressedMap(ImGuiKey_X) && is_editable) || IsKeyPressedMap(ImGuiKey_C)) && (!is_multiline || edit_state.HasSelection())) 7919 { 7920 // Cut, Copy 7921 const bool cut = IsKeyPressedMap(ImGuiKey_X); 7922 if (cut && !edit_state.HasSelection()) 7923 edit_state.SelectAll(); 7924 7925 if (io.SetClipboardTextFn) 7926 { 7927 const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; 7928 const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; 7929 edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1); 7930 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie); 7931 SetClipboardText(edit_state.TempTextBuffer.Data); 7932 } 7933 7934 if (cut) 7935 { 7936 edit_state.CursorFollow = true; 7937 stb_textedit_cut(&edit_state, &edit_state.StbState); 7938 } 7939 } 7940 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V) && is_editable) 7941 { 7942 // Paste 7943 if (const char* clipboard = GetClipboardText()) 7944 { 7945 // Filter pasted buffer 7946 const int clipboard_len = (int)strlen(clipboard); 7947 ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar)); 7948 int clipboard_filtered_len = 0; 7949 for (const char* s = clipboard; *s; ) 7950 { 7951 unsigned int c; 7952 s += ImTextCharFromUtf8(&c, s, NULL); 7953 if (c == 0) 7954 break; 7955 if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data)) 7956 continue; 7957 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; 7958 } 7959 clipboard_filtered[clipboard_filtered_len] = 0; 7960 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation 7961 { 7962 stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); 7963 edit_state.CursorFollow = true; 7964 } 7965 ImGui::MemFree(clipboard_filtered); 7966 } 7967 } 7968 7969 if (cancel_edit) 7970 { 7971 // Restore initial value 7972 if (is_editable) 7973 { 7974 ImStrncpy(buf, edit_state.InitialText.Data, buf_size); 7975 value_changed = true; 7976 } 7977 } 7978 else 7979 { 7980 // Apply new value immediately - copy modified buffer back 7981 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer 7982 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. 7983 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. 7984 if (is_editable) 7985 { 7986 edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4); 7987 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL); 7988 } 7989 7990 // User callback 7991 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) 7992 { 7993 IM_ASSERT(callback != NULL); 7994 7995 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. 7996 ImGuiInputTextFlags event_flag = 0; 7997 ImGuiKey event_key = ImGuiKey_COUNT; 7998 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) 7999 { 8000 event_flag = ImGuiInputTextFlags_CallbackCompletion; 8001 event_key = ImGuiKey_Tab; 8002 } 8003 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) 8004 { 8005 event_flag = ImGuiInputTextFlags_CallbackHistory; 8006 event_key = ImGuiKey_UpArrow; 8007 } 8008 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) 8009 { 8010 event_flag = ImGuiInputTextFlags_CallbackHistory; 8011 event_key = ImGuiKey_DownArrow; 8012 } 8013 else if (flags & ImGuiInputTextFlags_CallbackAlways) 8014 event_flag = ImGuiInputTextFlags_CallbackAlways; 8015 8016 if (event_flag) 8017 { 8018 ImGuiTextEditCallbackData callback_data; 8019 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); 8020 callback_data.EventFlag = event_flag; 8021 callback_data.Flags = flags; 8022 callback_data.UserData = user_data; 8023 callback_data.ReadOnly = !is_editable; 8024 8025 callback_data.EventKey = event_key; 8026 callback_data.Buf = edit_state.TempTextBuffer.Data; 8027 callback_data.BufTextLen = edit_state.CurLenA; 8028 callback_data.BufSize = edit_state.BufSizeA; 8029 callback_data.BufDirty = false; 8030 8031 // 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) 8032 ImWchar* text = edit_state.Text.Data; 8033 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); 8034 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); 8035 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); 8036 8037 // Call user code 8038 callback(&callback_data); 8039 8040 // Read back what user may have modified 8041 IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields 8042 IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA); 8043 IM_ASSERT(callback_data.Flags == flags); 8044 if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); 8045 if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); 8046 if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); 8047 if (callback_data.BufDirty) 8048 { 8049 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! 8050 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL); 8051 edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() 8052 edit_state.CursorAnimReset(); 8053 } 8054 } 8055 } 8056 8057 // Copy back to user buffer 8058 if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0) 8059 { 8060 ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size); 8061 value_changed = true; 8062 } 8063 } 8064 } 8065 8066 // Render 8067 // 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. 8068 const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL; 8069 8070 if (!is_multiline) 8071 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); 8072 8073 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 8074 ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; 8075 ImVec2 text_size(0.f, 0.f); 8076 const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); 8077 if (g.ActiveId == id || is_currently_scrolling) 8078 { 8079 edit_state.CursorAnim += io.DeltaTime; 8080 8081 // This is going to be messy. We need to: 8082 // - Display the text (this alone can be more easily clipped) 8083 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) 8084 // - Measure text height (for scrollbar) 8085 // 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) 8086 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. 8087 const ImWchar* text_begin = edit_state.Text.Data; 8088 ImVec2 cursor_offset, select_start_offset; 8089 8090 { 8091 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. 8092 const ImWchar* searches_input_ptr[2]; 8093 searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; 8094 searches_input_ptr[1] = NULL; 8095 int searches_remaining = 1; 8096 int searches_result_line_number[2] = { -1, -999 }; 8097 if (edit_state.StbState.select_start != edit_state.StbState.select_end) 8098 { 8099 searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); 8100 searches_result_line_number[1] = -1; 8101 searches_remaining++; 8102 } 8103 8104 // Iterate all lines to find our line numbers 8105 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. 8106 searches_remaining += is_multiline ? 1 : 0; 8107 int line_count = 0; 8108 for (const ImWchar* s = text_begin; *s != 0; s++) 8109 if (*s == '\n') 8110 { 8111 line_count++; 8112 if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } 8113 if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } 8114 } 8115 line_count++; 8116 if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; 8117 if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; 8118 8119 // Calculate 2d position by finding the beginning of the line and measuring distance 8120 cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; 8121 cursor_offset.y = searches_result_line_number[0] * g.FontSize; 8122 if (searches_result_line_number[1] >= 0) 8123 { 8124 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; 8125 select_start_offset.y = searches_result_line_number[1] * g.FontSize; 8126 } 8127 8128 // Calculate text height 8129 if (is_multiline) 8130 text_size = ImVec2(size.x, line_count * g.FontSize); 8131 } 8132 8133 // Scroll 8134 if (edit_state.CursorFollow) 8135 { 8136 // Horizontal scroll in chunks of quarter width 8137 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) 8138 { 8139 const float scroll_increment_x = size.x * 0.25f; 8140 if (cursor_offset.x < edit_state.ScrollX) 8141 edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); 8142 else if (cursor_offset.x - size.x >= edit_state.ScrollX) 8143 edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); 8144 } 8145 else 8146 { 8147 edit_state.ScrollX = 0.0f; 8148 } 8149 8150 // Vertical scroll 8151 if (is_multiline) 8152 { 8153 float scroll_y = draw_window->Scroll.y; 8154 if (cursor_offset.y - g.FontSize < scroll_y) 8155 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); 8156 else if (cursor_offset.y - size.y >= scroll_y) 8157 scroll_y = cursor_offset.y - size.y; 8158 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag 8159 draw_window->Scroll.y = scroll_y; 8160 render_pos.y = draw_window->DC.CursorPos.y; 8161 } 8162 } 8163 edit_state.CursorFollow = false; 8164 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); 8165 8166 // Draw selection 8167 if (edit_state.StbState.select_start != edit_state.StbState.select_end) 8168 { 8169 const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); 8170 const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); 8171 8172 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. 8173 float bg_offy_dn = is_multiline ? 0.0f : 2.0f; 8174 ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); 8175 ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; 8176 for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) 8177 { 8178 if (rect_pos.y > clip_rect.w + g.FontSize) 8179 break; 8180 if (rect_pos.y < clip_rect.y) 8181 { 8182 while (p < text_selected_end) 8183 if (*p++ == '\n') 8184 break; 8185 } 8186 else 8187 { 8188 ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); 8189 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 8190 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); 8191 rect.Clip(clip_rect); 8192 if (rect.Overlaps(clip_rect)) 8193 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); 8194 } 8195 rect_pos.x = render_pos.x - render_scroll.x; 8196 rect_pos.y += g.FontSize; 8197 } 8198 } 8199 8200 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); 8201 8202 // Draw blinking cursor 8203 bool cursor_is_visible = (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; 8204 ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; 8205 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); 8206 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) 8207 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); 8208 8209 // 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.) 8210 if (is_editable) 8211 g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); 8212 } 8213 else 8214 { 8215 // Render text only 8216 const char* buf_end = NULL; 8217 if (is_multiline) 8218 text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width 8219 draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); 8220 } 8221 8222 if (is_multiline) 8223 { 8224 Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line 8225 EndChildFrame(); 8226 EndGroup(); 8227 } 8228 8229 if (is_password) 8230 PopFont(); 8231 8232 // Log as text 8233 if (g.LogEnabled && !is_password) 8234 LogRenderedText(render_pos, buf_display, NULL); 8235 8236 if (label_size.x > 0) 8237 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); 8238 8239 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) 8240 return enter_pressed; 8241 else 8242 return value_changed; 8243 } 8244 8245 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) 8246 { 8247 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() 8248 return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); 8249 } 8250 8251 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) 8252 { 8253 return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); 8254 } 8255 8256 // NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument) 8257 bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags) 8258 { 8259 ImGuiWindow* window = GetCurrentWindow(); 8260 if (window->SkipItems) 8261 return false; 8262 8263 ImGuiContext& g = *GImGui; 8264 const ImGuiStyle& style = g.Style; 8265 const ImVec2 label_size = CalcTextSize(label, NULL, true); 8266 8267 BeginGroup(); 8268 PushID(label); 8269 const ImVec2 button_sz = ImVec2(g.FontSize, g.FontSize) + style.FramePadding*2.0f; 8270 if (step_ptr) 8271 PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2)); 8272 8273 char buf[64]; 8274 DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf)); 8275 8276 bool value_changed = false; 8277 if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal)) 8278 extra_flags |= ImGuiInputTextFlags_CharsDecimal; 8279 extra_flags |= ImGuiInputTextFlags_AutoSelectAll; 8280 if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view 8281 value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format); 8282 8283 // Step buttons 8284 if (step_ptr) 8285 { 8286 PopItemWidth(); 8287 SameLine(0, style.ItemInnerSpacing.x); 8288 if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) 8289 { 8290 DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); 8291 value_changed = true; 8292 } 8293 SameLine(0, style.ItemInnerSpacing.x); 8294 if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) 8295 { 8296 DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); 8297 value_changed = true; 8298 } 8299 } 8300 PopID(); 8301 8302 if (label_size.x > 0) 8303 { 8304 SameLine(0, style.ItemInnerSpacing.x); 8305 RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); 8306 ItemSize(label_size, style.FramePadding.y); 8307 } 8308 EndGroup(); 8309 8310 return value_changed; 8311 } 8312 8313 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) 8314 { 8315 char display_format[16]; 8316 if (decimal_precision < 0) 8317 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 8318 else 8319 ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision); 8320 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); 8321 } 8322 8323 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) 8324 { 8325 // 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. 8326 const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; 8327 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); 8328 } 8329 8330 bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) 8331 { 8332 ImGuiWindow* window = GetCurrentWindow(); 8333 if (window->SkipItems) 8334 return false; 8335 8336 ImGuiContext& g = *GImGui; 8337 bool value_changed = false; 8338 BeginGroup(); 8339 PushID(label); 8340 PushMultiItemsWidths(components); 8341 for (int i = 0; i < components; i++) 8342 { 8343 PushID(i); 8344 value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags); 8345 SameLine(0, g.Style.ItemInnerSpacing.x); 8346 PopID(); 8347 PopItemWidth(); 8348 } 8349 PopID(); 8350 8351 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); 8352 TextUnformatted(label, FindRenderedTextEnd(label)); 8353 EndGroup(); 8354 8355 return value_changed; 8356 } 8357 8358 bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) 8359 { 8360 return InputFloatN(label, v, 2, decimal_precision, extra_flags); 8361 } 8362 8363 bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) 8364 { 8365 return InputFloatN(label, v, 3, decimal_precision, extra_flags); 8366 } 8367 8368 bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) 8369 { 8370 return InputFloatN(label, v, 4, decimal_precision, extra_flags); 8371 } 8372 8373 bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags) 8374 { 8375 ImGuiWindow* window = GetCurrentWindow(); 8376 if (window->SkipItems) 8377 return false; 8378 8379 ImGuiContext& g = *GImGui; 8380 bool value_changed = false; 8381 BeginGroup(); 8382 PushID(label); 8383 PushMultiItemsWidths(components); 8384 for (int i = 0; i < components; i++) 8385 { 8386 PushID(i); 8387 value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags); 8388 SameLine(0, g.Style.ItemInnerSpacing.x); 8389 PopID(); 8390 PopItemWidth(); 8391 } 8392 PopID(); 8393 8394 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); 8395 TextUnformatted(label, FindRenderedTextEnd(label)); 8396 EndGroup(); 8397 8398 return value_changed; 8399 } 8400 8401 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) 8402 { 8403 return InputIntN(label, v, 2, extra_flags); 8404 } 8405 8406 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) 8407 { 8408 return InputIntN(label, v, 3, extra_flags); 8409 } 8410 8411 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) 8412 { 8413 return InputIntN(label, v, 4, extra_flags); 8414 } 8415 8416 static bool Items_ArrayGetter(void* data, int idx, const char** out_text) 8417 { 8418 const char* const* items = (const char* const*)data; 8419 if (out_text) 8420 *out_text = items[idx]; 8421 return true; 8422 } 8423 8424 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) 8425 { 8426 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. 8427 const char* items_separated_by_zeros = (const char*)data; 8428 int items_count = 0; 8429 const char* p = items_separated_by_zeros; 8430 while (*p) 8431 { 8432 if (idx == items_count) 8433 break; 8434 p += strlen(p) + 1; 8435 items_count++; 8436 } 8437 if (!*p) 8438 return false; 8439 if (out_text) 8440 *out_text = p; 8441 return true; 8442 } 8443 8444 // Combo box helper allowing to pass an array of strings. 8445 bool ImGui::Combo(const char* label, int* current_item, const char* const* items, int items_count, int height_in_items) 8446 { 8447 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); 8448 return value_changed; 8449 } 8450 8451 // Combo box helper allowing to pass all items in a single string. 8452 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) 8453 { 8454 int items_count = 0; 8455 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open 8456 while (*p) 8457 { 8458 p += strlen(p) + 1; 8459 items_count++; 8460 } 8461 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); 8462 return value_changed; 8463 } 8464 8465 // Combo box function. 8466 bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) 8467 { 8468 ImGuiWindow* window = GetCurrentWindow(); 8469 if (window->SkipItems) 8470 return false; 8471 8472 ImGuiContext& g = *GImGui; 8473 const ImGuiStyle& style = g.Style; 8474 const ImGuiID id = window->GetID(label); 8475 const float w = CalcItemWidth(); 8476 8477 const ImVec2 label_size = CalcTextSize(label, NULL, true); 8478 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); 8479 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)); 8480 ItemSize(total_bb, style.FramePadding.y); 8481 if (!ItemAdd(total_bb, &id)) 8482 return false; 8483 8484 const float arrow_size = (g.FontSize + style.FramePadding.x * 2.0f); 8485 const bool hovered = IsHovered(frame_bb, id); 8486 bool popup_open = IsPopupOpen(id); 8487 bool popup_opened_now = false; 8488 8489 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); 8490 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); 8491 RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING 8492 RenderCollapseTriangle(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y) + style.FramePadding, true); 8493 8494 if (*current_item >= 0 && *current_item < items_count) 8495 { 8496 const char* item_text; 8497 if (items_getter(data, *current_item, &item_text)) 8498 RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, item_text, NULL, NULL, ImVec2(0.0f,0.0f)); 8499 } 8500 8501 if (label_size.x > 0) 8502 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); 8503 8504 if (hovered) 8505 { 8506 SetHoveredID(id); 8507 if (g.IO.MouseClicked[0]) 8508 { 8509 ClearActiveID(); 8510 if (IsPopupOpen(id)) 8511 { 8512 ClosePopup(id); 8513 } 8514 else 8515 { 8516 FocusWindow(window); 8517 OpenPopup(label); 8518 popup_open = popup_opened_now = true; 8519 } 8520 } 8521 } 8522 8523 bool value_changed = false; 8524 if (IsPopupOpen(id)) 8525 { 8526 // Size default to hold ~7 items 8527 if (height_in_items < 0) 8528 height_in_items = 7; 8529 8530 float popup_height = (label_size.y + style.ItemSpacing.y) * ImMin(items_count, height_in_items) + (style.FramePadding.y * 3); 8531 float popup_y1 = frame_bb.Max.y; 8532 float popup_y2 = ImClamp(popup_y1 + popup_height, popup_y1, g.IO.DisplaySize.y - style.DisplaySafeAreaPadding.y); 8533 if ((popup_y2 - popup_y1) < ImMin(popup_height, frame_bb.Min.y - style.DisplaySafeAreaPadding.y)) 8534 { 8535 // 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) 8536 popup_y1 = ImClamp(frame_bb.Min.y - popup_height, style.DisplaySafeAreaPadding.y, frame_bb.Min.y); 8537 popup_y2 = frame_bb.Min.y; 8538 } 8539 ImRect popup_rect(ImVec2(frame_bb.Min.x, popup_y1), ImVec2(frame_bb.Max.x, popup_y2)); 8540 SetNextWindowPos(popup_rect.Min); 8541 SetNextWindowSize(popup_rect.GetSize()); 8542 PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); 8543 8544 const ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); 8545 if (BeginPopupEx(label, flags)) 8546 { 8547 // Display items 8548 Spacing(); 8549 for (int i = 0; i < items_count; i++) 8550 { 8551 PushID((void*)(intptr_t)i); 8552 const bool item_selected = (i == *current_item); 8553 const char* item_text; 8554 if (!items_getter(data, i, &item_text)) 8555 item_text = "*Unknown item*"; 8556 if (Selectable(item_text, item_selected)) 8557 { 8558 ClearActiveID(); 8559 value_changed = true; 8560 *current_item = i; 8561 } 8562 if (item_selected && popup_opened_now) 8563 SetScrollHere(); 8564 PopID(); 8565 } 8566 EndPopup(); 8567 } 8568 PopStyleVar(); 8569 } 8570 return value_changed; 8571 } 8572 8573 // Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. 8574 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. 8575 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) 8576 { 8577 ImGuiWindow* window = GetCurrentWindow(); 8578 if (window->SkipItems) 8579 return false; 8580 8581 ImGuiContext& g = *GImGui; 8582 const ImGuiStyle& style = g.Style; 8583 8584 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) 8585 PopClipRect(); 8586 8587 ImGuiID id = window->GetID(label); 8588 ImVec2 label_size = CalcTextSize(label, NULL, true); 8589 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); 8590 ImVec2 pos = window->DC.CursorPos; 8591 pos.y += window->DC.CurrentLineTextBaseOffset; 8592 ImRect bb(pos, pos + size); 8593 ItemSize(bb); 8594 8595 // Fill horizontal space. 8596 ImVec2 window_padding = window->WindowPadding; 8597 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; 8598 float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); 8599 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); 8600 ImRect bb_with_spacing(pos, pos + size_draw); 8601 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) 8602 bb_with_spacing.Max.x += window_padding.x; 8603 8604 // Selectables are tightly packed together, we extend the box to cover spacing between selectable. 8605 float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); 8606 float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); 8607 float spacing_R = style.ItemSpacing.x - spacing_L; 8608 float spacing_D = style.ItemSpacing.y - spacing_U; 8609 bb_with_spacing.Min.x -= spacing_L; 8610 bb_with_spacing.Min.y -= spacing_U; 8611 bb_with_spacing.Max.x += spacing_R; 8612 bb_with_spacing.Max.y += spacing_D; 8613 if (!ItemAdd(bb_with_spacing, &id)) 8614 { 8615 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) 8616 PushColumnClipRect(); 8617 return false; 8618 } 8619 8620 ImGuiButtonFlags button_flags = 0; 8621 if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick; 8622 if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnClick|ImGuiButtonFlags_PressedOnRelease; 8623 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; 8624 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; 8625 bool hovered, held; 8626 bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags); 8627 if (flags & ImGuiSelectableFlags_Disabled) 8628 selected = false; 8629 8630 // Render 8631 if (hovered || selected) 8632 { 8633 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); 8634 RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f); 8635 } 8636 8637 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1) 8638 { 8639 PushColumnClipRect(); 8640 bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x); 8641 } 8642 8643 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); 8644 RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); 8645 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); 8646 8647 // Automatically close popups 8648 if (pressed && !(flags & ImGuiSelectableFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) 8649 CloseCurrentPopup(); 8650 return pressed; 8651 } 8652 8653 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) 8654 { 8655 if (Selectable(label, *p_selected, flags, size_arg)) 8656 { 8657 *p_selected = !*p_selected; 8658 return true; 8659 } 8660 return false; 8661 } 8662 8663 // Helper to calculate the size of a listbox and display a label on the right. 8664 // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" 8665 bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) 8666 { 8667 ImGuiWindow* window = GetCurrentWindow(); 8668 if (window->SkipItems) 8669 return false; 8670 8671 const ImGuiStyle& style = GetStyle(); 8672 const ImGuiID id = GetID(label); 8673 const ImVec2 label_size = CalcTextSize(label, NULL, true); 8674 8675 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. 8676 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); 8677 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); 8678 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); 8679 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); 8680 window->DC.LastItemRect = bb; 8681 8682 BeginGroup(); 8683 if (label_size.x > 0) 8684 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); 8685 8686 BeginChildFrame(id, frame_bb.GetSize()); 8687 return true; 8688 } 8689 8690 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) 8691 { 8692 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. 8693 // 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. 8694 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. 8695 if (height_in_items < 0) 8696 height_in_items = ImMin(items_count, 7); 8697 float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); 8698 8699 // 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(). 8700 ImVec2 size; 8701 size.x = 0.0f; 8702 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; 8703 return ListBoxHeader(label, size); 8704 } 8705 8706 void ImGui::ListBoxFooter() 8707 { 8708 ImGuiWindow* parent_window = GetParentWindow(); 8709 const ImRect bb = parent_window->DC.LastItemRect; 8710 const ImGuiStyle& style = GetStyle(); 8711 8712 EndChildFrame(); 8713 8714 // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) 8715 // We call SameLine() to restore DC.CurrentLine* data 8716 SameLine(); 8717 parent_window->DC.CursorPos = bb.Min; 8718 ItemSize(bb, style.FramePadding.y); 8719 EndGroup(); 8720 } 8721 8722 bool ImGui::ListBox(const char* label, int* current_item, const char* const* items, int items_count, int height_items) 8723 { 8724 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); 8725 return value_changed; 8726 } 8727 8728 bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) 8729 { 8730 if (!ListBoxHeader(label, items_count, height_in_items)) 8731 return false; 8732 8733 // 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. 8734 bool value_changed = false; 8735 ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); 8736 while (clipper.Step()) 8737 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) 8738 { 8739 const bool item_selected = (i == *current_item); 8740 const char* item_text; 8741 if (!items_getter(data, i, &item_text)) 8742 item_text = "*Unknown item*"; 8743 8744 PushID(i); 8745 if (Selectable(item_text, item_selected)) 8746 { 8747 *current_item = i; 8748 value_changed = true; 8749 } 8750 PopID(); 8751 } 8752 ListBoxFooter(); 8753 return value_changed; 8754 } 8755 8756 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) 8757 { 8758 ImGuiWindow* window = GetCurrentWindow(); 8759 if (window->SkipItems) 8760 return false; 8761 8762 ImGuiContext& g = *GImGui; 8763 ImVec2 pos = window->DC.CursorPos; 8764 ImVec2 label_size = CalcTextSize(label, NULL, true); 8765 ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); 8766 float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame 8767 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); 8768 8769 bool pressed = Selectable(label, false, ImGuiSelectableFlags_MenuItem | ImGuiSelectableFlags_DrawFillAvailWidth | (enabled ? 0 : ImGuiSelectableFlags_Disabled), ImVec2(w, 0.0f)); 8770 if (shortcut_size.x > 0.0f) 8771 { 8772 PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); 8773 RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); 8774 PopStyleColor(); 8775 } 8776 8777 if (selected) 8778 RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled)); 8779 8780 return pressed; 8781 } 8782 8783 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) 8784 { 8785 if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) 8786 { 8787 if (p_selected) 8788 *p_selected = !*p_selected; 8789 return true; 8790 } 8791 return false; 8792 } 8793 8794 bool ImGui::BeginMainMenuBar() 8795 { 8796 ImGuiContext& g = *GImGui; 8797 SetNextWindowPos(ImVec2(0.0f, 0.0f)); 8798 SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f)); 8799 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 8800 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); 8801 if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar) 8802 || !BeginMenuBar()) 8803 { 8804 End(); 8805 PopStyleVar(2); 8806 return false; 8807 } 8808 g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x; 8809 return true; 8810 } 8811 8812 void ImGui::EndMainMenuBar() 8813 { 8814 EndMenuBar(); 8815 End(); 8816 PopStyleVar(2); 8817 } 8818 8819 bool ImGui::BeginMenuBar() 8820 { 8821 ImGuiWindow* window = GetCurrentWindow(); 8822 if (window->SkipItems) 8823 return false; 8824 if (!(window->Flags & ImGuiWindowFlags_MenuBar)) 8825 return false; 8826 8827 IM_ASSERT(!window->DC.MenuBarAppending); 8828 BeginGroup(); // Save position 8829 PushID("##menubar"); 8830 ImRect rect = window->MenuBarRect(); 8831 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); 8832 window->DC.CursorPos = ImVec2(rect.Min.x + window->DC.MenuBarOffsetX, rect.Min.y);// + g.Style.FramePadding.y); 8833 window->DC.LayoutType = ImGuiLayoutType_Horizontal; 8834 window->DC.MenuBarAppending = true; 8835 AlignFirstTextHeightToWidgets(); 8836 return true; 8837 } 8838 8839 void ImGui::EndMenuBar() 8840 { 8841 ImGuiWindow* window = GetCurrentWindow(); 8842 if (window->SkipItems) 8843 return; 8844 8845 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); 8846 IM_ASSERT(window->DC.MenuBarAppending); 8847 PopClipRect(); 8848 PopID(); 8849 window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x; 8850 window->DC.GroupStack.back().AdvanceCursor = false; 8851 EndGroup(); 8852 window->DC.LayoutType = ImGuiLayoutType_Vertical; 8853 window->DC.MenuBarAppending = false; 8854 } 8855 8856 bool ImGui::BeginMenu(const char* label, bool enabled) 8857 { 8858 ImGuiWindow* window = GetCurrentWindow(); 8859 if (window->SkipItems) 8860 return false; 8861 8862 ImGuiContext& g = *GImGui; 8863 const ImGuiStyle& style = g.Style; 8864 const ImGuiID id = window->GetID(label); 8865 8866 ImVec2 label_size = CalcTextSize(label, NULL, true); 8867 ImGuiWindow* backed_focused_window = g.FocusedWindow; 8868 8869 bool pressed; 8870 bool menu_is_open = IsPopupOpen(id); 8871 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentMenuSet == window->GetID("##menus")); 8872 if (menuset_is_open) 8873 g.FocusedWindow = window; 8874 8875 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos). 8876 ImVec2 popup_pos, pos = window->DC.CursorPos; 8877 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) 8878 { 8879 popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); 8880 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); 8881 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); 8882 float w = label_size.x; 8883 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); 8884 PopStyleVar(); 8885 SameLine(); 8886 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); 8887 } 8888 else 8889 { 8890 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); 8891 float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame 8892 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); 8893 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); 8894 if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); 8895 RenderCollapseTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), false); 8896 if (!enabled) PopStyleColor(); 8897 } 8898 8899 bool hovered = enabled && IsHovered(window->DC.LastItemRect, id); 8900 if (menuset_is_open) 8901 g.FocusedWindow = backed_focused_window; 8902 8903 bool want_open = false, want_close = false; 8904 if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) 8905 { 8906 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. 8907 bool moving_within_opened_triangle = false; 8908 if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window) 8909 { 8910 if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) 8911 { 8912 ImRect next_window_rect = next_window->Rect(); 8913 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; 8914 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); 8915 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); 8916 float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. 8917 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues 8918 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? 8919 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); 8920 moving_within_opened_triangle = ImIsPointInTriangle(g.IO.MousePos, ta, tb, tc); 8921 //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 8922 } 8923 } 8924 8925 want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); 8926 want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); 8927 } 8928 else if (menu_is_open && pressed && menuset_is_open) // menu-bar: click open menu to close 8929 { 8930 want_close = true; 8931 want_open = menu_is_open = false; 8932 } 8933 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // menu-bar: first click to open, then hover to open others 8934 want_open = true; 8935 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.. }' 8936 want_close = true; 8937 if (want_close && IsPopupOpen(id)) 8938 ClosePopupToLevel(GImGui->CurrentPopupStack.Size); 8939 8940 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) 8941 { 8942 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. 8943 OpenPopup(label); 8944 return false; 8945 } 8946 8947 menu_is_open |= want_open; 8948 if (want_open) 8949 OpenPopup(label); 8950 8951 if (menu_is_open) 8952 { 8953 SetNextWindowPos(popup_pos, ImGuiSetCond_Always); 8954 ImGuiWindowFlags flags = ImGuiWindowFlags_ShowBorders | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); 8955 menu_is_open = BeginPopupEx(label, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) 8956 } 8957 8958 return menu_is_open; 8959 } 8960 8961 void ImGui::EndMenu() 8962 { 8963 EndPopup(); 8964 } 8965 8966 // A little colored square. Return true when clicked. 8967 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. 8968 bool ImGui::ColorButton(const ImVec4& col, bool small_height, bool outline_border) 8969 { 8970 ImGuiWindow* window = GetCurrentWindow(); 8971 if (window->SkipItems) 8972 return false; 8973 8974 ImGuiContext& g = *GImGui; 8975 const ImGuiStyle& style = g.Style; 8976 const ImGuiID id = window->GetID("#colorbutton"); 8977 const float square_size = g.FontSize; 8978 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(square_size + style.FramePadding.y*2, square_size + (small_height ? 0 : style.FramePadding.y*2))); 8979 ItemSize(bb, small_height ? 0.0f : style.FramePadding.y); 8980 if (!ItemAdd(bb, &id)) 8981 return false; 8982 8983 bool hovered, held; 8984 bool pressed = ButtonBehavior(bb, id, &hovered, &held); 8985 RenderFrame(bb.Min, bb.Max, GetColorU32(col), outline_border, style.FrameRounding); 8986 8987 if (hovered) 8988 SetTooltip("Color:\n(%.2f,%.2f,%.2f,%.2f)\n#%02X%02X%02X%02X", col.x, col.y, col.z, col.w, IM_F32_TO_INT8_SAT(col.x), IM_F32_TO_INT8_SAT(col.y), IM_F32_TO_INT8_SAT(col.z), IM_F32_TO_INT8_SAT(col.w)); 8989 8990 return pressed; 8991 } 8992 8993 bool ImGui::ColorEdit3(const char* label, float col[3]) 8994 { 8995 float col4[4]; 8996 col4[0] = col[0]; 8997 col4[1] = col[1]; 8998 col4[2] = col[2]; 8999 col4[3] = 1.0f; 9000 const bool value_changed = ColorEdit4(label, col4, false); 9001 col[0] = col4[0]; 9002 col[1] = col4[1]; 9003 col[2] = col4[2]; 9004 return value_changed; 9005 } 9006 9007 // Edit colors components (each component in 0.0f..1.0f range 9008 // Use CTRL-Click to input value and TAB to go to next item. 9009 bool ImGui::ColorEdit4(const char* label, float col[4], bool alpha) 9010 { 9011 ImGuiWindow* window = GetCurrentWindow(); 9012 if (window->SkipItems) 9013 return false; 9014 9015 ImGuiContext& g = *GImGui; 9016 const ImGuiStyle& style = g.Style; 9017 const ImGuiID id = window->GetID(label); 9018 const float w_full = CalcItemWidth(); 9019 const float square_sz = (g.FontSize + style.FramePadding.y * 2.0f); 9020 9021 ImGuiColorEditMode edit_mode = window->DC.ColorEditMode; 9022 if (edit_mode == ImGuiColorEditMode_UserSelect || edit_mode == ImGuiColorEditMode_UserSelectShowButton) 9023 edit_mode = g.ColorEditModeStorage.GetInt(id, 0) % 3; 9024 9025 float f[4] = { col[0], col[1], col[2], col[3] }; 9026 if (edit_mode == ImGuiColorEditMode_HSV) 9027 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); 9028 9029 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]) }; 9030 9031 int components = alpha ? 4 : 3; 9032 bool value_changed = false; 9033 9034 BeginGroup(); 9035 PushID(label); 9036 9037 const bool hsv = (edit_mode == 1); 9038 switch (edit_mode) 9039 { 9040 case ImGuiColorEditMode_RGB: 9041 case ImGuiColorEditMode_HSV: 9042 { 9043 // RGB/HSV 0..255 Sliders 9044 const float w_items_all = w_full - (square_sz + style.ItemInnerSpacing.x); 9045 const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); 9046 const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); 9047 9048 const bool hide_prefix = (w_item_one <= CalcTextSize("M:999").x); 9049 const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; 9050 const char* fmt_table[3][4] = 9051 { 9052 { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, 9053 { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, 9054 { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } 9055 }; 9056 const char** fmt = hide_prefix ? fmt_table[0] : hsv ? fmt_table[2] : fmt_table[1]; 9057 9058 PushItemWidth(w_item_one); 9059 for (int n = 0; n < components; n++) 9060 { 9061 if (n > 0) 9062 SameLine(0, style.ItemInnerSpacing.x); 9063 if (n + 1 == components) 9064 PushItemWidth(w_item_last); 9065 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, 255, fmt[n]); 9066 } 9067 PopItemWidth(); 9068 PopItemWidth(); 9069 } 9070 break; 9071 case ImGuiColorEditMode_HEX: 9072 { 9073 // RGB Hexadecimal Input 9074 const float w_slider_all = w_full - square_sz; 9075 char buf[64]; 9076 if (alpha) 9077 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", i[0], i[1], i[2], i[3]); 9078 else 9079 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", i[0], i[1], i[2]); 9080 PushItemWidth(w_slider_all - style.ItemInnerSpacing.x); 9081 if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) 9082 { 9083 value_changed |= true; 9084 char* p = buf; 9085 while (*p == '#' || ImCharIsSpace(*p)) 9086 p++; 9087 i[0] = i[1] = i[2] = i[3] = 0; 9088 if (alpha) 9089 sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) 9090 else 9091 sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); 9092 } 9093 PopItemWidth(); 9094 } 9095 break; 9096 } 9097 9098 SameLine(0, style.ItemInnerSpacing.x); 9099 9100 const ImVec4 col_display(col[0], col[1], col[2], 1.0f); 9101 if (ColorButton(col_display)) 9102 g.ColorEditModeStorage.SetInt(id, (edit_mode + 1) % 3); // Don't set local copy of 'edit_mode' right away! 9103 9104 // Recreate our own tooltip over's ColorButton() one because we want to display correct alpha here 9105 if (IsItemHovered()) 9106 SetTooltip("Color:\n(%.2f,%.2f,%.2f,%.2f)\n#%02X%02X%02X%02X", col[0], col[1], col[2], col[3], IM_F32_TO_INT8_SAT(col[0]), IM_F32_TO_INT8_SAT(col[1]), IM_F32_TO_INT8_SAT(col[2]), IM_F32_TO_INT8_SAT(col[3])); 9107 9108 if (window->DC.ColorEditMode == ImGuiColorEditMode_UserSelectShowButton) 9109 { 9110 SameLine(0, style.ItemInnerSpacing.x); 9111 const char* button_titles[3] = { "RGB", "HSV", "HEX" }; 9112 if (ButtonEx(button_titles[edit_mode], ImVec2(0,0), ImGuiButtonFlags_DontClosePopups)) 9113 g.ColorEditModeStorage.SetInt(id, (edit_mode + 1) % 3); // Don't set local copy of 'edit_mode' right away! 9114 } 9115 9116 const char* label_display_end = FindRenderedTextEnd(label); 9117 if (label != label_display_end) 9118 { 9119 SameLine(0, (window->DC.ColorEditMode == ImGuiColorEditMode_UserSelectShowButton) ? -1.0f : style.ItemInnerSpacing.x); 9120 TextUnformatted(label, label_display_end); 9121 } 9122 9123 // Convert back 9124 for (int n = 0; n < 4; n++) 9125 f[n] = i[n] / 255.0f; 9126 if (edit_mode == 1) 9127 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); 9128 9129 if (value_changed) 9130 { 9131 col[0] = f[0]; 9132 col[1] = f[1]; 9133 col[2] = f[2]; 9134 if (alpha) 9135 col[3] = f[3]; 9136 } 9137 9138 PopID(); 9139 EndGroup(); 9140 9141 return value_changed; 9142 } 9143 9144 void ImGui::ColorEditMode(ImGuiColorEditMode mode) 9145 { 9146 ImGuiWindow* window = GetCurrentWindow(); 9147 window->DC.ColorEditMode = mode; 9148 } 9149 9150 // Horizontal separating line. 9151 void ImGui::Separator() 9152 { 9153 ImGuiWindow* window = GetCurrentWindow(); 9154 if (window->SkipItems) 9155 return; 9156 9157 if (window->DC.ColumnsCount > 1) 9158 PopClipRect(); 9159 9160 float x1 = window->Pos.x; 9161 float x2 = window->Pos.x + window->Size.x; 9162 if (!window->DC.GroupStack.empty()) 9163 x1 += window->DC.IndentX; 9164 9165 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); 9166 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. 9167 if (!ItemAdd(bb, NULL)) 9168 { 9169 if (window->DC.ColumnsCount > 1) 9170 PushColumnClipRect(); 9171 return; 9172 } 9173 9174 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Border)); 9175 9176 ImGuiContext& g = *GImGui; 9177 if (g.LogEnabled) 9178 LogText(IM_NEWLINE "--------------------------------"); 9179 9180 if (window->DC.ColumnsCount > 1) 9181 { 9182 PushColumnClipRect(); 9183 window->DC.ColumnsCellMinY = window->DC.CursorPos.y; 9184 } 9185 } 9186 9187 void ImGui::Spacing() 9188 { 9189 ImGuiWindow* window = GetCurrentWindow(); 9190 if (window->SkipItems) 9191 return; 9192 ItemSize(ImVec2(0,0)); 9193 } 9194 9195 void ImGui::Dummy(const ImVec2& size) 9196 { 9197 ImGuiWindow* window = GetCurrentWindow(); 9198 if (window->SkipItems) 9199 return; 9200 9201 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); 9202 ItemSize(bb); 9203 ItemAdd(bb, NULL); 9204 } 9205 9206 bool ImGui::IsRectVisible(const ImVec2& size) 9207 { 9208 ImGuiWindow* window = GetCurrentWindowRead(); 9209 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); 9210 } 9211 9212 bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) 9213 { 9214 ImGuiWindow* window = GetCurrentWindowRead(); 9215 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); 9216 } 9217 9218 // 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.) 9219 void ImGui::BeginGroup() 9220 { 9221 ImGuiWindow* window = GetCurrentWindow(); 9222 9223 window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); 9224 ImGuiGroupData& group_data = window->DC.GroupStack.back(); 9225 group_data.BackupCursorPos = window->DC.CursorPos; 9226 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; 9227 group_data.BackupIndentX = window->DC.IndentX; 9228 group_data.BackupGroupOffsetX = window->DC.GroupOffsetX; 9229 group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight; 9230 group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; 9231 group_data.BackupLogLinePosY = window->DC.LogLinePosY; 9232 group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive; 9233 group_data.AdvanceCursor = true; 9234 9235 window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX; 9236 window->DC.IndentX = window->DC.GroupOffsetX; 9237 window->DC.CursorMaxPos = window->DC.CursorPos; 9238 window->DC.CurrentLineHeight = 0.0f; 9239 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; 9240 } 9241 9242 void ImGui::EndGroup() 9243 { 9244 ImGuiContext& g = *GImGui; 9245 ImGuiWindow* window = GetCurrentWindow(); 9246 9247 IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls 9248 9249 ImGuiGroupData& group_data = window->DC.GroupStack.back(); 9250 9251 ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); 9252 group_bb.Max.y -= g.Style.ItemSpacing.y; // Cancel out last vertical spacing because we are adding one ourselves. 9253 group_bb.Max = ImMax(group_bb.Min, group_bb.Max); 9254 9255 window->DC.CursorPos = group_data.BackupCursorPos; 9256 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); 9257 window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight; 9258 window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; 9259 window->DC.IndentX = group_data.BackupIndentX; 9260 window->DC.GroupOffsetX = group_data.BackupGroupOffsetX; 9261 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; 9262 9263 if (group_data.AdvanceCursor) 9264 { 9265 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. 9266 ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); 9267 ItemAdd(group_bb, NULL); 9268 } 9269 9270 // 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. 9271 // 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. 9272 const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); 9273 if (active_id_within_group) 9274 window->DC.LastItemId = g.ActiveId; 9275 if (active_id_within_group && g.HoveredId == g.ActiveId) 9276 window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = true; 9277 9278 window->DC.GroupStack.pop_back(); 9279 9280 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // Debug 9281 } 9282 9283 // Gets back to previous line and continue with horizontal layout 9284 // pos_x == 0 : follow right after previous item 9285 // pos_x != 0 : align to specified x position (relative to window/group left) 9286 // spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 9287 // spacing_w >= 0 : enforce spacing amount 9288 void ImGui::SameLine(float pos_x, float spacing_w) 9289 { 9290 ImGuiWindow* window = GetCurrentWindow(); 9291 if (window->SkipItems) 9292 return; 9293 9294 ImGuiContext& g = *GImGui; 9295 if (pos_x != 0.0f) 9296 { 9297 if (spacing_w < 0.0f) spacing_w = 0.0f; 9298 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX; 9299 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; 9300 } 9301 else 9302 { 9303 if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; 9304 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; 9305 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; 9306 } 9307 window->DC.CurrentLineHeight = window->DC.PrevLineHeight; 9308 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; 9309 } 9310 9311 void ImGui::NewLine() 9312 { 9313 ImGuiWindow* window = GetCurrentWindow(); 9314 if (window->SkipItems) 9315 return; 9316 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. 9317 ItemSize(ImVec2(0,0)); 9318 else 9319 ItemSize(ImVec2(0.0f, GImGui->FontSize)); 9320 } 9321 9322 void ImGui::NextColumn() 9323 { 9324 ImGuiWindow* window = GetCurrentWindow(); 9325 if (window->SkipItems || window->DC.ColumnsCount <= 1) 9326 return; 9327 9328 ImGuiContext& g = *GImGui; 9329 PopItemWidth(); 9330 PopClipRect(); 9331 9332 window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y); 9333 if (++window->DC.ColumnsCurrent < window->DC.ColumnsCount) 9334 { 9335 // Columns 1+ cancel out IndentX 9336 window->DC.ColumnsOffsetX = GetColumnOffset(window->DC.ColumnsCurrent) - window->DC.IndentX + g.Style.ItemSpacing.x; 9337 window->DrawList->ChannelsSetCurrent(window->DC.ColumnsCurrent); 9338 } 9339 else 9340 { 9341 window->DC.ColumnsCurrent = 0; 9342 window->DC.ColumnsOffsetX = 0.0f; 9343 window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY; 9344 window->DrawList->ChannelsSetCurrent(0); 9345 } 9346 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); 9347 window->DC.CursorPos.y = window->DC.ColumnsCellMinY; 9348 window->DC.CurrentLineHeight = 0.0f; 9349 window->DC.CurrentLineTextBaseOffset = 0.0f; 9350 9351 PushColumnClipRect(); 9352 PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup 9353 } 9354 9355 int ImGui::GetColumnIndex() 9356 { 9357 ImGuiWindow* window = GetCurrentWindowRead(); 9358 return window->DC.ColumnsCurrent; 9359 } 9360 9361 int ImGui::GetColumnsCount() 9362 { 9363 ImGuiWindow* window = GetCurrentWindowRead(); 9364 return window->DC.ColumnsCount; 9365 } 9366 9367 static float GetDraggedColumnOffset(int column_index) 9368 { 9369 // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing 9370 // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. 9371 ImGuiContext& g = *GImGui; 9372 ImGuiWindow* window = ImGui::GetCurrentWindowRead(); 9373 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. 9374 IM_ASSERT(g.ActiveId == window->DC.ColumnsSetId + ImGuiID(column_index)); 9375 9376 float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x - window->Pos.x; 9377 x = ImClamp(x, ImGui::GetColumnOffset(column_index-1)+g.Style.ColumnsMinSpacing, ImGui::GetColumnOffset(column_index+1)-g.Style.ColumnsMinSpacing); 9378 9379 return (float)(int)x; 9380 } 9381 9382 float ImGui::GetColumnOffset(int column_index) 9383 { 9384 ImGuiContext& g = *GImGui; 9385 ImGuiWindow* window = GetCurrentWindowRead(); 9386 if (column_index < 0) 9387 column_index = window->DC.ColumnsCurrent; 9388 9389 if (g.ActiveId) 9390 { 9391 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(column_index); 9392 if (g.ActiveId == column_id) 9393 return GetDraggedColumnOffset(column_index); 9394 } 9395 9396 IM_ASSERT(column_index < window->DC.ColumnsData.Size); 9397 const float t = window->DC.ColumnsData[column_index].OffsetNorm; 9398 const float x_offset = window->DC.ColumnsMinX + t * (window->DC.ColumnsMaxX - window->DC.ColumnsMinX); 9399 return (float)(int)x_offset; 9400 } 9401 9402 void ImGui::SetColumnOffset(int column_index, float offset) 9403 { 9404 ImGuiWindow* window = GetCurrentWindow(); 9405 if (column_index < 0) 9406 column_index = window->DC.ColumnsCurrent; 9407 9408 IM_ASSERT(column_index < window->DC.ColumnsData.Size); 9409 const float t = (offset - window->DC.ColumnsMinX) / (window->DC.ColumnsMaxX - window->DC.ColumnsMinX); 9410 window->DC.ColumnsData[column_index].OffsetNorm = t; 9411 9412 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(column_index); 9413 window->DC.StateStorage->SetFloat(column_id, t); 9414 } 9415 9416 float ImGui::GetColumnWidth(int column_index) 9417 { 9418 ImGuiWindow* window = GetCurrentWindowRead(); 9419 if (column_index < 0) 9420 column_index = window->DC.ColumnsCurrent; 9421 9422 float w = GetColumnOffset(column_index+1) - GetColumnOffset(column_index); 9423 return w; 9424 } 9425 9426 static void PushColumnClipRect(int column_index) 9427 { 9428 ImGuiWindow* window = ImGui::GetCurrentWindow(); 9429 if (column_index < 0) 9430 column_index = window->DC.ColumnsCurrent; 9431 9432 float x1 = ImFloor(0.5f + window->Pos.x + ImGui::GetColumnOffset(column_index) - 1.0f); 9433 float x2 = ImFloor(0.5f + window->Pos.x + ImGui::GetColumnOffset(column_index+1) - 1.0f); 9434 ImGui::PushClipRect(ImVec2(x1,-FLT_MAX), ImVec2(x2,+FLT_MAX), true); 9435 } 9436 9437 void ImGui::Columns(int columns_count, const char* id, bool border) 9438 { 9439 ImGuiContext& g = *GImGui; 9440 ImGuiWindow* window = GetCurrentWindow(); 9441 IM_ASSERT(columns_count >= 1); 9442 9443 if (window->DC.ColumnsCount != 1) 9444 { 9445 if (window->DC.ColumnsCurrent != 0) 9446 ItemSize(ImVec2(0,0)); // Advance to column 0 9447 PopItemWidth(); 9448 PopClipRect(); 9449 window->DrawList->ChannelsMerge(); 9450 9451 window->DC.ColumnsCellMaxY = ImMax(window->DC.ColumnsCellMaxY, window->DC.CursorPos.y); 9452 window->DC.CursorPos.y = window->DC.ColumnsCellMaxY; 9453 } 9454 9455 // Draw columns borders and handle resize at the time of "closing" a columns set 9456 if (window->DC.ColumnsCount != columns_count && window->DC.ColumnsCount != 1 && window->DC.ColumnsShowBorders && !window->SkipItems) 9457 { 9458 const float y1 = window->DC.ColumnsStartPosY; 9459 const float y2 = window->DC.CursorPos.y; 9460 for (int i = 1; i < window->DC.ColumnsCount; i++) 9461 { 9462 float x = window->Pos.x + GetColumnOffset(i); 9463 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(i); 9464 const ImRect column_rect(ImVec2(x-4,y1),ImVec2(x+4,y2)); 9465 if (IsClippedEx(column_rect, &column_id, false)) 9466 continue; 9467 9468 bool hovered, held; 9469 ButtonBehavior(column_rect, column_id, &hovered, &held); 9470 if (hovered || held) 9471 g.MouseCursor = ImGuiMouseCursor_ResizeEW; 9472 9473 // Draw before resize so our items positioning are in sync with the line being drawn 9474 const ImU32 col = GetColorU32(held ? ImGuiCol_ColumnActive : hovered ? ImGuiCol_ColumnHovered : ImGuiCol_Column); 9475 const float xi = (float)(int)x; 9476 window->DrawList->AddLine(ImVec2(xi, y1+1.0f), ImVec2(xi, y2), col); 9477 9478 if (held) 9479 { 9480 if (g.ActiveIdIsJustActivated) 9481 g.ActiveIdClickOffset.x -= 4; // Store from center of column line (we used a 8 wide rect for columns clicking) 9482 x = GetDraggedColumnOffset(i); 9483 SetColumnOffset(i, x); 9484 } 9485 } 9486 } 9487 9488 // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. 9489 // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. 9490 PushID(0x11223347 + (id ? 0 : columns_count)); 9491 window->DC.ColumnsSetId = window->GetID(id ? id : "columns"); 9492 PopID(); 9493 9494 // Set state for first column 9495 window->DC.ColumnsCurrent = 0; 9496 window->DC.ColumnsCount = columns_count; 9497 window->DC.ColumnsShowBorders = border; 9498 9499 const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : window->Size.x; 9500 window->DC.ColumnsMinX = window->DC.IndentX; // Lock our horizontal range 9501 window->DC.ColumnsMaxX = content_region_width - window->Scroll.x - ((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x; 9502 window->DC.ColumnsStartPosY = window->DC.CursorPos.y; 9503 window->DC.ColumnsCellMinY = window->DC.ColumnsCellMaxY = window->DC.CursorPos.y; 9504 window->DC.ColumnsOffsetX = 0.0f; 9505 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); 9506 9507 if (window->DC.ColumnsCount != 1) 9508 { 9509 // Cache column offsets 9510 window->DC.ColumnsData.resize(columns_count + 1); 9511 for (int column_index = 0; column_index < columns_count + 1; column_index++) 9512 { 9513 const ImGuiID column_id = window->DC.ColumnsSetId + ImGuiID(column_index); 9514 KeepAliveID(column_id); 9515 const float default_t = column_index / (float)window->DC.ColumnsCount; 9516 const float t = window->DC.StateStorage->GetFloat(column_id, default_t); // Cheaply store our floating point value inside the integer (could store a union into the map?) 9517 window->DC.ColumnsData[column_index].OffsetNorm = t; 9518 } 9519 window->DrawList->ChannelsSplit(window->DC.ColumnsCount); 9520 PushColumnClipRect(); 9521 PushItemWidth(GetColumnWidth() * 0.65f); 9522 } 9523 else 9524 { 9525 window->DC.ColumnsData.resize(0); 9526 } 9527 } 9528 9529 void ImGui::Indent(float indent_w) 9530 { 9531 ImGuiContext& g = *GImGui; 9532 ImGuiWindow* window = GetCurrentWindow(); 9533 window->DC.IndentX += (indent_w > 0.0f) ? indent_w : g.Style.IndentSpacing; 9534 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; 9535 } 9536 9537 void ImGui::Unindent(float indent_w) 9538 { 9539 ImGuiContext& g = *GImGui; 9540 ImGuiWindow* window = GetCurrentWindow(); 9541 window->DC.IndentX -= (indent_w > 0.0f) ? indent_w : g.Style.IndentSpacing; 9542 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; 9543 } 9544 9545 void ImGui::TreePush(const char* str_id) 9546 { 9547 ImGuiWindow* window = GetCurrentWindow(); 9548 Indent(); 9549 window->DC.TreeDepth++; 9550 PushID(str_id ? str_id : "#TreePush"); 9551 } 9552 9553 void ImGui::TreePush(const void* ptr_id) 9554 { 9555 ImGuiWindow* window = GetCurrentWindow(); 9556 Indent(); 9557 window->DC.TreeDepth++; 9558 PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); 9559 } 9560 9561 void ImGui::TreePushRawID(ImGuiID id) 9562 { 9563 ImGuiWindow* window = GetCurrentWindow(); 9564 Indent(); 9565 window->DC.TreeDepth++; 9566 window->IDStack.push_back(id); 9567 } 9568 9569 void ImGui::TreePop() 9570 { 9571 ImGuiWindow* window = GetCurrentWindow(); 9572 Unindent(); 9573 window->DC.TreeDepth--; 9574 PopID(); 9575 } 9576 9577 void ImGui::Value(const char* prefix, bool b) 9578 { 9579 Text("%s: %s", prefix, (b ? "true" : "false")); 9580 } 9581 9582 void ImGui::Value(const char* prefix, int v) 9583 { 9584 Text("%s: %d", prefix, v); 9585 } 9586 9587 void ImGui::Value(const char* prefix, unsigned int v) 9588 { 9589 Text("%s: %d", prefix, v); 9590 } 9591 9592 void ImGui::Value(const char* prefix, float v, const char* float_format) 9593 { 9594 if (float_format) 9595 { 9596 char fmt[64]; 9597 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); 9598 Text(fmt, prefix, v); 9599 } 9600 else 9601 { 9602 Text("%s: %.3f", prefix, v); 9603 } 9604 } 9605 9606 // FIXME: May want to remove those helpers? 9607 void ImGui::ValueColor(const char* prefix, const ImVec4& v) 9608 { 9609 Text("%s: (%.2f,%.2f,%.2f,%.2f)", prefix, v.x, v.y, v.z, v.w); 9610 SameLine(); 9611 ColorButton(v, true); 9612 } 9613 9614 void ImGui::ValueColor(const char* prefix, ImU32 v) 9615 { 9616 Text("%s: %08X", prefix, v); 9617 SameLine(); 9618 ColorButton(ColorConvertU32ToFloat4(v), true); 9619 } 9620 9621 //----------------------------------------------------------------------------- 9622 // PLATFORM DEPENDENT HELPERS 9623 //----------------------------------------------------------------------------- 9624 9625 #if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS)) 9626 #undef WIN32_LEAN_AND_MEAN 9627 #define WIN32_LEAN_AND_MEAN 9628 #include <windows.h> 9629 #endif 9630 9631 // Win32 API clipboard implementation 9632 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) 9633 9634 #ifdef _MSC_VER 9635 #pragma comment(lib, "user32") 9636 #endif 9637 9638 static const char* GetClipboardTextFn_DefaultImpl(void*) 9639 { 9640 static ImVector<char> buf_local; 9641 buf_local.clear(); 9642 if (!OpenClipboard(NULL)) 9643 return NULL; 9644 HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT); 9645 if (wbuf_handle == NULL) 9646 return NULL; 9647 if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle)) 9648 { 9649 int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; 9650 buf_local.resize(buf_len); 9651 ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); 9652 } 9653 GlobalUnlock(wbuf_handle); 9654 CloseClipboard(); 9655 return buf_local.Data; 9656 } 9657 9658 static void SetClipboardTextFn_DefaultImpl(void*, const char* text) 9659 { 9660 if (!OpenClipboard(NULL)) 9661 return; 9662 const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; 9663 HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); 9664 if (wbuf_handle == NULL) 9665 return; 9666 ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle); 9667 ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); 9668 GlobalUnlock(wbuf_handle); 9669 EmptyClipboard(); 9670 SetClipboardData(CF_UNICODETEXT, wbuf_handle); 9671 CloseClipboard(); 9672 } 9673 9674 #else 9675 9676 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers 9677 static const char* GetClipboardTextFn_DefaultImpl(void*) 9678 { 9679 return GImGui->PrivateClipboard; 9680 } 9681 9682 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers 9683 static void SetClipboardTextFn_DefaultImpl(void*, const char* text) 9684 { 9685 ImGuiContext& g = *GImGui; 9686 if (g.PrivateClipboard) 9687 { 9688 ImGui::MemFree(g.PrivateClipboard); 9689 g.PrivateClipboard = NULL; 9690 } 9691 const char* text_end = text + strlen(text); 9692 g.PrivateClipboard = (char*)ImGui::MemAlloc((size_t)(text_end - text) + 1); 9693 memcpy(g.PrivateClipboard, text, (size_t)(text_end - text)); 9694 g.PrivateClipboard[(int)(text_end - text)] = 0; 9695 } 9696 9697 #endif 9698 9699 // Win32 API IME support (for Asian languages, etc.) 9700 #if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS) 9701 9702 #include <imm.h> 9703 #ifdef _MSC_VER 9704 #pragma comment(lib, "imm32") 9705 #endif 9706 9707 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) 9708 { 9709 // Notify OS Input Method Editor of text input position 9710 if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) 9711 if (HIMC himc = ImmGetContext(hwnd)) 9712 { 9713 COMPOSITIONFORM cf; 9714 cf.ptCurrentPos.x = x; 9715 cf.ptCurrentPos.y = y; 9716 cf.dwStyle = CFS_FORCE_POSITION; 9717 ImmSetCompositionWindow(himc, &cf); 9718 } 9719 } 9720 9721 #else 9722 9723 static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} 9724 9725 #endif 9726 9727 //----------------------------------------------------------------------------- 9728 // HELP 9729 //----------------------------------------------------------------------------- 9730 9731 void ImGui::ShowMetricsWindow(bool* p_open) 9732 { 9733 if (ImGui::Begin("ImGui Metrics", p_open)) 9734 { 9735 ImGui::Text("ImGui %s", ImGui::GetVersion()); 9736 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); 9737 ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3); 9738 ImGui::Text("%d allocations", ImGui::GetIO().MetricsAllocs); 9739 static bool show_clip_rects = true; 9740 ImGui::Checkbox("Show clipping rectangles when hovering a ImDrawCmd", &show_clip_rects); 9741 ImGui::Separator(); 9742 9743 struct Funcs 9744 { 9745 static void NodeDrawList(ImDrawList* draw_list, const char* label) 9746 { 9747 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); 9748 if (draw_list == ImGui::GetWindowDrawList()) 9749 { 9750 ImGui::SameLine(); 9751 ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) 9752 if (node_open) ImGui::TreePop(); 9753 return; 9754 } 9755 if (!node_open) 9756 return; 9757 9758 ImDrawList* overlay_draw_list = &GImGui->OverlayDrawList; // Render additional visuals into the top-most draw list 9759 overlay_draw_list->PushClipRectFullScreen(); 9760 int elem_offset = 0; 9761 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) 9762 { 9763 if (pcmd->UserCallback) 9764 { 9765 ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); 9766 continue; 9767 } 9768 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; 9769 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); 9770 if (show_clip_rects && ImGui::IsItemHovered()) 9771 { 9772 ImRect clip_rect = pcmd->ClipRect; 9773 ImRect vtxs_rect; 9774 for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) 9775 vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); 9776 clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); 9777 vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); 9778 } 9779 if (!pcmd_node_open) 9780 continue; 9781 ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. 9782 while (clipper.Step()) 9783 for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) 9784 { 9785 char buf[300], *buf_p = buf; 9786 ImVec2 triangles_pos[3]; 9787 for (int n = 0; n < 3; n++, vtx_i++) 9788 { 9789 ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; 9790 triangles_pos[n] = v.pos; 9791 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); 9792 } 9793 ImGui::Selectable(buf, false); 9794 if (ImGui::IsItemHovered()) 9795 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 9796 } 9797 ImGui::TreePop(); 9798 } 9799 overlay_draw_list->PopClipRect(); 9800 ImGui::TreePop(); 9801 } 9802 9803 static void NodeWindows(ImVector<ImGuiWindow*>& windows, const char* label) 9804 { 9805 if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) 9806 return; 9807 for (int i = 0; i < windows.Size; i++) 9808 Funcs::NodeWindow(windows[i], "Window"); 9809 ImGui::TreePop(); 9810 } 9811 9812 static void NodeWindow(ImGuiWindow* window, const char* label) 9813 { 9814 if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) 9815 return; 9816 NodeDrawList(window->DrawList, "DrawList"); 9817 ImGui::BulletText("Pos: (%.1f,%.1f)", window->Pos.x, window->Pos.y); 9818 ImGui::BulletText("Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); 9819 ImGui::BulletText("Scroll: (%.2f,%.2f)", window->Scroll.x, window->Scroll.y); 9820 if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); 9821 if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); 9822 ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); 9823 ImGui::TreePop(); 9824 } 9825 }; 9826 9827 ImGuiContext& g = *GImGui; // Access private state 9828 Funcs::NodeWindows(g.Windows, "Windows"); 9829 if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.RenderDrawLists[0].Size)) 9830 { 9831 for (int i = 0; i < g.RenderDrawLists[0].Size; i++) 9832 Funcs::NodeDrawList(g.RenderDrawLists[0][i], "DrawList"); 9833 ImGui::TreePop(); 9834 } 9835 if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) 9836 { 9837 for (int i = 0; i < g.OpenPopupStack.Size; i++) 9838 { 9839 ImGuiWindow* window = g.OpenPopupStack[i].Window; 9840 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" : ""); 9841 } 9842 ImGui::TreePop(); 9843 } 9844 if (ImGui::TreeNode("Basic state")) 9845 { 9846 ImGui::Text("FocusedWindow: '%s'", g.FocusedWindow ? g.FocusedWindow->Name : "NULL"); 9847 ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); 9848 ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); 9849 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 9850 ImGui::Text("ActiveID: 0x%08X/0x%08X", g.ActiveId, g.ActiveIdPreviousFrame); 9851 ImGui::TreePop(); 9852 } 9853 } 9854 ImGui::End(); 9855 } 9856 9857 //----------------------------------------------------------------------------- 9858 9859 // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. 9860 // 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. 9861 #ifdef IMGUI_INCLUDE_IMGUI_USER_INL 9862 #include "imgui_user.inl" 9863 #endif 9864 9865 //-----------------------------------------------------------------------------