1 // dear imgui, v1.68 WIP
2 // (widgets code)
3 
4 /*
5 
6 Index of this file:
7 
8 // [SECTION] Forward Declarations
9 // [SECTION] Widgets: Text, etc.
10 // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
11 // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
12 // [SECTION] Widgets: ComboBox
13 // [SECTION] Data Type and Data Formatting Helpers
14 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
15 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
16 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
17 // [SECTION] Widgets: InputText, InputTextMultiline
18 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
19 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
20 // [SECTION] Widgets: Selectable
21 // [SECTION] Widgets: ListBox
22 // [SECTION] Widgets: PlotLines, PlotHistogram
23 // [SECTION] Widgets: Value helpers
24 // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
25 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
26 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
27 
28 */
29 
30 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
31 #define _CRT_SECURE_NO_WARNINGS
32 #endif
33 
34 #include "imgui.h"
35 #ifndef IMGUI_DEFINE_MATH_OPERATORS
36 #define IMGUI_DEFINE_MATH_OPERATORS
37 #endif
38 #include "imgui_internal.h"
39 
40 #include <ctype.h>      // toupper, isprint
41 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
42 #include <stddef.h>     // intptr_t
43 #else
44 #include <stdint.h>     // intptr_t
45 #endif
46 
47 // Visual Studio warnings
48 #ifdef _MSC_VER
49 #pragma warning (disable: 4127) // condition expression is constant
50 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
51 #endif
52 
53 // Clang/GCC warnings with -Weverything
54 #ifdef __clang__
55 #pragma clang diagnostic ignored "-Wold-style-cast"         // warning : use of old-style cast                              // yes, they are more terse.
56 #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.
57 #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.
58 #pragma clang diagnostic ignored "-Wsign-conversion"        // warning : implicit conversion changes signedness             //
59 #if __has_warning("-Wzero-as-null-pointer-constant")
60 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning : zero as null pointer constant              // some standard header variations use #define NULL 0
61 #endif
62 #if __has_warning("-Wdouble-promotion")
63 #pragma clang diagnostic ignored "-Wdouble-promotion"       // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
64 #endif
65 #elif defined(__GNUC__)
66 #pragma GCC diagnostic ignored "-Wformat-nonliteral"        // warning: format not a string literal, format string not checked
67 #if __GNUC__ >= 8
68 #pragma GCC diagnostic ignored "-Wclass-memaccess"          // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
69 #endif
70 #endif
71 
72 //-------------------------------------------------------------------------
73 // Data
74 //-------------------------------------------------------------------------
75 
76 // Those MIN/MAX values are not define because we need to point to them
77 static const ImS32  IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
78 static const ImS32  IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
79 static const ImU32  IM_U32_MIN = 0;
80 static const ImU32  IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
81 #ifdef LLONG_MIN
82 static const ImS64  IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
83 static const ImS64  IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
84 #else
85 static const ImS64  IM_S64_MIN = -9223372036854775807LL - 1;
86 static const ImS64  IM_S64_MAX = 9223372036854775807LL;
87 #endif
88 static const ImU64  IM_U64_MIN = 0;
89 #ifdef ULLONG_MAX
90 static const ImU64  IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
91 #else
92 static const ImU64  IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
93 #endif
94 
95 //-------------------------------------------------------------------------
96 // [SECTION] Forward Declarations
97 //-------------------------------------------------------------------------
98 
99 // Data Type helpers
100 static inline int       DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
101 static void             DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
102 static bool             DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
103 
104 // For InputTextEx()
105 static bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
106 static int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
107 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);
108 
109 //-------------------------------------------------------------------------
110 // [SECTION] Widgets: Text, etc.
111 //-------------------------------------------------------------------------
112 // - TextUnformatted()
113 // - Text()
114 // - TextV()
115 // - TextColored()
116 // - TextColoredV()
117 // - TextDisabled()
118 // - TextDisabledV()
119 // - TextWrapped()
120 // - TextWrappedV()
121 // - LabelText()
122 // - LabelTextV()
123 // - BulletText()
124 // - BulletTextV()
125 //-------------------------------------------------------------------------
126 
TextUnformatted(const char * text,const char * text_end)127 void ImGui::TextUnformatted(const char* text, const char* text_end)
128 {
129     ImGuiWindow* window = GetCurrentWindow();
130     if (window->SkipItems)
131         return;
132 
133     ImGuiContext& g = *GImGui;
134     IM_ASSERT(text != NULL);
135     const char* text_begin = text;
136     if (text_end == NULL)
137         text_end = text + strlen(text); // FIXME-OPT
138 
139     const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
140     const float wrap_pos_x = window->DC.TextWrapPos;
141     const bool wrap_enabled = wrap_pos_x >= 0.0f;
142     if (text_end - text > 2000 && !wrap_enabled)
143     {
144         // Long text!
145         // Perform manual coarse clipping to optimize for long multi-line text
146         // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
147         // - 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.
148         // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
149         const char* line = text;
150         const float line_height = GetTextLineHeight();
151         const ImRect clip_rect = window->ClipRect;
152         ImVec2 text_size(0,0);
153 
154         if (text_pos.y <= clip_rect.Max.y)
155         {
156             ImVec2 pos = text_pos;
157 
158             // Lines to skip (can't skip when logging text)
159             if (!g.LogEnabled)
160             {
161                 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
162                 if (lines_skippable > 0)
163                 {
164                     int lines_skipped = 0;
165                     while (line < text_end && lines_skipped < lines_skippable)
166                     {
167                         const char* line_end = (const char*)memchr(line, '\n', text_end - line);
168                         if (!line_end)
169                             line_end = text_end;
170                         line = line_end + 1;
171                         lines_skipped++;
172                     }
173                     pos.y += lines_skipped * line_height;
174                 }
175             }
176 
177             // Lines to render
178             if (line < text_end)
179             {
180                 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
181                 while (line < text_end)
182                 {
183                     if (IsClippedEx(line_rect, 0, false))
184                         break;
185 
186                     const char* line_end = (const char*)memchr(line, '\n', text_end - line);
187                     if (!line_end)
188                         line_end = text_end;
189                     const ImVec2 line_size = CalcTextSize(line, line_end, false);
190                     text_size.x = ImMax(text_size.x, line_size.x);
191                     RenderText(pos, line, line_end, false);
192                     line = line_end + 1;
193                     line_rect.Min.y += line_height;
194                     line_rect.Max.y += line_height;
195                     pos.y += line_height;
196                 }
197 
198                 // Count remaining lines
199                 int lines_skipped = 0;
200                 while (line < text_end)
201                 {
202                     const char* line_end = (const char*)memchr(line, '\n', text_end - line);
203                     if (!line_end)
204                         line_end = text_end;
205                     line = line_end + 1;
206                     lines_skipped++;
207                 }
208                 pos.y += lines_skipped * line_height;
209             }
210 
211             text_size.y += (pos - text_pos).y;
212         }
213 
214         ImRect bb(text_pos, text_pos + text_size);
215         ItemSize(text_size);
216         ItemAdd(bb, 0);
217     }
218     else
219     {
220         const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
221         const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
222 
223         // Account of baseline offset
224         ImRect bb(text_pos, text_pos + text_size);
225         ItemSize(text_size);
226         if (!ItemAdd(bb, 0))
227             return;
228 
229         // Render (we don't hide text after ## in this end-user function)
230         RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
231     }
232 }
233 
Text(const char * fmt,...)234 void ImGui::Text(const char* fmt, ...)
235 {
236     va_list args;
237     va_start(args, fmt);
238     TextV(fmt, args);
239     va_end(args);
240 }
241 
TextV(const char * fmt,va_list args)242 void ImGui::TextV(const char* fmt, va_list args)
243 {
244     ImGuiWindow* window = GetCurrentWindow();
245     if (window->SkipItems)
246         return;
247 
248     ImGuiContext& g = *GImGui;
249     const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
250     TextUnformatted(g.TempBuffer, text_end);
251 }
252 
TextColored(const ImVec4 & col,const char * fmt,...)253 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
254 {
255     va_list args;
256     va_start(args, fmt);
257     TextColoredV(col, fmt, args);
258     va_end(args);
259 }
260 
TextColoredV(const ImVec4 & col,const char * fmt,va_list args)261 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
262 {
263     PushStyleColor(ImGuiCol_Text, col);
264     TextV(fmt, args);
265     PopStyleColor();
266 }
267 
TextDisabled(const char * fmt,...)268 void ImGui::TextDisabled(const char* fmt, ...)
269 {
270     va_list args;
271     va_start(args, fmt);
272     TextDisabledV(fmt, args);
273     va_end(args);
274 }
275 
TextDisabledV(const char * fmt,va_list args)276 void ImGui::TextDisabledV(const char* fmt, va_list args)
277 {
278     PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
279     TextV(fmt, args);
280     PopStyleColor();
281 }
282 
TextWrapped(const char * fmt,...)283 void ImGui::TextWrapped(const char* fmt, ...)
284 {
285     va_list args;
286     va_start(args, fmt);
287     TextWrappedV(fmt, args);
288     va_end(args);
289 }
290 
TextWrappedV(const char * fmt,va_list args)291 void ImGui::TextWrappedV(const char* fmt, va_list args)
292 {
293     bool need_backup = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
294     if (need_backup)
295         PushTextWrapPos(0.0f);
296     TextV(fmt, args);
297     if (need_backup)
298         PopTextWrapPos();
299 }
300 
LabelText(const char * label,const char * fmt,...)301 void ImGui::LabelText(const char* label, const char* fmt, ...)
302 {
303     va_list args;
304     va_start(args, fmt);
305     LabelTextV(label, fmt, args);
306     va_end(args);
307 }
308 
309 // Add a label+text combo aligned to other label+value widgets
LabelTextV(const char * label,const char * fmt,va_list args)310 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
311 {
312     ImGuiWindow* window = GetCurrentWindow();
313     if (window->SkipItems)
314         return;
315 
316     ImGuiContext& g = *GImGui;
317     const ImGuiStyle& style = g.Style;
318     const float w = CalcItemWidth();
319 
320     const ImVec2 label_size = CalcTextSize(label, NULL, true);
321     const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
322     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);
323     ItemSize(total_bb, style.FramePadding.y);
324     if (!ItemAdd(total_bb, 0))
325         return;
326 
327     // Render
328     const char* value_text_begin = &g.TempBuffer[0];
329     const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
330     RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
331     if (label_size.x > 0.0f)
332         RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
333 }
334 
BulletText(const char * fmt,...)335 void ImGui::BulletText(const char* fmt, ...)
336 {
337     va_list args;
338     va_start(args, fmt);
339     BulletTextV(fmt, args);
340     va_end(args);
341 }
342 
343 // Text with a little bullet aligned to the typical tree node.
BulletTextV(const char * fmt,va_list args)344 void ImGui::BulletTextV(const char* fmt, va_list args)
345 {
346     ImGuiWindow* window = GetCurrentWindow();
347     if (window->SkipItems)
348         return;
349 
350     ImGuiContext& g = *GImGui;
351     const ImGuiStyle& style = g.Style;
352 
353     const char* text_begin = g.TempBuffer;
354     const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
355     const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
356     const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
357     const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
358     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
359     ItemSize(bb);
360     if (!ItemAdd(bb, 0))
361         return;
362 
363     // Render
364     RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
365     RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
366 }
367 
368 //-------------------------------------------------------------------------
369 // [SECTION] Widgets: Main
370 //-------------------------------------------------------------------------
371 // - ButtonBehavior() [Internal]
372 // - Button()
373 // - SmallButton()
374 // - InvisibleButton()
375 // - ArrowButton()
376 // - CloseButton() [Internal]
377 // - CollapseButton() [Internal]
378 // - Scrollbar() [Internal]
379 // - Image()
380 // - ImageButton()
381 // - Checkbox()
382 // - CheckboxFlags()
383 // - RadioButton()
384 // - ProgressBar()
385 // - Bullet()
386 //-------------------------------------------------------------------------
387 
ButtonBehavior(const ImRect & bb,ImGuiID id,bool * out_hovered,bool * out_held,ImGuiButtonFlags flags)388 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
389 {
390     ImGuiContext& g = *GImGui;
391     ImGuiWindow* window = GetCurrentWindow();
392 
393     if (flags & ImGuiButtonFlags_Disabled)
394     {
395         if (out_hovered) *out_hovered = false;
396         if (out_held) *out_held = false;
397         if (g.ActiveId == id) ClearActiveID();
398         return false;
399     }
400 
401     // Default behavior requires click+release on same spot
402     if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
403         flags |= ImGuiButtonFlags_PressedOnClickRelease;
404 
405     ImGuiWindow* backup_hovered_window = g.HoveredWindow;
406     if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
407         g.HoveredWindow = window;
408 
409 #ifdef IMGUI_ENABLE_TEST_ENGINE
410     if (id != 0 && window->DC.LastItemId != id)
411         ImGuiTestEngineHook_ItemAdd(&g, bb, id);
412 #endif
413 
414     bool pressed = false;
415     bool hovered = ItemHoverable(bb, id);
416 
417     // Drag source doesn't report as hovered
418     if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
419         hovered = false;
420 
421     // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
422     if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
423         if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
424         {
425             hovered = true;
426             SetHoveredID(id);
427             if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
428             {
429                 pressed = true;
430                 FocusWindow(window);
431             }
432         }
433 
434     if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
435         g.HoveredWindow = backup_hovered_window;
436 
437     // 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.
438     if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
439         hovered = false;
440 
441     // Mouse
442     if (hovered)
443     {
444         if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
445         {
446             //                        | CLICKING        | HOLDING with ImGuiButtonFlags_Repeat
447             // PressedOnClickRelease  |  <on release>*  |  <on repeat> <on repeat> .. (NOT on release)  <-- MOST COMMON! (*) only if both click/release were over bounds
448             // PressedOnClick         |  <on click>     |  <on click> <on repeat> <on repeat> ..
449             // PressedOnRelease       |  <on release>   |  <on repeat> <on repeat> .. (NOT on release)
450             // PressedOnDoubleClick   |  <on dclick>    |  <on dclick> <on repeat> <on repeat> ..
451             // FIXME-NAV: We don't honor those different behaviors.
452             if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
453             {
454                 SetActiveID(id, window);
455                 if (!(flags & ImGuiButtonFlags_NoNavFocus))
456                     SetFocusID(id, window);
457                 FocusWindow(window);
458             }
459             if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
460             {
461                 pressed = true;
462                 if (flags & ImGuiButtonFlags_NoHoldingActiveID)
463                     ClearActiveID();
464                 else
465                     SetActiveID(id, window); // Hold on ID
466                 FocusWindow(window);
467             }
468             if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
469             {
470                 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay))  // Repeat mode trumps <on release>
471                     pressed = true;
472                 ClearActiveID();
473             }
474 
475             // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
476             // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
477             if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
478                 pressed = true;
479         }
480 
481         if (pressed)
482             g.NavDisableHighlight = true;
483     }
484 
485     // Gamepad/Keyboard navigation
486     // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
487     if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
488         hovered = true;
489 
490     if (g.NavActivateDownId == id)
491     {
492         bool nav_activated_by_code = (g.NavActivateId == id);
493         bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
494         if (nav_activated_by_code || nav_activated_by_inputs)
495             pressed = true;
496         if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
497         {
498             // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
499             g.NavActivateId = id; // This is so SetActiveId assign a Nav source
500             SetActiveID(id, window);
501             if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))
502                 SetFocusID(id, window);
503             g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
504         }
505     }
506 
507     bool held = false;
508     if (g.ActiveId == id)
509     {
510         if (pressed)
511             g.ActiveIdHasBeenPressed = true;
512         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
513         {
514             if (g.ActiveIdIsJustActivated)
515                 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
516             if (g.IO.MouseDown[0])
517             {
518                 held = true;
519             }
520             else
521             {
522                 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
523                     if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay))  // Repeat mode trumps <on release>
524                         if (!g.DragDropActive)
525                             pressed = true;
526                 ClearActiveID();
527             }
528             if (!(flags & ImGuiButtonFlags_NoNavFocus))
529                 g.NavDisableHighlight = true;
530         }
531         else if (g.ActiveIdSource == ImGuiInputSource_Nav)
532         {
533             if (g.NavActivateDownId != id)
534                 ClearActiveID();
535         }
536     }
537 
538     if (out_hovered) *out_hovered = hovered;
539     if (out_held) *out_held = held;
540 
541     return pressed;
542 }
543 
ButtonEx(const char * label,const ImVec2 & size_arg,ImGuiButtonFlags flags)544 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
545 {
546     ImGuiWindow* window = GetCurrentWindow();
547     if (window->SkipItems)
548         return false;
549 
550     ImGuiContext& g = *GImGui;
551     const ImGuiStyle& style = g.Style;
552     const ImGuiID id = window->GetID(label);
553     const ImVec2 label_size = CalcTextSize(label, NULL, true);
554 
555     ImVec2 pos = window->DC.CursorPos;
556     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)
557         pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
558     ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
559 
560     const ImRect bb(pos, pos + size);
561     ItemSize(size, style.FramePadding.y);
562     if (!ItemAdd(bb, id))
563         return false;
564 
565     if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
566         flags |= ImGuiButtonFlags_Repeat;
567     bool hovered, held;
568     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
569     if (pressed)
570         MarkItemEdited(id);
571 
572     // Render
573     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
574     RenderNavHighlight(bb, id);
575     RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
576     RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
577 
578     // Automatically close popups
579     //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
580     //    CloseCurrentPopup();
581 
582     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
583     return pressed;
584 }
585 
Button(const char * label,const ImVec2 & size_arg)586 bool ImGui::Button(const char* label, const ImVec2& size_arg)
587 {
588     return ButtonEx(label, size_arg, 0);
589 }
590 
591 // Small buttons fits within text without additional vertical spacing.
SmallButton(const char * label)592 bool ImGui::SmallButton(const char* label)
593 {
594     ImGuiContext& g = *GImGui;
595     float backup_padding_y = g.Style.FramePadding.y;
596     g.Style.FramePadding.y = 0.0f;
597     bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
598     g.Style.FramePadding.y = backup_padding_y;
599     return pressed;
600 }
601 
602 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
603 // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
InvisibleButton(const char * str_id,const ImVec2 & size_arg)604 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
605 {
606     ImGuiWindow* window = GetCurrentWindow();
607     if (window->SkipItems)
608         return false;
609 
610     // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
611     IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
612 
613     const ImGuiID id = window->GetID(str_id);
614     ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
615     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
616     ItemSize(size);
617     if (!ItemAdd(bb, id))
618         return false;
619 
620     bool hovered, held;
621     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
622 
623     return pressed;
624 }
625 
ArrowButtonEx(const char * str_id,ImGuiDir dir,ImVec2 size,ImGuiButtonFlags flags)626 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
627 {
628     ImGuiWindow* window = GetCurrentWindow();
629     if (window->SkipItems)
630         return false;
631 
632     ImGuiContext& g = *GImGui;
633     const ImGuiID id = window->GetID(str_id);
634     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
635     const float default_size = GetFrameHeight();
636     ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
637     if (!ItemAdd(bb, id))
638         return false;
639 
640     if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
641         flags |= ImGuiButtonFlags_Repeat;
642 
643     bool hovered, held;
644     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
645 
646     // Render
647     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
648     RenderNavHighlight(bb, id);
649     RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
650     RenderArrow(bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), dir);
651 
652     return pressed;
653 }
654 
ArrowButton(const char * str_id,ImGuiDir dir)655 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
656 {
657     float sz = GetFrameHeight();
658     return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
659 }
660 
661 // Button to close a window
CloseButton(ImGuiID id,const ImVec2 & pos,float radius)662 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
663 {
664     ImGuiContext& g = *GImGui;
665     ImGuiWindow* window = g.CurrentWindow;
666 
667     // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
668     // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
669     const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
670     bool is_clipped = !ItemAdd(bb, id);
671 
672     bool hovered, held;
673     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
674     if (is_clipped)
675         return pressed;
676 
677     // Render
678     ImVec2 center = bb.GetCenter();
679     if (hovered)
680         window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
681 
682     float cross_extent = (radius * 0.7071f) - 1.0f;
683     ImU32 cross_col = GetColorU32(ImGuiCol_Text);
684     center -= ImVec2(0.5f, 0.5f);
685     window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
686     window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
687 
688     return pressed;
689 }
690 
CollapseButton(ImGuiID id,const ImVec2 & pos)691 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
692 {
693     ImGuiContext& g = *GImGui;
694     ImGuiWindow* window = g.CurrentWindow;
695 
696     ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
697     ItemAdd(bb, id);
698     bool hovered, held;
699     bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
700 
701     ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
702     if (hovered || held)
703         window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
704     RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
705 
706     // Switch to moving the window after mouse is moved beyond the initial drag threshold
707     if (IsItemActive() && IsMouseDragging())
708         StartMouseMovingWindow(window);
709 
710     return pressed;
711 }
712 
GetScrollbarID(ImGuiLayoutType direction)713 ImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction)
714 {
715     ImGuiContext& g = *GImGui;
716     ImGuiWindow* window = g.CurrentWindow;
717     return window->GetID((direction == ImGuiLayoutType_Horizontal) ? "#SCROLLX" : "#SCROLLY");
718 }
719 
720 // Vertical/Horizontal scrollbar
721 // The entire piece of code below is rather confusing because:
722 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
723 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
724 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
Scrollbar(ImGuiLayoutType direction)725 void ImGui::Scrollbar(ImGuiLayoutType direction)
726 {
727     ImGuiContext& g = *GImGui;
728     ImGuiWindow* window = g.CurrentWindow;
729 
730     const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
731     const ImGuiStyle& style = g.Style;
732     const ImGuiID id = GetScrollbarID(direction);
733 
734     // Render background
735     bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
736     float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
737     const ImRect window_rect = window->Rect();
738     const float border_size = window->WindowBorderSize;
739     ImRect bb = horizontal
740         ? 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)
741         : 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);
742     if (!horizontal)
743         bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
744 
745     const float bb_height = bb.GetHeight();
746     if (bb.GetWidth() <= 0.0f || bb_height <= 0.0f)
747         return;
748 
749     // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the resize grab)
750     float alpha = 1.0f;
751     if ((direction == ImGuiLayoutType_Vertical) && bb_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
752     {
753         alpha = ImSaturate((bb_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
754         if (alpha <= 0.0f)
755             return;
756     }
757     const bool allow_interaction = (alpha >= 1.0f);
758 
759     int window_rounding_corners;
760     if (horizontal)
761         window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
762     else
763         window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
764     window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
765     bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f)));
766 
767     // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
768     float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
769     float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
770     float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
771     float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
772 
773     // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
774     // But we maintain a minimum size in pixel to allow for the user to still aim inside.
775     IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
776     const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
777     const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
778     const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
779 
780     // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
781     bool held = false;
782     bool hovered = false;
783     const bool previously_held = (g.ActiveId == id);
784     ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
785 
786     float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
787     float scroll_ratio = ImSaturate(scroll_v / scroll_max);
788     float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
789     if (held && allow_interaction && grab_h_norm < 1.0f)
790     {
791         float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
792         float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
793         float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
794 
795         // Click position in scrollbar normalized space (0.0f->1.0f)
796         const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
797         SetHoveredID(id);
798 
799         bool seek_absolute = false;
800         if (!previously_held)
801         {
802             // On initial click calculate the distance between mouse and the center of the grab
803             if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
804             {
805                 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
806             }
807             else
808             {
809                 seek_absolute = true;
810                 *click_delta_to_grab_center_v = 0.0f;
811             }
812         }
813 
814         // Apply scroll
815         // 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
816         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));
817         scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
818         if (horizontal)
819             window->Scroll.x = scroll_v;
820         else
821             window->Scroll.y = scroll_v;
822 
823         // Update values for rendering
824         scroll_ratio = ImSaturate(scroll_v / scroll_max);
825         grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
826 
827         // Update distance to grab now that we have seeked and saturated
828         if (seek_absolute)
829             *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
830     }
831 
832     // Render grab
833     const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
834     ImRect grab_rect;
835     if (horizontal)
836         grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y);
837     else
838         grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y));
839     window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
840 }
841 
Image(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec4 & tint_col,const ImVec4 & border_col)842 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
843 {
844     ImGuiWindow* window = GetCurrentWindow();
845     if (window->SkipItems)
846         return;
847 
848     ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
849     if (border_col.w > 0.0f)
850         bb.Max += ImVec2(2, 2);
851     ItemSize(bb);
852     if (!ItemAdd(bb, 0))
853         return;
854 
855     if (border_col.w > 0.0f)
856     {
857         window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
858         window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
859     }
860     else
861     {
862         window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
863     }
864 }
865 
866 // frame_padding < 0: uses FramePadding from style (default)
867 // frame_padding = 0: no framing
868 // frame_padding > 0: set framing size
869 // The color used are the button colors.
ImageButton(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,int frame_padding,const ImVec4 & bg_col,const ImVec4 & tint_col)870 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)
871 {
872     ImGuiWindow* window = GetCurrentWindow();
873     if (window->SkipItems)
874         return false;
875 
876     ImGuiContext& g = *GImGui;
877     const ImGuiStyle& style = g.Style;
878 
879     // Default to using texture ID as ID. User can still push string/integer prefixes.
880     // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
881     PushID((void*)(intptr_t)user_texture_id);
882     const ImGuiID id = window->GetID("#image");
883     PopID();
884 
885     const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
886     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
887     const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
888     ItemSize(bb);
889     if (!ItemAdd(bb, id))
890         return false;
891 
892     bool hovered, held;
893     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
894 
895     // Render
896     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
897     RenderNavHighlight(bb, id);
898     RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
899     if (bg_col.w > 0.0f)
900         window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
901     window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
902 
903     return pressed;
904 }
905 
Checkbox(const char * label,bool * v)906 bool ImGui::Checkbox(const char* label, bool* v)
907 {
908     ImGuiWindow* window = GetCurrentWindow();
909     if (window->SkipItems)
910         return false;
911 
912     ImGuiContext& g = *GImGui;
913     const ImGuiStyle& style = g.Style;
914     const ImGuiID id = window->GetID(label);
915     const ImVec2 label_size = CalcTextSize(label, NULL, true);
916 
917     const float square_sz = GetFrameHeight();
918     const ImVec2 pos = window->DC.CursorPos;
919     const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
920     ItemSize(total_bb, style.FramePadding.y);
921     if (!ItemAdd(total_bb, id))
922         return false;
923 
924     bool hovered, held;
925     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
926     if (pressed)
927     {
928         *v = !(*v);
929         MarkItemEdited(id);
930     }
931 
932     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
933     RenderNavHighlight(total_bb, id);
934     RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
935     if (*v)
936     {
937         const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));
938         RenderCheckMark(check_bb.Min + ImVec2(pad, pad), GetColorU32(ImGuiCol_CheckMark), square_sz - pad*2.0f);
939     }
940 
941     if (g.LogEnabled)
942         LogRenderedText(&total_bb.Min, *v ? "[x]" : "[ ]");
943     if (label_size.x > 0.0f)
944         RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);
945 
946     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
947     return pressed;
948 }
949 
CheckboxFlags(const char * label,unsigned int * flags,unsigned int flags_value)950 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
951 {
952     bool v = ((*flags & flags_value) == flags_value);
953     bool pressed = Checkbox(label, &v);
954     if (pressed)
955     {
956         if (v)
957             *flags |= flags_value;
958         else
959             *flags &= ~flags_value;
960     }
961 
962     return pressed;
963 }
964 
RadioButton(const char * label,bool active)965 bool ImGui::RadioButton(const char* label, bool active)
966 {
967     ImGuiWindow* window = GetCurrentWindow();
968     if (window->SkipItems)
969         return false;
970 
971     ImGuiContext& g = *GImGui;
972     const ImGuiStyle& style = g.Style;
973     const ImGuiID id = window->GetID(label);
974     const ImVec2 label_size = CalcTextSize(label, NULL, true);
975 
976     const float square_sz = GetFrameHeight();
977     const ImVec2 pos = window->DC.CursorPos;
978     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
979     const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
980     ItemSize(total_bb, style.FramePadding.y);
981     if (!ItemAdd(total_bb, id))
982         return false;
983 
984     ImVec2 center = check_bb.GetCenter();
985     center.x = (float)(int)center.x + 0.5f;
986     center.y = (float)(int)center.y + 0.5f;
987     const float radius = (square_sz - 1.0f) * 0.5f;
988 
989     bool hovered, held;
990     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
991     if (pressed)
992         MarkItemEdited(id);
993 
994     RenderNavHighlight(total_bb, id);
995     window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
996     if (active)
997     {
998         const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));
999         window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
1000     }
1001 
1002     if (style.FrameBorderSize > 0.0f)
1003     {
1004         window->DrawList->AddCircle(center + ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
1005         window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
1006     }
1007 
1008     if (g.LogEnabled)
1009         LogRenderedText(&total_bb.Min, active ? "(x)" : "( )");
1010     if (label_size.x > 0.0f)
1011         RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);
1012 
1013     return pressed;
1014 }
1015 
RadioButton(const char * label,int * v,int v_button)1016 bool ImGui::RadioButton(const char* label, int* v, int v_button)
1017 {
1018     const bool pressed = RadioButton(label, *v == v_button);
1019     if (pressed)
1020         *v = v_button;
1021     return pressed;
1022 }
1023 
1024 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
ProgressBar(float fraction,const ImVec2 & size_arg,const char * overlay)1025 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1026 {
1027     ImGuiWindow* window = GetCurrentWindow();
1028     if (window->SkipItems)
1029         return;
1030 
1031     ImGuiContext& g = *GImGui;
1032     const ImGuiStyle& style = g.Style;
1033 
1034     ImVec2 pos = window->DC.CursorPos;
1035     ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
1036     ItemSize(bb, style.FramePadding.y);
1037     if (!ItemAdd(bb, 0))
1038         return;
1039 
1040     // Render
1041     fraction = ImSaturate(fraction);
1042     RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1043     bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1044     const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
1045     RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
1046 
1047     // Default displaying the fraction as percentage string, but user can override it
1048     char overlay_buf[32];
1049     if (!overlay)
1050     {
1051         ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
1052         overlay = overlay_buf;
1053     }
1054 
1055     ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1056     if (overlay_size.x > 0.0f)
1057         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);
1058 }
1059 
Bullet()1060 void ImGui::Bullet()
1061 {
1062     ImGuiWindow* window = GetCurrentWindow();
1063     if (window->SkipItems)
1064         return;
1065 
1066     ImGuiContext& g = *GImGui;
1067     const ImGuiStyle& style = g.Style;
1068     const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
1069     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1070     ItemSize(bb);
1071     if (!ItemAdd(bb, 0))
1072     {
1073         SameLine(0, style.FramePadding.x*2);
1074         return;
1075     }
1076 
1077     // Render and stay on same line
1078     RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
1079     SameLine(0, style.FramePadding.x*2);
1080 }
1081 
1082 //-------------------------------------------------------------------------
1083 // [SECTION] Widgets: Low-level Layout helpers
1084 //-------------------------------------------------------------------------
1085 // - Spacing()
1086 // - Dummy()
1087 // - NewLine()
1088 // - AlignTextToFramePadding()
1089 // - Separator()
1090 // - VerticalSeparator() [Internal]
1091 // - SplitterBehavior() [Internal]
1092 //-------------------------------------------------------------------------
1093 
Spacing()1094 void ImGui::Spacing()
1095 {
1096     ImGuiWindow* window = GetCurrentWindow();
1097     if (window->SkipItems)
1098         return;
1099     ItemSize(ImVec2(0,0));
1100 }
1101 
Dummy(const ImVec2 & size)1102 void ImGui::Dummy(const ImVec2& size)
1103 {
1104     ImGuiWindow* window = GetCurrentWindow();
1105     if (window->SkipItems)
1106         return;
1107 
1108     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1109     ItemSize(bb);
1110     ItemAdd(bb, 0);
1111 }
1112 
NewLine()1113 void ImGui::NewLine()
1114 {
1115     ImGuiWindow* window = GetCurrentWindow();
1116     if (window->SkipItems)
1117         return;
1118 
1119     ImGuiContext& g = *GImGui;
1120     const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1121     window->DC.LayoutType = ImGuiLayoutType_Vertical;
1122     if (window->DC.CurrentLineSize.y > 0.0f)     // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
1123         ItemSize(ImVec2(0,0));
1124     else
1125         ItemSize(ImVec2(0.0f, g.FontSize));
1126     window->DC.LayoutType = backup_layout_type;
1127 }
1128 
AlignTextToFramePadding()1129 void ImGui::AlignTextToFramePadding()
1130 {
1131     ImGuiWindow* window = GetCurrentWindow();
1132     if (window->SkipItems)
1133         return;
1134 
1135     ImGuiContext& g = *GImGui;
1136     window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1137     window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
1138 }
1139 
1140 // Horizontal/vertical separating line
Separator()1141 void ImGui::Separator()
1142 {
1143     ImGuiWindow* window = GetCurrentWindow();
1144     if (window->SkipItems)
1145         return;
1146     ImGuiContext& g = *GImGui;
1147 
1148     // Those flags should eventually be overridable by the user
1149     ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1150     IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))));   // Check that only 1 option is selected
1151     if (flags & ImGuiSeparatorFlags_Vertical)
1152     {
1153         VerticalSeparator();
1154         return;
1155     }
1156 
1157     // Horizontal Separator
1158     if (window->DC.ColumnsSet)
1159         PopClipRect();
1160 
1161     float x1 = window->Pos.x;
1162     float x2 = window->Pos.x + window->Size.x;
1163     if (!window->DC.GroupStack.empty())
1164         x1 += window->DC.Indent.x;
1165 
1166     const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
1167     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.
1168     if (!ItemAdd(bb, 0))
1169     {
1170         if (window->DC.ColumnsSet)
1171             PushColumnClipRect();
1172         return;
1173     }
1174 
1175     window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
1176 
1177     if (g.LogEnabled)
1178         LogRenderedText(&bb.Min, "--------------------------------");
1179 
1180     if (window->DC.ColumnsSet)
1181     {
1182         PushColumnClipRect();
1183         window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
1184     }
1185 }
1186 
VerticalSeparator()1187 void ImGui::VerticalSeparator()
1188 {
1189     ImGuiWindow* window = GetCurrentWindow();
1190     if (window->SkipItems)
1191         return;
1192     ImGuiContext& g = *GImGui;
1193 
1194     float y1 = window->DC.CursorPos.y;
1195     float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;
1196     const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
1197     ItemSize(ImVec2(bb.GetWidth(), 0.0f));
1198     if (!ItemAdd(bb, 0))
1199         return;
1200 
1201     window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
1202     if (g.LogEnabled)
1203         LogText(" |");
1204 }
1205 
1206 // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
SplitterBehavior(const ImRect & bb,ImGuiID id,ImGuiAxis axis,float * size1,float * size2,float min_size1,float min_size2,float hover_extend,float hover_visibility_delay)1207 bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
1208 {
1209     ImGuiContext& g = *GImGui;
1210     ImGuiWindow* window = g.CurrentWindow;
1211 
1212     const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
1213     window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
1214     bool item_add = ItemAdd(bb, id);
1215     window->DC.ItemFlags = item_flags_backup;
1216     if (!item_add)
1217         return false;
1218 
1219     bool hovered, held;
1220     ImRect bb_interact = bb;
1221     bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1222     ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
1223     if (g.ActiveId != id)
1224         SetItemAllowOverlap();
1225 
1226     if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1227         SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1228 
1229     ImRect bb_render = bb;
1230     if (held)
1231     {
1232         ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
1233         float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
1234 
1235         // Minimum pane size
1236         float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1237         float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1238         if (mouse_delta < -size_1_maximum_delta)
1239             mouse_delta = -size_1_maximum_delta;
1240         if (mouse_delta > size_2_maximum_delta)
1241             mouse_delta = size_2_maximum_delta;
1242 
1243         // Apply resize
1244         if (mouse_delta != 0.0f)
1245         {
1246             if (mouse_delta < 0.0f)
1247                 IM_ASSERT(*size1 + mouse_delta >= min_size1);
1248             if (mouse_delta > 0.0f)
1249                 IM_ASSERT(*size2 - mouse_delta >= min_size2);
1250             *size1 += mouse_delta;
1251             *size2 -= mouse_delta;
1252             bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1253             MarkItemEdited(id);
1254         }
1255     }
1256 
1257     // Render
1258     const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1259     window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
1260 
1261     return held;
1262 }
1263 
1264 //-------------------------------------------------------------------------
1265 // [SECTION] Widgets: ComboBox
1266 //-------------------------------------------------------------------------
1267 // - BeginCombo()
1268 // - EndCombo()
1269 // - Combo()
1270 //-------------------------------------------------------------------------
1271 
CalcMaxPopupHeightFromItemCount(int items_count)1272 static float CalcMaxPopupHeightFromItemCount(int items_count)
1273 {
1274     ImGuiContext& g = *GImGui;
1275     if (items_count <= 0)
1276         return FLT_MAX;
1277     return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1278 }
1279 
BeginCombo(const char * label,const char * preview_value,ImGuiComboFlags flags)1280 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1281 {
1282     // Always consume the SetNextWindowSizeConstraint() call in our early return paths
1283     ImGuiContext& g = *GImGui;
1284     ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
1285     g.NextWindowData.SizeConstraintCond = 0;
1286 
1287     ImGuiWindow* window = GetCurrentWindow();
1288     if (window->SkipItems)
1289         return false;
1290 
1291     IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1292 
1293     const ImGuiStyle& style = g.Style;
1294     const ImGuiID id = window->GetID(label);
1295 
1296     const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1297     const ImVec2 label_size = CalcTextSize(label, NULL, true);
1298     const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
1299     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
1300     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));
1301     ItemSize(total_bb, style.FramePadding.y);
1302     if (!ItemAdd(total_bb, id, &frame_bb))
1303         return false;
1304 
1305     bool hovered, held;
1306     bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
1307     bool popup_open = IsPopupOpen(id);
1308 
1309     const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
1310     const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1311     RenderNavHighlight(frame_bb, id);
1312     if (!(flags & ImGuiComboFlags_NoPreview))
1313         window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
1314     if (!(flags & ImGuiComboFlags_NoArrowButton))
1315     {
1316         window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
1317         RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
1318     }
1319     RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
1320     if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1321         RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
1322     if (label_size.x > 0)
1323         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
1324 
1325     if ((pressed || g.NavActivateId == id) && !popup_open)
1326     {
1327         if (window->DC.NavLayerCurrent == 0)
1328             window->NavLastIds[0] = id;
1329         OpenPopupEx(id);
1330         popup_open = true;
1331     }
1332 
1333     if (!popup_open)
1334         return false;
1335 
1336     if (backup_next_window_size_constraint)
1337     {
1338         g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
1339         g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1340     }
1341     else
1342     {
1343         if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1344             flags |= ImGuiComboFlags_HeightRegular;
1345         IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_));    // Only one
1346         int popup_max_height_in_items = -1;
1347         if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
1348         else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
1349         else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
1350         SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1351     }
1352 
1353     char name[16];
1354     ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
1355 
1356     // Peak into expected window size so we can position it
1357     if (ImGuiWindow* popup_window = FindWindowByName(name))
1358         if (popup_window->WasActive)
1359         {
1360             ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
1361             if (flags & ImGuiComboFlags_PopupAlignLeft)
1362                 popup_window->AutoPosLastDirection = ImGuiDir_Left;
1363             ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
1364             ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
1365             SetNextWindowPos(pos);
1366         }
1367 
1368     // Horizontally align ourselves with the framed text
1369     ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
1370     PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
1371     bool ret = Begin(name, NULL, window_flags);
1372     PopStyleVar();
1373     if (!ret)
1374     {
1375         EndPopup();
1376         IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
1377         return false;
1378     }
1379     return true;
1380 }
1381 
EndCombo()1382 void ImGui::EndCombo()
1383 {
1384     EndPopup();
1385 }
1386 
1387 // Getter for the old Combo() API: const char*[]
Items_ArrayGetter(void * data,int idx,const char ** out_text)1388 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
1389 {
1390     const char* const* items = (const char* const*)data;
1391     if (out_text)
1392         *out_text = items[idx];
1393     return true;
1394 }
1395 
1396 // Getter for the old Combo() API: "item1\0item2\0item3\0"
Items_SingleStringGetter(void * data,int idx,const char ** out_text)1397 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
1398 {
1399     // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
1400     const char* items_separated_by_zeros = (const char*)data;
1401     int items_count = 0;
1402     const char* p = items_separated_by_zeros;
1403     while (*p)
1404     {
1405         if (idx == items_count)
1406             break;
1407         p += strlen(p) + 1;
1408         items_count++;
1409     }
1410     if (!*p)
1411         return false;
1412     if (out_text)
1413         *out_text = p;
1414     return true;
1415 }
1416 
1417 // Old API, prefer using BeginCombo() nowadays if you can.
Combo(const char * label,int * current_item,bool (* items_getter)(void *,int,const char **),void * data,int items_count,int popup_max_height_in_items)1418 bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
1419 {
1420     ImGuiContext& g = *GImGui;
1421 
1422     // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1423     const char* preview_value = NULL;
1424     if (*current_item >= 0 && *current_item < items_count)
1425         items_getter(data, *current_item, &preview_value);
1426 
1427     // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
1428     if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
1429         SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1430 
1431     if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1432         return false;
1433 
1434     // Display items
1435     // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1436     bool value_changed = false;
1437     for (int i = 0; i < items_count; i++)
1438     {
1439         PushID((void*)(intptr_t)i);
1440         const bool item_selected = (i == *current_item);
1441         const char* item_text;
1442         if (!items_getter(data, i, &item_text))
1443             item_text = "*Unknown item*";
1444         if (Selectable(item_text, item_selected))
1445         {
1446             value_changed = true;
1447             *current_item = i;
1448         }
1449         if (item_selected)
1450             SetItemDefaultFocus();
1451         PopID();
1452     }
1453 
1454     EndCombo();
1455     return value_changed;
1456 }
1457 
1458 // Combo box helper allowing to pass an array of strings.
Combo(const char * label,int * current_item,const char * const items[],int items_count,int height_in_items)1459 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1460 {
1461     const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
1462     return value_changed;
1463 }
1464 
1465 // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
Combo(const char * label,int * current_item,const char * items_separated_by_zeros,int height_in_items)1466 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1467 {
1468     int items_count = 0;
1469     const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
1470     while (*p)
1471     {
1472         p += strlen(p) + 1;
1473         items_count++;
1474     }
1475     bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
1476     return value_changed;
1477 }
1478 
1479 //-------------------------------------------------------------------------
1480 // [SECTION] Data Type and Data Formatting Helpers [Internal]
1481 //-------------------------------------------------------------------------
1482 // - PatchFormatStringFloatToInt()
1483 // - DataTypeFormatString()
1484 // - DataTypeApplyOp()
1485 // - DataTypeApplyOpFromText()
1486 // - GetMinimumStepAtDecimalPrecision
1487 // - RoundScalarWithFormat<>()
1488 //-------------------------------------------------------------------------
1489 
1490 struct ImGuiDataTypeInfo
1491 {
1492     size_t      Size;
1493     const char* PrintFmt;   // Unused
1494     const char* ScanFmt;
1495 };
1496 
1497 static const ImGuiDataTypeInfo GDataTypeInfo[] =
1498 {
1499     { sizeof(int),          "%d",   "%d"    },
1500     { sizeof(unsigned int), "%u",   "%u"    },
1501 #ifdef _MSC_VER
1502     { sizeof(ImS64),        "%I64d","%I64d" },
1503     { sizeof(ImU64),        "%I64u","%I64u" },
1504 #else
1505     { sizeof(ImS64),        "%lld", "%lld"  },
1506     { sizeof(ImU64),        "%llu", "%llu"  },
1507 #endif
1508     { sizeof(float),        "%f",   "%f"    },  // float are promoted to double in va_arg
1509     { sizeof(double),       "%f",   "%lf"   },
1510 };
1511 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1512 
1513 // FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
1514 // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
1515 // To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
PatchFormatStringFloatToInt(const char * fmt)1516 static const char* PatchFormatStringFloatToInt(const char* fmt)
1517 {
1518     if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
1519         return "%d";
1520     const char* fmt_start = ImParseFormatFindStart(fmt);    // Find % (if any, and ignore %%)
1521     const char* fmt_end = ImParseFormatFindEnd(fmt_start);  // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
1522     if (fmt_end > fmt_start && fmt_end[-1] == 'f')
1523     {
1524 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1525         if (fmt_start == fmt && fmt_end[0] == 0)
1526             return "%d";
1527         ImGuiContext& g = *GImGui;
1528         ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
1529         return g.TempBuffer;
1530 #else
1531         IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
1532 #endif
1533     }
1534     return fmt;
1535 }
1536 
DataTypeFormatString(char * buf,int buf_size,ImGuiDataType data_type,const void * data_ptr,const char * format)1537 static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
1538 {
1539     if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)   // Signedness doesn't matter when pushing the argument
1540         return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);
1541     if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)   // Signedness doesn't matter when pushing the argument
1542         return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);
1543     if (data_type == ImGuiDataType_Float)
1544         return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);
1545     if (data_type == ImGuiDataType_Double)
1546         return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);
1547     IM_ASSERT(0);
1548     return 0;
1549 }
1550 
1551 // FIXME: Adding support for clamping on boundaries of the data type would be nice.
DataTypeApplyOp(ImGuiDataType data_type,int op,void * output,void * arg1,const void * arg2)1552 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
1553 {
1554     IM_ASSERT(op == '+' || op == '-');
1555     switch (data_type)
1556     {
1557         case ImGuiDataType_S32:
1558             if (op == '+')      *(int*)output = *(const int*)arg1 + *(const int*)arg2;
1559             else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
1560             return;
1561         case ImGuiDataType_U32:
1562             if (op == '+')      *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
1563             else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
1564             return;
1565         case ImGuiDataType_S64:
1566             if (op == '+')      *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
1567             else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
1568             return;
1569         case ImGuiDataType_U64:
1570             if (op == '+')      *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
1571             else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
1572             return;
1573         case ImGuiDataType_Float:
1574             if (op == '+')      *(float*)output = *(const float*)arg1 + *(const float*)arg2;
1575             else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
1576             return;
1577         case ImGuiDataType_Double:
1578             if (op == '+')      *(double*)output = *(const double*)arg1 + *(const double*)arg2;
1579             else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
1580             return;
1581         case ImGuiDataType_COUNT: break;
1582     }
1583     IM_ASSERT(0);
1584 }
1585 
1586 // User can input math operators (e.g. +100) to edit a numerical values.
1587 // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
DataTypeApplyOpFromText(const char * buf,const char * initial_value_buf,ImGuiDataType data_type,void * data_ptr,const char * format)1588 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
1589 {
1590     while (ImCharIsBlankA(*buf))
1591         buf++;
1592 
1593     // We don't support '-' op because it would conflict with inputing negative value.
1594     // Instead you can use +-100 to subtract from an existing value
1595     char op = buf[0];
1596     if (op == '+' || op == '*' || op == '/')
1597     {
1598         buf++;
1599         while (ImCharIsBlankA(*buf))
1600             buf++;
1601     }
1602     else
1603     {
1604         op = 0;
1605     }
1606     if (!buf[0])
1607         return false;
1608 
1609     // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
1610     IM_ASSERT(data_type < ImGuiDataType_COUNT);
1611     int data_backup[2];
1612     IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
1613     memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
1614 
1615     if (format == NULL)
1616         format = GDataTypeInfo[data_type].ScanFmt;
1617 
1618     int arg1i = 0;
1619     if (data_type == ImGuiDataType_S32)
1620     {
1621         int* v = (int*)data_ptr;
1622         int arg0i = *v;
1623         float arg1f = 0.0f;
1624         if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
1625             return false;
1626         // 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
1627         if (op == '+')      { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); }                   // Add (use "+-" to subtract)
1628         else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); }                   // Multiply
1629         else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }  // Divide
1630         else                { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; }                           // Assign constant
1631     }
1632     else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1633     {
1634         // Assign constant
1635         // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
1636         sscanf(buf, format, data_ptr);
1637     }
1638     else if (data_type == ImGuiDataType_Float)
1639     {
1640         // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
1641         format = "%f";
1642         float* v = (float*)data_ptr;
1643         float arg0f = *v, arg1f = 0.0f;
1644         if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
1645             return false;
1646         if (sscanf(buf, format, &arg1f) < 1)
1647             return false;
1648         if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
1649         else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
1650         else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
1651         else                { *v = arg1f; }                            // Assign constant
1652     }
1653     else if (data_type == ImGuiDataType_Double)
1654     {
1655         format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
1656         double* v = (double*)data_ptr;
1657         double arg0f = *v, arg1f = 0.0;
1658         if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
1659             return false;
1660         if (sscanf(buf, format, &arg1f) < 1)
1661             return false;
1662         if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
1663         else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
1664         else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
1665         else                { *v = arg1f; }                            // Assign constant
1666     }
1667     return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
1668 }
1669 
GetMinimumStepAtDecimalPrecision(int decimal_precision)1670 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
1671 {
1672     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 };
1673     if (decimal_precision < 0)
1674         return FLT_MIN;
1675     return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
1676 }
1677 
1678 template<typename TYPE>
ImAtoi(const char * src,TYPE * output)1679 static const char* ImAtoi(const char* src, TYPE* output)
1680 {
1681     int negative = 0;
1682     if (*src == '-') { negative = 1; src++; }
1683     if (*src == '+') { src++; }
1684     TYPE v = 0;
1685     while (*src >= '0' && *src <= '9')
1686         v = (v * 10) + (*src++ - '0');
1687     *output = negative ? -v : v;
1688     return src;
1689 }
1690 
1691 template<typename TYPE, typename SIGNEDTYPE>
1692 TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
1693 {
1694     const char* fmt_start = ImParseFormatFindStart(format);
1695     if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
1696         return v;
1697     char v_str[64];
1698     ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
1699     const char* p = v_str;
1700     while (*p == ' ')
1701         p++;
1702     if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
1703         v = (TYPE)ImAtof(p);
1704     else
1705         ImAtoi(p, (SIGNEDTYPE*)&v);
1706     return v;
1707 }
1708 
1709 //-------------------------------------------------------------------------
1710 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1711 //-------------------------------------------------------------------------
1712 // - DragBehaviorT<>() [Internal]
1713 // - DragBehavior() [Internal]
1714 // - DragScalar()
1715 // - DragScalarN()
1716 // - DragFloat()
1717 // - DragFloat2()
1718 // - DragFloat3()
1719 // - DragFloat4()
1720 // - DragFloatRange2()
1721 // - DragInt()
1722 // - DragInt2()
1723 // - DragInt3()
1724 // - DragInt4()
1725 // - DragIntRange2()
1726 //-------------------------------------------------------------------------
1727 
1728 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
1729 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
DragBehaviorT(ImGuiDataType data_type,TYPE * v,float v_speed,const TYPE v_min,const TYPE v_max,const char * format,float power,ImGuiDragFlags flags)1730 bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiDragFlags flags)
1731 {
1732     ImGuiContext& g = *GImGui;
1733     const ImGuiAxis axis = (flags & ImGuiDragFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
1734     const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1735     const bool has_min_max = (v_min != v_max);
1736     const bool is_power = (power != 1.0f && is_decimal && has_min_max && (v_max - v_min < FLT_MAX));
1737 
1738     // Default tweak speed
1739     if (v_speed == 0.0f && has_min_max && (v_max - v_min < FLT_MAX))
1740         v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
1741 
1742     // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
1743     float adjust_delta = 0.0f;
1744     if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
1745     {
1746         adjust_delta = g.IO.MouseDelta[axis];
1747         if (g.IO.KeyAlt)
1748             adjust_delta *= 1.0f / 100.0f;
1749         if (g.IO.KeyShift)
1750             adjust_delta *= 10.0f;
1751     }
1752     else if (g.ActiveIdSource == ImGuiInputSource_Nav)
1753     {
1754         int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
1755         adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
1756         v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
1757     }
1758     adjust_delta *= v_speed;
1759 
1760     // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
1761     if (axis == ImGuiAxis_Y)
1762         adjust_delta = -adjust_delta;
1763 
1764     // Clear current value on activation
1765     // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
1766     bool is_just_activated = g.ActiveIdIsJustActivated;
1767     bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
1768     bool is_drag_direction_change_with_power = is_power && ((adjust_delta < 0 && g.DragCurrentAccum > 0) || (adjust_delta > 0 && g.DragCurrentAccum < 0));
1769     if (is_just_activated || is_already_past_limits_and_pushing_outward || is_drag_direction_change_with_power)
1770     {
1771         g.DragCurrentAccum = 0.0f;
1772         g.DragCurrentAccumDirty = false;
1773     }
1774     else if (adjust_delta != 0.0f)
1775     {
1776         g.DragCurrentAccum += adjust_delta;
1777         g.DragCurrentAccumDirty = true;
1778     }
1779 
1780     if (!g.DragCurrentAccumDirty)
1781         return false;
1782 
1783     TYPE v_cur = *v;
1784     FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
1785 
1786     if (is_power)
1787     {
1788         // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
1789         FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
1790         FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
1791         v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
1792         v_old_ref_for_accum_remainder = v_old_norm_curved;
1793     }
1794     else
1795     {
1796         v_cur += (TYPE)g.DragCurrentAccum;
1797     }
1798 
1799     // Round to user desired precision based on format string
1800     v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
1801 
1802     // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
1803     g.DragCurrentAccumDirty = false;
1804     if (is_power)
1805     {
1806         FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
1807         g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
1808     }
1809     else
1810     {
1811         g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
1812     }
1813 
1814     // Lose zero sign for float/double
1815     if (v_cur == (TYPE)-0)
1816         v_cur = (TYPE)0;
1817 
1818     // Clamp values (+ handle overflow/wrap-around for integer types)
1819     if (*v != v_cur && has_min_max)
1820     {
1821         if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal))
1822             v_cur = v_min;
1823         if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal))
1824             v_cur = v_max;
1825     }
1826 
1827     // Apply result
1828     if (*v == v_cur)
1829         return false;
1830     *v = v_cur;
1831     return true;
1832 }
1833 
DragBehavior(ImGuiID id,ImGuiDataType data_type,void * v,float v_speed,const void * v_min,const void * v_max,const char * format,float power,ImGuiDragFlags flags)1834 bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power, ImGuiDragFlags flags)
1835 {
1836     ImGuiContext& g = *GImGui;
1837     if (g.ActiveId == id)
1838     {
1839         if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
1840             ClearActiveID();
1841         else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1842             ClearActiveID();
1843     }
1844     if (g.ActiveId != id)
1845         return false;
1846 
1847     switch (data_type)
1848     {
1849     case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v,  v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power, flags);
1850     case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v,  v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power, flags);
1851     case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v,  v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power, flags);
1852     case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v,  v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power, flags);
1853     case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)v,  v_speed, v_min ? *(const float* )v_min : -FLT_MAX,   v_max ? *(const float* )v_max : FLT_MAX,    format, power, flags);
1854     case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX,   v_max ? *(const double*)v_max : DBL_MAX,    format, power, flags);
1855     case ImGuiDataType_COUNT:  break;
1856     }
1857     IM_ASSERT(0);
1858     return false;
1859 }
1860 
DragScalar(const char * label,ImGuiDataType data_type,void * v,float v_speed,const void * v_min,const void * v_max,const char * format,float power)1861 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
1862 {
1863     ImGuiWindow* window = GetCurrentWindow();
1864     if (window->SkipItems)
1865         return false;
1866 
1867     if (power != 1.0f)
1868         IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
1869 
1870     ImGuiContext& g = *GImGui;
1871     const ImGuiStyle& style = g.Style;
1872     const ImGuiID id = window->GetID(label);
1873     const float w = CalcItemWidth();
1874 
1875     const ImVec2 label_size = CalcTextSize(label, NULL, true);
1876     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
1877     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));
1878 
1879     ItemSize(total_bb, style.FramePadding.y);
1880     if (!ItemAdd(total_bb, id, &frame_bb))
1881         return false;
1882 
1883     const bool hovered = ItemHoverable(frame_bb, id);
1884 
1885     // Default format string when passing NULL
1886     // Patch old "%.0f" format string to use "%d", read function comments for more details.
1887     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
1888     if (format == NULL)
1889         format = GDataTypeInfo[data_type].PrintFmt;
1890     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
1891         format = PatchFormatStringFloatToInt(format);
1892 
1893     // Tabbing or CTRL-clicking on Drag turns it into an input box
1894     bool start_text_input = false;
1895     const bool tab_focus_requested = FocusableItemRegister(window, id);
1896     if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
1897     {
1898         SetActiveID(id, window);
1899         SetFocusID(id, window);
1900         FocusWindow(window);
1901         g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
1902         if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
1903         {
1904             start_text_input = true;
1905             g.ScalarAsInputTextId = 0;
1906         }
1907     }
1908     if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
1909     {
1910         window->DC.CursorPos = frame_bb.Min;
1911         FocusableItemUnregister(window);
1912         return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
1913     }
1914 
1915     // Actual drag behavior
1916     const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power, ImGuiDragFlags_None);
1917     if (value_changed)
1918         MarkItemEdited(id);
1919 
1920     // Draw frame
1921     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1922     RenderNavHighlight(frame_bb, id);
1923     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
1924 
1925     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1926     char value_buf[64];
1927     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
1928     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
1929 
1930     if (label_size.x > 0.0f)
1931         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
1932 
1933     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
1934     return value_changed;
1935 }
1936 
DragScalarN(const char * label,ImGuiDataType data_type,void * v,int components,float v_speed,const void * v_min,const void * v_max,const char * format,float power)1937 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
1938 {
1939     ImGuiWindow* window = GetCurrentWindow();
1940     if (window->SkipItems)
1941         return false;
1942 
1943     ImGuiContext& g = *GImGui;
1944     bool value_changed = false;
1945     BeginGroup();
1946     PushID(label);
1947     PushMultiItemsWidths(components);
1948     size_t type_size = GDataTypeInfo[data_type].Size;
1949     for (int i = 0; i < components; i++)
1950     {
1951         PushID(i);
1952         value_changed |= DragScalar("", data_type, v, v_speed, v_min, v_max, format, power);
1953         SameLine(0, g.Style.ItemInnerSpacing.x);
1954         PopID();
1955         PopItemWidth();
1956         v = (void*)((char*)v + type_size);
1957     }
1958     PopID();
1959 
1960     TextUnformatted(label, FindRenderedTextEnd(label));
1961     EndGroup();
1962     return value_changed;
1963 }
1964 
DragFloat(const char * label,float * v,float v_speed,float v_min,float v_max,const char * format,float power)1965 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
1966 {
1967     return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
1968 }
1969 
DragFloat2(const char * label,float v[2],float v_speed,float v_min,float v_max,const char * format,float power)1970 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
1971 {
1972     return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
1973 }
1974 
DragFloat3(const char * label,float v[3],float v_speed,float v_min,float v_max,const char * format,float power)1975 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
1976 {
1977     return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
1978 }
1979 
DragFloat4(const char * label,float v[4],float v_speed,float v_min,float v_max,const char * format,float power)1980 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
1981 {
1982     return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
1983 }
1984 
DragFloatRange2(const char * label,float * v_current_min,float * v_current_max,float v_speed,float v_min,float v_max,const char * format,const char * format_max,float power)1985 bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power)
1986 {
1987     ImGuiWindow* window = GetCurrentWindow();
1988     if (window->SkipItems)
1989         return false;
1990 
1991     ImGuiContext& g = *GImGui;
1992     PushID(label);
1993     BeginGroup();
1994     PushMultiItemsWidths(2);
1995 
1996     bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power);
1997     PopItemWidth();
1998     SameLine(0, g.Style.ItemInnerSpacing.x);
1999     value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power);
2000     PopItemWidth();
2001     SameLine(0, g.Style.ItemInnerSpacing.x);
2002 
2003     TextUnformatted(label, FindRenderedTextEnd(label));
2004     EndGroup();
2005     PopID();
2006     return value_changed;
2007 }
2008 
2009 // NB: v_speed is float to allow adjusting the drag speed with more precision
DragInt(const char * label,int * v,float v_speed,int v_min,int v_max,const char * format)2010 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
2011 {
2012     return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
2013 }
2014 
DragInt2(const char * label,int v[2],float v_speed,int v_min,int v_max,const char * format)2015 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
2016 {
2017     return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
2018 }
2019 
DragInt3(const char * label,int v[3],float v_speed,int v_min,int v_max,const char * format)2020 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
2021 {
2022     return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
2023 }
2024 
DragInt4(const char * label,int v[4],float v_speed,int v_min,int v_max,const char * format)2025 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
2026 {
2027     return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
2028 }
2029 
DragIntRange2(const char * label,int * v_current_min,int * v_current_max,float v_speed,int v_min,int v_max,const char * format,const char * format_max)2030 bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max)
2031 {
2032     ImGuiWindow* window = GetCurrentWindow();
2033     if (window->SkipItems)
2034         return false;
2035 
2036     ImGuiContext& g = *GImGui;
2037     PushID(label);
2038     BeginGroup();
2039     PushMultiItemsWidths(2);
2040 
2041     bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format);
2042     PopItemWidth();
2043     SameLine(0, g.Style.ItemInnerSpacing.x);
2044     value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format);
2045     PopItemWidth();
2046     SameLine(0, g.Style.ItemInnerSpacing.x);
2047 
2048     TextUnformatted(label, FindRenderedTextEnd(label));
2049     EndGroup();
2050     PopID();
2051 
2052     return value_changed;
2053 }
2054 
2055 //-------------------------------------------------------------------------
2056 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2057 //-------------------------------------------------------------------------
2058 // - SliderBehaviorT<>() [Internal]
2059 // - SliderBehavior() [Internal]
2060 // - SliderScalar()
2061 // - SliderScalarN()
2062 // - SliderFloat()
2063 // - SliderFloat2()
2064 // - SliderFloat3()
2065 // - SliderFloat4()
2066 // - SliderAngle()
2067 // - SliderInt()
2068 // - SliderInt2()
2069 // - SliderInt3()
2070 // - SliderInt4()
2071 // - VSliderScalar()
2072 // - VSliderFloat()
2073 // - VSliderInt()
2074 //-------------------------------------------------------------------------
2075 
2076 template<typename TYPE, typename FLOATTYPE>
SliderCalcRatioFromValueT(ImGuiDataType data_type,TYPE v,TYPE v_min,TYPE v_max,float power,float linear_zero_pos)2077 float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
2078 {
2079     if (v_min == v_max)
2080         return 0.0f;
2081 
2082     const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2083     const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2084     if (is_power)
2085     {
2086         if (v_clamped < 0.0f)
2087         {
2088             const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
2089             return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
2090         }
2091         else
2092         {
2093             const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
2094             return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
2095         }
2096     }
2097 
2098     // Linear slider
2099     return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
2100 }
2101 
2102 // FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
2103 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
SliderBehaviorT(const ImRect & bb,ImGuiID id,ImGuiDataType data_type,TYPE * v,const TYPE v_min,const TYPE v_max,const char * format,float power,ImGuiSliderFlags flags,ImRect * out_grab_bb)2104 bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2105 {
2106     ImGuiContext& g = *GImGui;
2107     const ImGuiStyle& style = g.Style;
2108 
2109     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2110     const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2111     const bool is_power = (power != 1.0f) && is_decimal;
2112 
2113     const float grab_padding = 2.0f;
2114     const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
2115     float grab_sz = style.GrabMinSize;
2116     SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
2117     if (!is_decimal && v_range >= 0)                                             // v_range < 0 may happen on integer overflows
2118         grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize);  // For integer sliders: if possible have the grab size represent 1 unit
2119     grab_sz = ImMin(grab_sz, slider_sz);
2120     const float slider_usable_sz = slider_sz - grab_sz;
2121     const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz*0.5f;
2122     const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz*0.5f;
2123 
2124     // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
2125     float linear_zero_pos;   // 0.0->1.0f
2126     if (is_power && v_min * v_max < 0.0f)
2127     {
2128         // Different sign
2129         const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
2130         const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
2131         linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
2132     }
2133     else
2134     {
2135         // Same sign
2136         linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
2137     }
2138 
2139     // Process interacting with the slider
2140     bool value_changed = false;
2141     if (g.ActiveId == id)
2142     {
2143         bool set_new_value = false;
2144         float clicked_t = 0.0f;
2145         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
2146         {
2147             if (!g.IO.MouseDown[0])
2148             {
2149                 ClearActiveID();
2150             }
2151             else
2152             {
2153                 const float mouse_abs_pos = g.IO.MousePos[axis];
2154                 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
2155                 if (axis == ImGuiAxis_Y)
2156                     clicked_t = 1.0f - clicked_t;
2157                 set_new_value = true;
2158             }
2159         }
2160         else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2161         {
2162             const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
2163             float delta = (axis == ImGuiAxis_X) ? delta2.x : -delta2.y;
2164             if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2165             {
2166                 ClearActiveID();
2167             }
2168             else if (delta != 0.0f)
2169             {
2170                 clicked_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
2171                 const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
2172                 if ((decimal_precision > 0) || is_power)
2173                 {
2174                     delta /= 100.0f;    // Gamepad/keyboard tweak speeds in % of slider bounds
2175                     if (IsNavInputDown(ImGuiNavInput_TweakSlow))
2176                         delta /= 10.0f;
2177                 }
2178                 else
2179                 {
2180                     if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
2181                         delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
2182                     else
2183                         delta /= 100.0f;
2184                 }
2185                 if (IsNavInputDown(ImGuiNavInput_TweakFast))
2186                     delta *= 10.0f;
2187                 set_new_value = true;
2188                 if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
2189                     set_new_value = false;
2190                 else
2191                     clicked_t = ImSaturate(clicked_t + delta);
2192             }
2193         }
2194 
2195         if (set_new_value)
2196         {
2197             TYPE v_new;
2198             if (is_power)
2199             {
2200                 // Account for power curve scale on both sides of the zero
2201                 if (clicked_t < linear_zero_pos)
2202                 {
2203                     // Negative: rescale to the negative range before powering
2204                     float a = 1.0f - (clicked_t / linear_zero_pos);
2205                     a = ImPow(a, power);
2206                     v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
2207                 }
2208                 else
2209                 {
2210                     // Positive: rescale to the positive range before powering
2211                     float a;
2212                     if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
2213                         a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
2214                     else
2215                         a = clicked_t;
2216                     a = ImPow(a, power);
2217                     v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
2218                 }
2219             }
2220             else
2221             {
2222                 // Linear slider
2223                 if (is_decimal)
2224                 {
2225                     v_new = ImLerp(v_min, v_max, clicked_t);
2226                 }
2227                 else
2228                 {
2229                     // For integer values we want the clicking position to match the grab box so we round above
2230                     // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2231                     FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
2232                     TYPE v_new_off_floor = (TYPE)(v_new_off_f);
2233                     TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
2234                     if (!is_decimal && v_new_off_floor < v_new_off_round)
2235                         v_new = v_min + v_new_off_round;
2236                     else
2237                         v_new = v_min + v_new_off_floor;
2238                 }
2239             }
2240 
2241             // Round to user desired precision based on format string
2242             v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);
2243 
2244             // Apply result
2245             if (*v != v_new)
2246             {
2247                 *v = v_new;
2248                 value_changed = true;
2249             }
2250         }
2251     }
2252 
2253     // Output grab position so it can be displayed by the caller
2254     float grab_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
2255     if (axis == ImGuiAxis_Y)
2256         grab_t = 1.0f - grab_t;
2257     const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
2258     if (axis == ImGuiAxis_X)
2259         *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding);
2260     else
2261         *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f);
2262 
2263     return value_changed;
2264 }
2265 
2266 // For 32-bits and larger types, slider bounds are limited to half the natural type range.
2267 // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
2268 // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
SliderBehavior(const ImRect & bb,ImGuiID id,ImGuiDataType data_type,void * v,const void * v_min,const void * v_max,const char * format,float power,ImGuiSliderFlags flags,ImRect * out_grab_bb)2269 bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2270 {
2271     switch (data_type)
2272     {
2273     case ImGuiDataType_S32:
2274         IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
2275         return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v,  *(const ImS32*)v_min,  *(const ImS32*)v_max,  format, power, flags, out_grab_bb);
2276     case ImGuiDataType_U32:
2277         IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
2278         return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v,  *(const ImU32*)v_min,  *(const ImU32*)v_max,  format, power, flags, out_grab_bb);
2279     case ImGuiDataType_S64:
2280         IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
2281         return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v,  *(const ImS64*)v_min,  *(const ImS64*)v_max,  format, power, flags, out_grab_bb);
2282     case ImGuiDataType_U64:
2283         IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
2284         return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v,  *(const ImU64*)v_min,  *(const ImU64*)v_max,  format, power, flags, out_grab_bb);
2285     case ImGuiDataType_Float:
2286         IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
2287         return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v,  *(const float*)v_min,  *(const float*)v_max,  format, power, flags, out_grab_bb);
2288     case ImGuiDataType_Double:
2289         IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
2290         return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb);
2291     case ImGuiDataType_COUNT: break;
2292     }
2293     IM_ASSERT(0);
2294     return false;
2295 }
2296 
SliderScalar(const char * label,ImGuiDataType data_type,void * v,const void * v_min,const void * v_max,const char * format,float power)2297 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
2298 {
2299     ImGuiWindow* window = GetCurrentWindow();
2300     if (window->SkipItems)
2301         return false;
2302 
2303     ImGuiContext& g = *GImGui;
2304     const ImGuiStyle& style = g.Style;
2305     const ImGuiID id = window->GetID(label);
2306     const float w = CalcItemWidth();
2307 
2308     const ImVec2 label_size = CalcTextSize(label, NULL, true);
2309     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
2310     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));
2311 
2312     ItemSize(total_bb, style.FramePadding.y);
2313     if (!ItemAdd(total_bb, id, &frame_bb))
2314         return false;
2315 
2316     // Default format string when passing NULL
2317     // Patch old "%.0f" format string to use "%d", read function comments for more details.
2318     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2319     if (format == NULL)
2320         format = GDataTypeInfo[data_type].PrintFmt;
2321     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
2322         format = PatchFormatStringFloatToInt(format);
2323 
2324     // Tabbing or CTRL-clicking on Slider turns it into an input box
2325     bool start_text_input = false;
2326     const bool tab_focus_requested = FocusableItemRegister(window, id);
2327     const bool hovered = ItemHoverable(frame_bb, id);
2328     if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
2329     {
2330         SetActiveID(id, window);
2331         SetFocusID(id, window);
2332         FocusWindow(window);
2333         g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
2334         if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
2335         {
2336             start_text_input = true;
2337             g.ScalarAsInputTextId = 0;
2338         }
2339     }
2340     if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
2341     {
2342         window->DC.CursorPos = frame_bb.Min;
2343         FocusableItemUnregister(window);
2344         return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
2345     }
2346 
2347     // Draw frame
2348     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2349     RenderNavHighlight(frame_bb, id);
2350     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
2351 
2352     // Slider behavior
2353     ImRect grab_bb;
2354     const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);
2355     if (value_changed)
2356         MarkItemEdited(id);
2357 
2358     // Render grab
2359     window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
2360 
2361     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2362     char value_buf[64];
2363     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
2364     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
2365 
2366     if (label_size.x > 0.0f)
2367         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
2368 
2369     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
2370     return value_changed;
2371 }
2372 
2373 // Add multiple sliders on 1 line for compact edition of multiple components
SliderScalarN(const char * label,ImGuiDataType data_type,void * v,int components,const void * v_min,const void * v_max,const char * format,float power)2374 bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
2375 {
2376     ImGuiWindow* window = GetCurrentWindow();
2377     if (window->SkipItems)
2378         return false;
2379 
2380     ImGuiContext& g = *GImGui;
2381     bool value_changed = false;
2382     BeginGroup();
2383     PushID(label);
2384     PushMultiItemsWidths(components);
2385     size_t type_size = GDataTypeInfo[data_type].Size;
2386     for (int i = 0; i < components; i++)
2387     {
2388         PushID(i);
2389         value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, power);
2390         SameLine(0, g.Style.ItemInnerSpacing.x);
2391         PopID();
2392         PopItemWidth();
2393         v = (void*)((char*)v + type_size);
2394     }
2395     PopID();
2396 
2397     TextUnformatted(label, FindRenderedTextEnd(label));
2398     EndGroup();
2399     return value_changed;
2400 }
2401 
SliderFloat(const char * label,float * v,float v_min,float v_max,const char * format,float power)2402 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
2403 {
2404     return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
2405 }
2406 
SliderFloat2(const char * label,float v[2],float v_min,float v_max,const char * format,float power)2407 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
2408 {
2409     return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
2410 }
2411 
SliderFloat3(const char * label,float v[3],float v_min,float v_max,const char * format,float power)2412 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
2413 {
2414     return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
2415 }
2416 
SliderFloat4(const char * label,float v[4],float v_min,float v_max,const char * format,float power)2417 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
2418 {
2419     return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
2420 }
2421 
SliderAngle(const char * label,float * v_rad,float v_degrees_min,float v_degrees_max,const char * format)2422 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format)
2423 {
2424     if (format == NULL)
2425         format = "%.0f deg";
2426     float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
2427     bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, 1.0f);
2428     *v_rad = v_deg * (2*IM_PI) / 360.0f;
2429     return value_changed;
2430 }
2431 
SliderInt(const char * label,int * v,int v_min,int v_max,const char * format)2432 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
2433 {
2434     return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
2435 }
2436 
SliderInt2(const char * label,int v[2],int v_min,int v_max,const char * format)2437 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
2438 {
2439     return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
2440 }
2441 
SliderInt3(const char * label,int v[3],int v_min,int v_max,const char * format)2442 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
2443 {
2444     return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
2445 }
2446 
SliderInt4(const char * label,int v[4],int v_min,int v_max,const char * format)2447 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
2448 {
2449     return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
2450 }
2451 
VSliderScalar(const char * label,const ImVec2 & size,ImGuiDataType data_type,void * v,const void * v_min,const void * v_max,const char * format,float power)2452 bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
2453 {
2454     ImGuiWindow* window = GetCurrentWindow();
2455     if (window->SkipItems)
2456         return false;
2457 
2458     ImGuiContext& g = *GImGui;
2459     const ImGuiStyle& style = g.Style;
2460     const ImGuiID id = window->GetID(label);
2461 
2462     const ImVec2 label_size = CalcTextSize(label, NULL, true);
2463     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
2464     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));
2465 
2466     ItemSize(bb, style.FramePadding.y);
2467     if (!ItemAdd(frame_bb, id))
2468         return false;
2469 
2470     // Default format string when passing NULL
2471     // Patch old "%.0f" format string to use "%d", read function comments for more details.
2472     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2473     if (format == NULL)
2474         format = GDataTypeInfo[data_type].PrintFmt;
2475     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
2476         format = PatchFormatStringFloatToInt(format);
2477 
2478     const bool hovered = ItemHoverable(frame_bb, id);
2479     if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
2480     {
2481         SetActiveID(id, window);
2482         SetFocusID(id, window);
2483         FocusWindow(window);
2484         g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2485     }
2486 
2487     // Draw frame
2488     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2489     RenderNavHighlight(frame_bb, id);
2490     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
2491 
2492     // Slider behavior
2493     ImRect grab_bb;
2494     const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
2495     if (value_changed)
2496         MarkItemEdited(id);
2497 
2498     // Render grab
2499     window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
2500 
2501     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2502     // For the vertical slider we allow centered text to overlap the frame padding
2503     char value_buf[64];
2504     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
2505     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));
2506     if (label_size.x > 0.0f)
2507         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
2508 
2509     return value_changed;
2510 }
2511 
VSliderFloat(const char * label,const ImVec2 & size,float * v,float v_min,float v_max,const char * format,float power)2512 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
2513 {
2514     return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
2515 }
2516 
VSliderInt(const char * label,const ImVec2 & size,int * v,int v_min,int v_max,const char * format)2517 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
2518 {
2519     return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
2520 }
2521 
2522 //-------------------------------------------------------------------------
2523 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
2524 //-------------------------------------------------------------------------
2525 // - ImParseFormatFindStart() [Internal]
2526 // - ImParseFormatFindEnd() [Internal]
2527 // - ImParseFormatTrimDecorations() [Internal]
2528 // - ImParseFormatPrecision() [Internal]
2529 // - InputScalarAsWidgetReplacement() [Internal]
2530 // - InputScalar()
2531 // - InputScalarN()
2532 // - InputFloat()
2533 // - InputFloat2()
2534 // - InputFloat3()
2535 // - InputFloat4()
2536 // - InputInt()
2537 // - InputInt2()
2538 // - InputInt3()
2539 // - InputInt4()
2540 // - InputDouble()
2541 //-------------------------------------------------------------------------
2542 
2543 // We don't use strchr() because our strings are usually very short and often start with '%'
ImParseFormatFindStart(const char * fmt)2544 const char* ImParseFormatFindStart(const char* fmt)
2545 {
2546     while (char c = fmt[0])
2547     {
2548         if (c == '%' && fmt[1] != '%')
2549             return fmt;
2550         else if (c == '%')
2551             fmt++;
2552         fmt++;
2553     }
2554     return fmt;
2555 }
2556 
ImParseFormatFindEnd(const char * fmt)2557 const char* ImParseFormatFindEnd(const char* fmt)
2558 {
2559     // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
2560     if (fmt[0] != '%')
2561         return fmt;
2562     const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
2563     const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
2564     for (char c; (c = *fmt) != 0; fmt++)
2565     {
2566         if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
2567             return fmt + 1;
2568         if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
2569             return fmt + 1;
2570     }
2571     return fmt;
2572 }
2573 
2574 // Extract the format out of a format string with leading or trailing decorations
2575 //  fmt = "blah blah"  -> return fmt
2576 //  fmt = "%.3f"       -> return fmt
2577 //  fmt = "hello %.3f" -> return fmt + 6
2578 //  fmt = "%.3f hello" -> return buf written with "%.3f"
ImParseFormatTrimDecorations(const char * fmt,char * buf,size_t buf_size)2579 const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
2580 {
2581     const char* fmt_start = ImParseFormatFindStart(fmt);
2582     if (fmt_start[0] != '%')
2583         return fmt;
2584     const char* fmt_end = ImParseFormatFindEnd(fmt_start);
2585     if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
2586         return fmt_start;
2587     ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
2588     return buf;
2589 }
2590 
2591 // Parse display precision back from the display format string
2592 // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
ImParseFormatPrecision(const char * fmt,int default_precision)2593 int ImParseFormatPrecision(const char* fmt, int default_precision)
2594 {
2595     fmt = ImParseFormatFindStart(fmt);
2596     if (fmt[0] != '%')
2597         return default_precision;
2598     fmt++;
2599     while (*fmt >= '0' && *fmt <= '9')
2600         fmt++;
2601     int precision = INT_MAX;
2602     if (*fmt == '.')
2603     {
2604         fmt = ImAtoi<int>(fmt + 1, &precision);
2605         if (precision < 0 || precision > 99)
2606             precision = default_precision;
2607     }
2608     if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
2609         precision = -1;
2610     if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
2611         precision = -1;
2612     return (precision == INT_MAX) ? default_precision : precision;
2613 }
2614 
2615 // Create text input in place of an active drag/slider (used when doing a CTRL+Click on drag/slider widgets)
2616 // FIXME: Facilitate using this in variety of other situations.
InputScalarAsWidgetReplacement(const ImRect & bb,ImGuiID id,const char * label,ImGuiDataType data_type,void * data_ptr,const char * format)2617 bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
2618 {
2619     ImGuiContext& g = *GImGui;
2620 
2621     // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id.
2622     // We clear ActiveID on the first frame to allow the InputText() taking it back.
2623     if (g.ScalarAsInputTextId == 0)
2624         ClearActiveID();
2625 
2626     char fmt_buf[32];
2627     char data_buf[32];
2628     format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
2629     DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
2630     ImStrTrimBlanks(data_buf);
2631     ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
2632     bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
2633     if (g.ScalarAsInputTextId == 0)
2634     {
2635         // First frame we started displaying the InputText widget, we expect it to take the active id.
2636         IM_ASSERT(g.ActiveId == id);
2637         g.ScalarAsInputTextId = g.ActiveId;
2638     }
2639     if (value_changed)
2640         return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
2641     return false;
2642 }
2643 
InputScalar(const char * label,ImGuiDataType data_type,void * data_ptr,const void * step,const void * step_fast,const char * format,ImGuiInputTextFlags flags)2644 bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags flags)
2645 {
2646     ImGuiWindow* window = GetCurrentWindow();
2647     if (window->SkipItems)
2648         return false;
2649 
2650     ImGuiContext& g = *GImGui;
2651     const ImGuiStyle& style = g.Style;
2652 
2653     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2654     if (format == NULL)
2655         format = GDataTypeInfo[data_type].PrintFmt;
2656 
2657     char buf[64];
2658     DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
2659 
2660     bool value_changed = false;
2661     if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
2662         flags |= ImGuiInputTextFlags_CharsDecimal;
2663     flags |= ImGuiInputTextFlags_AutoSelectAll;
2664 
2665     if (step != NULL)
2666     {
2667         const float button_size = GetFrameHeight();
2668 
2669         BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
2670         PushID(label);
2671         PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
2672         if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
2673             value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
2674         PopItemWidth();
2675 
2676         // Step buttons
2677         ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
2678         if (flags & ImGuiInputTextFlags_ReadOnly)
2679             button_flags |= ImGuiButtonFlags_Disabled;
2680         SameLine(0, style.ItemInnerSpacing.x);
2681         if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
2682         {
2683             DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
2684             value_changed = true;
2685         }
2686         SameLine(0, style.ItemInnerSpacing.x);
2687         if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
2688         {
2689             DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
2690             value_changed = true;
2691         }
2692         SameLine(0, style.ItemInnerSpacing.x);
2693         TextUnformatted(label, FindRenderedTextEnd(label));
2694 
2695         PopID();
2696         EndGroup();
2697     }
2698     else
2699     {
2700         if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
2701             value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
2702     }
2703 
2704     return value_changed;
2705 }
2706 
InputScalarN(const char * label,ImGuiDataType data_type,void * v,int components,const void * step,const void * step_fast,const char * format,ImGuiInputTextFlags flags)2707 bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags flags)
2708 {
2709     ImGuiWindow* window = GetCurrentWindow();
2710     if (window->SkipItems)
2711         return false;
2712 
2713     ImGuiContext& g = *GImGui;
2714     bool value_changed = false;
2715     BeginGroup();
2716     PushID(label);
2717     PushMultiItemsWidths(components);
2718     size_t type_size = GDataTypeInfo[data_type].Size;
2719     for (int i = 0; i < components; i++)
2720     {
2721         PushID(i);
2722         value_changed |= InputScalar("", data_type, v, step, step_fast, format, flags);
2723         SameLine(0, g.Style.ItemInnerSpacing.x);
2724         PopID();
2725         PopItemWidth();
2726         v = (void*)((char*)v + type_size);
2727     }
2728     PopID();
2729 
2730     TextUnformatted(label, FindRenderedTextEnd(label));
2731     EndGroup();
2732     return value_changed;
2733 }
2734 
InputFloat(const char * label,float * v,float step,float step_fast,const char * format,ImGuiInputTextFlags flags)2735 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
2736 {
2737     flags |= ImGuiInputTextFlags_CharsScientific;
2738     return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, flags);
2739 }
2740 
InputFloat2(const char * label,float v[2],const char * format,ImGuiInputTextFlags flags)2741 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
2742 {
2743     return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
2744 }
2745 
InputFloat3(const char * label,float v[3],const char * format,ImGuiInputTextFlags flags)2746 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
2747 {
2748     return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
2749 }
2750 
InputFloat4(const char * label,float v[4],const char * format,ImGuiInputTextFlags flags)2751 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
2752 {
2753     return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
2754 }
2755 
2756 // Prefer using "const char* format" directly, which is more flexible and consistent with other API.
2757 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
InputFloat(const char * label,float * v,float step,float step_fast,int decimal_precision,ImGuiInputTextFlags flags)2758 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags flags)
2759 {
2760     char format[16] = "%f";
2761     if (decimal_precision >= 0)
2762         ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
2763     return InputFloat(label, v, step, step_fast, format, flags);
2764 }
2765 
InputFloat2(const char * label,float v[2],int decimal_precision,ImGuiInputTextFlags flags)2766 bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags flags)
2767 {
2768     char format[16] = "%f";
2769     if (decimal_precision >= 0)
2770         ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
2771     return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
2772 }
2773 
InputFloat3(const char * label,float v[3],int decimal_precision,ImGuiInputTextFlags flags)2774 bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags flags)
2775 {
2776     char format[16] = "%f";
2777     if (decimal_precision >= 0)
2778         ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
2779     return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
2780 }
2781 
InputFloat4(const char * label,float v[4],int decimal_precision,ImGuiInputTextFlags flags)2782 bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags flags)
2783 {
2784     char format[16] = "%f";
2785     if (decimal_precision >= 0)
2786         ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
2787     return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
2788 }
2789 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2790 
InputInt(const char * label,int * v,int step,int step_fast,ImGuiInputTextFlags flags)2791 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
2792 {
2793     // 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.
2794     const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
2795     return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, flags);
2796 }
2797 
InputInt2(const char * label,int v[2],ImGuiInputTextFlags flags)2798 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
2799 {
2800     return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
2801 }
2802 
InputInt3(const char * label,int v[3],ImGuiInputTextFlags flags)2803 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
2804 {
2805     return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
2806 }
2807 
InputInt4(const char * label,int v[4],ImGuiInputTextFlags flags)2808 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
2809 {
2810     return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
2811 }
2812 
InputDouble(const char * label,double * v,double step,double step_fast,const char * format,ImGuiInputTextFlags flags)2813 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
2814 {
2815     flags |= ImGuiInputTextFlags_CharsScientific;
2816     return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, flags);
2817 }
2818 
2819 //-------------------------------------------------------------------------
2820 // [SECTION] Widgets: InputText, InputTextMultiline
2821 //-------------------------------------------------------------------------
2822 // - InputText()
2823 // - InputTextMultiline()
2824 // - InputTextEx() [Internal]
2825 //-------------------------------------------------------------------------
2826 
InputText(const char * label,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)2827 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
2828 {
2829     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
2830     return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
2831 }
2832 
InputTextMultiline(const char * label,char * buf,size_t buf_size,const ImVec2 & size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)2833 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
2834 {
2835     return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
2836 }
2837 
InputTextCalcTextLenAndLineCount(const char * text_begin,const char ** out_text_end)2838 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
2839 {
2840     int line_count = 0;
2841     const char* s = text_begin;
2842     while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
2843         if (c == '\n')
2844             line_count++;
2845     s--;
2846     if (s[0] != '\n' && s[0] != '\r')
2847         line_count++;
2848     *out_text_end = s;
2849     return line_count;
2850 }
2851 
InputTextCalcTextSizeW(const ImWchar * text_begin,const ImWchar * text_end,const ImWchar ** remaining,ImVec2 * out_offset,bool stop_on_new_line)2852 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
2853 {
2854     ImGuiContext& g = *GImGui;
2855     ImFont* font = g.Font;
2856     const float line_height = g.FontSize;
2857     const float scale = line_height / font->FontSize;
2858 
2859     ImVec2 text_size = ImVec2(0,0);
2860     float line_width = 0.0f;
2861 
2862     const ImWchar* s = text_begin;
2863     while (s < text_end)
2864     {
2865         unsigned int c = (unsigned int)(*s++);
2866         if (c == '\n')
2867         {
2868             text_size.x = ImMax(text_size.x, line_width);
2869             text_size.y += line_height;
2870             line_width = 0.0f;
2871             if (stop_on_new_line)
2872                 break;
2873             continue;
2874         }
2875         if (c == '\r')
2876             continue;
2877 
2878         const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
2879         line_width += char_width;
2880     }
2881 
2882     if (text_size.x < line_width)
2883         text_size.x = line_width;
2884 
2885     if (out_offset)
2886         *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
2887 
2888     if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
2889         text_size.y += line_height;
2890 
2891     if (remaining)
2892         *remaining = s;
2893 
2894     return text_size;
2895 }
2896 
2897 // 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)
2898 namespace ImGuiStb
2899 {
2900 
STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING * obj)2901 static int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                             { return obj->CurLenW; }
STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING * obj,int idx)2902 static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                      { return obj->TextW[idx]; }
STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING * obj,int line_start_idx,int char_idx)2903 static float   STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)  { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
STB_TEXTEDIT_KEYTOTEXT(int key)2904 static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x10000 ? 0 : key; }
2905 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
STB_TEXTEDIT_LAYOUTROW(StbTexteditRow * r,STB_TEXTEDIT_STRING * obj,int line_start_idx)2906 static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
2907 {
2908     const ImWchar* text = obj->TextW.Data;
2909     const ImWchar* text_remaining = NULL;
2910     const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
2911     r->x0 = 0.0f;
2912     r->x1 = size.x;
2913     r->baseline_y_delta = size.y;
2914     r->ymin = 0.0f;
2915     r->ymax = size.y;
2916     r->num_chars = (int)(text_remaining - (text + line_start_idx));
2917 }
2918 
is_separator(unsigned int c)2919 static bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
is_word_boundary_from_right(STB_TEXTEDIT_STRING * obj,int idx)2920 static int  is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx)      { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)2921 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; }
2922 #ifdef __APPLE__    // FIXME: Move setting to IO structure
is_word_boundary_from_left(STB_TEXTEDIT_STRING * obj,int idx)2923 static int  is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx)       { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)2924 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; }
2925 #else
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)2926 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; }
2927 #endif
2928 #define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
2929 #define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
2930 
STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING * obj,int pos,int n)2931 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
2932 {
2933     ImWchar* dst = obj->TextW.Data + pos;
2934 
2935     // We maintain our buffer length in both UTF-8 and wchar formats
2936     obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
2937     obj->CurLenW -= n;
2938 
2939     // Offset remaining text (FIXME-OPT: Use memmove)
2940     const ImWchar* src = obj->TextW.Data + pos + n;
2941     while (ImWchar c = *src++)
2942         *dst++ = c;
2943     *dst = '\0';
2944 }
2945 
STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING * obj,int pos,const ImWchar * new_text,int new_text_len)2946 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
2947 {
2948     const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
2949     const int text_len = obj->CurLenW;
2950     IM_ASSERT(pos <= text_len);
2951 
2952     const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
2953     if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
2954         return false;
2955 
2956     // Grow internal buffer if needed
2957     if (new_text_len + text_len + 1 > obj->TextW.Size)
2958     {
2959         if (!is_resizable)
2960             return false;
2961         IM_ASSERT(text_len < obj->TextW.Size);
2962         obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
2963     }
2964 
2965     ImWchar* text = obj->TextW.Data;
2966     if (pos != text_len)
2967         memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
2968     memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
2969 
2970     obj->CurLenW += new_text_len;
2971     obj->CurLenA += new_text_len_utf8;
2972     obj->TextW[obj->CurLenW] = '\0';
2973 
2974     return true;
2975 }
2976 
2977 // 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)
2978 #define STB_TEXTEDIT_K_LEFT         0x10000 // keyboard input to move cursor left
2979 #define STB_TEXTEDIT_K_RIGHT        0x10001 // keyboard input to move cursor right
2980 #define STB_TEXTEDIT_K_UP           0x10002 // keyboard input to move cursor up
2981 #define STB_TEXTEDIT_K_DOWN         0x10003 // keyboard input to move cursor down
2982 #define STB_TEXTEDIT_K_LINESTART    0x10004 // keyboard input to move cursor to start of line
2983 #define STB_TEXTEDIT_K_LINEEND      0x10005 // keyboard input to move cursor to end of line
2984 #define STB_TEXTEDIT_K_TEXTSTART    0x10006 // keyboard input to move cursor to start of text
2985 #define STB_TEXTEDIT_K_TEXTEND      0x10007 // keyboard input to move cursor to end of text
2986 #define STB_TEXTEDIT_K_DELETE       0x10008 // keyboard input to delete selection or character under cursor
2987 #define STB_TEXTEDIT_K_BACKSPACE    0x10009 // keyboard input to delete selection or character left of cursor
2988 #define STB_TEXTEDIT_K_UNDO         0x1000A // keyboard input to perform undo
2989 #define STB_TEXTEDIT_K_REDO         0x1000B // keyboard input to perform redo
2990 #define STB_TEXTEDIT_K_WORDLEFT     0x1000C // keyboard input to move cursor left one word
2991 #define STB_TEXTEDIT_K_WORDRIGHT    0x1000D // keyboard input to move cursor right one word
2992 #define STB_TEXTEDIT_K_SHIFT        0x20000
2993 
2994 #define STB_TEXTEDIT_IMPLEMENTATION
2995 #include "imstb_textedit.h"
2996 
2997 }
2998 
OnKeyPressed(int key)2999 void ImGuiInputTextState::OnKeyPressed(int key)
3000 {
3001     stb_textedit_key(this, &StbState, key);
3002     CursorFollow = true;
3003     CursorAnimReset();
3004 }
3005 
ImGuiInputTextCallbackData()3006 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
3007 {
3008     memset(this, 0, sizeof(*this));
3009 }
3010 
3011 // Public API to manipulate UTF-8 text
3012 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
3013 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
DeleteChars(int pos,int bytes_count)3014 void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
3015 {
3016     IM_ASSERT(pos + bytes_count <= BufTextLen);
3017     char* dst = Buf + pos;
3018     const char* src = Buf + pos + bytes_count;
3019     while (char c = *src++)
3020         *dst++ = c;
3021     *dst = '\0';
3022 
3023     if (CursorPos + bytes_count >= pos)
3024         CursorPos -= bytes_count;
3025     else if (CursorPos >= pos)
3026         CursorPos = pos;
3027     SelectionStart = SelectionEnd = CursorPos;
3028     BufDirty = true;
3029     BufTextLen -= bytes_count;
3030 }
3031 
InsertChars(int pos,const char * new_text,const char * new_text_end)3032 void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
3033 {
3034     const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3035     const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
3036     if (new_text_len + BufTextLen >= BufSize)
3037     {
3038         if (!is_resizable)
3039             return;
3040 
3041         // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
3042         ImGuiContext& g = *GImGui;
3043         ImGuiInputTextState* edit_state = &g.InputTextState;
3044         IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
3045         IM_ASSERT(Buf == edit_state->TempBuffer.Data);
3046         int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
3047         edit_state->TempBuffer.reserve(new_buf_size + 1);
3048         Buf = edit_state->TempBuffer.Data;
3049         BufSize = edit_state->BufCapacityA = new_buf_size;
3050     }
3051 
3052     if (BufTextLen != pos)
3053         memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
3054     memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
3055     Buf[BufTextLen + new_text_len] = '\0';
3056 
3057     if (CursorPos >= pos)
3058         CursorPos += new_text_len;
3059     SelectionStart = SelectionEnd = CursorPos;
3060     BufDirty = true;
3061     BufTextLen += new_text_len;
3062 }
3063 
3064 // Return false to discard a character.
InputTextFilterCharacter(unsigned int * p_char,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3065 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3066 {
3067     unsigned int c = *p_char;
3068 
3069     if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
3070     {
3071         bool pass = false;
3072         pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
3073         pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
3074         if (!pass)
3075             return false;
3076     }
3077 
3078     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.
3079         return false;
3080 
3081     if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
3082     {
3083         if (flags & ImGuiInputTextFlags_CharsDecimal)
3084             if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
3085                 return false;
3086 
3087         if (flags & ImGuiInputTextFlags_CharsScientific)
3088             if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
3089                 return false;
3090 
3091         if (flags & ImGuiInputTextFlags_CharsHexadecimal)
3092             if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
3093                 return false;
3094 
3095         if (flags & ImGuiInputTextFlags_CharsUppercase)
3096             if (c >= 'a' && c <= 'z')
3097                 *p_char = (c += (unsigned int)('A'-'a'));
3098 
3099         if (flags & ImGuiInputTextFlags_CharsNoBlank)
3100             if (ImCharIsBlankW(c))
3101                 return false;
3102     }
3103 
3104     if (flags & ImGuiInputTextFlags_CallbackCharFilter)
3105     {
3106         ImGuiInputTextCallbackData callback_data;
3107         memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
3108         callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
3109         callback_data.EventChar = (ImWchar)c;
3110         callback_data.Flags = flags;
3111         callback_data.UserData = user_data;
3112         if (callback(&callback_data) != 0)
3113             return false;
3114         *p_char = callback_data.EventChar;
3115         if (!callback_data.EventChar)
3116             return false;
3117     }
3118 
3119     return true;
3120 }
3121 
3122 // Edit a string of text
3123 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
3124 //   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
3125 //   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
3126 // - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
3127 // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
3128 // (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
InputTextEx(const char * label,char * buf,int buf_size,const ImVec2 & size_arg,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * callback_user_data)3129 bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
3130 {
3131     ImGuiWindow* window = GetCurrentWindow();
3132     if (window->SkipItems)
3133         return false;
3134 
3135     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
3136     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
3137 
3138     ImGuiContext& g = *GImGui;
3139     ImGuiIO& io = g.IO;
3140     const ImGuiStyle& style = g.Style;
3141 
3142     const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
3143     const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
3144     const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
3145     const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
3146     const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
3147     if (is_resizable)
3148         IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
3149 
3150     if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
3151         BeginGroup();
3152     const ImGuiID id = window->GetID(label);
3153     const ImVec2 label_size = CalcTextSize(label, NULL, true);
3154     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
3155     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3156     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));
3157 
3158     ImGuiWindow* draw_window = window;
3159     if (is_multiline)
3160     {
3161         if (!ItemAdd(total_bb, id, &frame_bb))
3162         {
3163             ItemSize(total_bb, style.FramePadding.y);
3164             EndGroup();
3165             return false;
3166         }
3167         if (!BeginChildFrame(id, frame_bb.GetSize()))
3168         {
3169             EndChildFrame();
3170             EndGroup();
3171             return false;
3172         }
3173         draw_window = GetCurrentWindow();
3174         draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
3175         size.x -= draw_window->ScrollbarSizes.x;
3176     }
3177     else
3178     {
3179         ItemSize(total_bb, style.FramePadding.y);
3180         if (!ItemAdd(total_bb, id, &frame_bb))
3181             return false;
3182     }
3183     const bool hovered = ItemHoverable(frame_bb, id);
3184     if (hovered)
3185         g.MouseCursor = ImGuiMouseCursor_TextInput;
3186 
3187     // Password pushes a temporary font with only a fallback glyph
3188     if (is_password)
3189     {
3190         const ImFontGlyph* glyph = g.Font->FindGlyph('*');
3191         ImFont* password_font = &g.InputTextPasswordFont;
3192         password_font->FontSize = g.Font->FontSize;
3193         password_font->Scale = g.Font->Scale;
3194         password_font->DisplayOffset = g.Font->DisplayOffset;
3195         password_font->Ascent = g.Font->Ascent;
3196         password_font->Descent = g.Font->Descent;
3197         password_font->ContainerAtlas = g.Font->ContainerAtlas;
3198         password_font->FallbackGlyph = glyph;
3199         password_font->FallbackAdvanceX = glyph->AdvanceX;
3200         IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
3201         PushFont(password_font);
3202     }
3203 
3204     // NB: we are only allowed to access 'edit_state' if we are the active widget.
3205     ImGuiInputTextState& edit_state = g.InputTextState;
3206 
3207     const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0);    // Using completion callback disable keyboard tabbing
3208     const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
3209     const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
3210 
3211     const bool user_clicked = hovered && io.MouseClicked[0];
3212     const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
3213     const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
3214 
3215     bool clear_active_id = false;
3216 
3217     bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
3218     if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
3219     {
3220         if (g.ActiveId != id)
3221         {
3222             // Start edition
3223             // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
3224             // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
3225             const int prev_len_w = edit_state.CurLenW;
3226             const int init_buf_len = (int)strlen(buf);
3227             edit_state.TextW.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.
3228             edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
3229             memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
3230             const char* buf_end = NULL;
3231             edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
3232             edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
3233             edit_state.CursorAnimReset();
3234 
3235             // Preserve cursor position and undo/redo stack if we come back to same widget
3236             // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
3237             const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
3238             if (recycle_state)
3239             {
3240                 // Recycle existing cursor/selection/undo stack but clamp position
3241                 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
3242                 edit_state.CursorClamp();
3243             }
3244             else
3245             {
3246                 edit_state.ID = id;
3247                 edit_state.ScrollX = 0.0f;
3248                 stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
3249                 if (!is_multiline && focus_requested_by_code)
3250                     select_all = true;
3251             }
3252             if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
3253                 edit_state.StbState.insert_mode = 1;
3254             if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
3255                 select_all = true;
3256         }
3257         SetActiveID(id, window);
3258         SetFocusID(id, window);
3259         FocusWindow(window);
3260         g.ActiveIdBlockNavInputFlags = (1 << ImGuiNavInput_Cancel);
3261         if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
3262             g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
3263     }
3264     else if (io.MouseClicked[0])
3265     {
3266         // Release focus when we click outside
3267         clear_active_id = true;
3268     }
3269 
3270     bool value_changed = false;
3271     bool enter_pressed = false;
3272     int backup_current_text_length = 0;
3273 
3274     if (g.ActiveId == id)
3275     {
3276         if (!is_editable && !g.ActiveIdIsJustActivated)
3277         {
3278             // When read-only we always use the live data passed to the function
3279             edit_state.TextW.resize(buf_size+1);
3280             const char* buf_end = NULL;
3281             edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
3282             edit_state.CurLenA = (int)(buf_end - buf);
3283             edit_state.CursorClamp();
3284         }
3285 
3286         backup_current_text_length = edit_state.CurLenA;
3287         edit_state.BufCapacityA = buf_size;
3288         edit_state.UserFlags = flags;
3289         edit_state.UserCallback = callback;
3290         edit_state.UserCallbackData = callback_user_data;
3291 
3292         // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
3293         // Down the line we should have a cleaner library-wide concept of Selected vs Active.
3294         g.ActiveIdAllowOverlap = !io.MouseDown[0];
3295         g.WantTextInputNextFrame = 1;
3296 
3297         // Edit in progress
3298         const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
3299         const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
3300 
3301         const bool is_osx = io.ConfigMacOSXBehaviors;
3302         if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
3303         {
3304             edit_state.SelectAll();
3305             edit_state.SelectedAllMouseLock = true;
3306         }
3307         else if (hovered && is_osx && io.MouseDoubleClicked[0])
3308         {
3309             // Double-click select a word only, OS X style (by simulating keystrokes)
3310             edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
3311             edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
3312         }
3313         else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
3314         {
3315             if (hovered)
3316             {
3317                 stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
3318                 edit_state.CursorAnimReset();
3319             }
3320         }
3321         else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
3322         {
3323             stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
3324             edit_state.CursorAnimReset();
3325             edit_state.CursorFollow = true;
3326         }
3327         if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
3328             edit_state.SelectedAllMouseLock = false;
3329 
3330         if (io.InputQueueCharacters.Size > 0)
3331         {
3332             // Process text input (before we check for Return because using some IME will effectively send a Return?)
3333             // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
3334             bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
3335             if (!ignore_inputs && is_editable && !user_nav_input_start)
3336                 for (int n = 0; n < io.InputQueueCharacters.Size; n++)
3337                 {
3338                     // Insert character if they pass filtering
3339                     unsigned int c = (unsigned int)io.InputQueueCharacters[n];
3340                     if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3341                         edit_state.OnKeyPressed((int)c);
3342                 }
3343 
3344             // Consume characters
3345             io.InputQueueCharacters.resize(0);
3346         }
3347     }
3348 
3349     bool cancel_edit = false;
3350     if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
3351     {
3352         // Handle key-presses
3353         const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
3354         const bool is_osx = io.ConfigMacOSXBehaviors;
3355         const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
3356         const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
3357         const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
3358         const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
3359         const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
3360         const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
3361 
3362         const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
3363         const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
3364         const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
3365         const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
3366         const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
3367 
3368         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); }
3369         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); }
3370         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); }
3371         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); }
3372         else if (IsKeyPressedMap(ImGuiKey_Home))                        { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
3373         else if (IsKeyPressedMap(ImGuiKey_End))                         { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
3374         else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable)       { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
3375         else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
3376         {
3377             if (!edit_state.HasSelection())
3378             {
3379                 if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
3380                 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
3381             }
3382             edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
3383         }
3384         else if (IsKeyPressedMap(ImGuiKey_Enter))
3385         {
3386             bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
3387             if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
3388             {
3389                 enter_pressed = clear_active_id = true;
3390             }
3391             else if (is_editable)
3392             {
3393                 unsigned int c = '\n'; // Insert new line
3394                 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3395                     edit_state.OnKeyPressed((int)c);
3396             }
3397         }
3398         else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
3399         {
3400             unsigned int c = '\t'; // Insert TAB
3401             if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3402                 edit_state.OnKeyPressed((int)c);
3403         }
3404         else if (IsKeyPressedMap(ImGuiKey_Escape))
3405         {
3406             clear_active_id = cancel_edit = true;
3407         }
3408         else if (is_undo || is_redo)
3409         {
3410             edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
3411             edit_state.ClearSelection();
3412         }
3413         else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
3414         {
3415             edit_state.SelectAll();
3416             edit_state.CursorFollow = true;
3417         }
3418         else if (is_cut || is_copy)
3419         {
3420             // Cut, Copy
3421             if (io.SetClipboardTextFn)
3422             {
3423                 const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
3424                 const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
3425                 edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
3426                 ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
3427                 SetClipboardText(edit_state.TempBuffer.Data);
3428             }
3429             if (is_cut)
3430             {
3431                 if (!edit_state.HasSelection())
3432                     edit_state.SelectAll();
3433                 edit_state.CursorFollow = true;
3434                 stb_textedit_cut(&edit_state, &edit_state.StbState);
3435             }
3436         }
3437         else if (is_paste)
3438         {
3439             if (const char* clipboard = GetClipboardText())
3440             {
3441                 // Filter pasted buffer
3442                 const int clipboard_len = (int)strlen(clipboard);
3443                 ImWchar* clipboard_filtered = (ImWchar*)MemAlloc((clipboard_len+1) * sizeof(ImWchar));
3444                 int clipboard_filtered_len = 0;
3445                 for (const char* s = clipboard; *s; )
3446                 {
3447                     unsigned int c;
3448                     s += ImTextCharFromUtf8(&c, s, NULL);
3449                     if (c == 0)
3450                         break;
3451                     if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3452                         continue;
3453                     clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
3454                 }
3455                 clipboard_filtered[clipboard_filtered_len] = 0;
3456                 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
3457                 {
3458                     stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
3459                     edit_state.CursorFollow = true;
3460                 }
3461                 MemFree(clipboard_filtered);
3462             }
3463         }
3464     }
3465 
3466     if (g.ActiveId == id)
3467     {
3468         const char* apply_new_text = NULL;
3469         int apply_new_text_length = 0;
3470         if (cancel_edit)
3471         {
3472             // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
3473             if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
3474             {
3475                 apply_new_text = edit_state.InitialText.Data;
3476                 apply_new_text_length = edit_state.InitialText.Size - 1;
3477             }
3478         }
3479 
3480         // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
3481         // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
3482         bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
3483         if (apply_edit_back_to_user_buffer)
3484         {
3485             // Apply new value immediately - copy modified buffer back
3486             // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
3487             // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
3488             // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
3489             if (is_editable)
3490             {
3491                 edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
3492                 ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
3493             }
3494 
3495             // User callback
3496             if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
3497             {
3498                 IM_ASSERT(callback != NULL);
3499 
3500                 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
3501                 ImGuiInputTextFlags event_flag = 0;
3502                 ImGuiKey event_key = ImGuiKey_COUNT;
3503                 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
3504                 {
3505                     event_flag = ImGuiInputTextFlags_CallbackCompletion;
3506                     event_key = ImGuiKey_Tab;
3507                 }
3508                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
3509                 {
3510                     event_flag = ImGuiInputTextFlags_CallbackHistory;
3511                     event_key = ImGuiKey_UpArrow;
3512                 }
3513                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
3514                 {
3515                     event_flag = ImGuiInputTextFlags_CallbackHistory;
3516                     event_key = ImGuiKey_DownArrow;
3517                 }
3518                 else if (flags & ImGuiInputTextFlags_CallbackAlways)
3519                     event_flag = ImGuiInputTextFlags_CallbackAlways;
3520 
3521                 if (event_flag)
3522                 {
3523                     ImGuiInputTextCallbackData callback_data;
3524                     memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
3525                     callback_data.EventFlag = event_flag;
3526                     callback_data.Flags = flags;
3527                     callback_data.UserData = callback_user_data;
3528 
3529                     callback_data.EventKey = event_key;
3530                     callback_data.Buf = edit_state.TempBuffer.Data;
3531                     callback_data.BufTextLen = edit_state.CurLenA;
3532                     callback_data.BufSize = edit_state.BufCapacityA;
3533                     callback_data.BufDirty = false;
3534 
3535                     // 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)
3536                     ImWchar* text = edit_state.TextW.Data;
3537                     const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
3538                     const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
3539                     const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
3540 
3541                     // Call user code
3542                     callback(&callback_data);
3543 
3544                     // Read back what user may have modified
3545                     IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data);  // Invalid to modify those fields
3546                     IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
3547                     IM_ASSERT(callback_data.Flags == flags);
3548                     if (callback_data.CursorPos != utf8_cursor_pos)            { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
3549                     if (callback_data.SelectionStart != utf8_selection_start)  { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
3550                     if (callback_data.SelectionEnd != utf8_selection_end)      { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
3551                     if (callback_data.BufDirty)
3552                     {
3553                         IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
3554                         if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
3555                             edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
3556                         edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
3557                         edit_state.CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
3558                         edit_state.CursorAnimReset();
3559                     }
3560                 }
3561             }
3562 
3563             // Will copy result string if modified
3564             if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
3565             {
3566                 apply_new_text = edit_state.TempBuffer.Data;
3567                 apply_new_text_length = edit_state.CurLenA;
3568             }
3569         }
3570 
3571         // Copy result to user buffer
3572         if (apply_new_text)
3573         {
3574             IM_ASSERT(apply_new_text_length >= 0);
3575             if (backup_current_text_length != apply_new_text_length && is_resizable)
3576             {
3577                 ImGuiInputTextCallbackData callback_data;
3578                 callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
3579                 callback_data.Flags = flags;
3580                 callback_data.Buf = buf;
3581                 callback_data.BufTextLen = apply_new_text_length;
3582                 callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
3583                 callback_data.UserData = callback_user_data;
3584                 callback(&callback_data);
3585                 buf = callback_data.Buf;
3586                 buf_size = callback_data.BufSize;
3587                 apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
3588                 IM_ASSERT(apply_new_text_length <= buf_size);
3589             }
3590 
3591             // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
3592             ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
3593             value_changed = true;
3594         }
3595 
3596         // Clear temporary user storage
3597         edit_state.UserFlags = 0;
3598         edit_state.UserCallback = NULL;
3599         edit_state.UserCallbackData = NULL;
3600     }
3601 
3602     // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
3603     if (clear_active_id && g.ActiveId == id)
3604         ClearActiveID();
3605 
3606     // Render
3607     // 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.
3608     const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
3609 
3610     // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
3611     // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
3612     // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
3613     const int buf_display_max_length = 2 * 1024 * 1024;
3614 
3615     if (!is_multiline)
3616     {
3617         RenderNavHighlight(frame_bb, id);
3618         RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
3619     }
3620 
3621     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
3622     ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
3623     ImVec2 text_size(0.f, 0.f);
3624     const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
3625     if (g.ActiveId == id || is_currently_scrolling)
3626     {
3627         edit_state.CursorAnim += io.DeltaTime;
3628 
3629         // This is going to be messy. We need to:
3630         // - Display the text (this alone can be more easily clipped)
3631         // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
3632         // - Measure text height (for scrollbar)
3633         // 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)
3634         // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
3635         const ImWchar* text_begin = edit_state.TextW.Data;
3636         ImVec2 cursor_offset, select_start_offset;
3637 
3638         {
3639             // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
3640             const ImWchar* searches_input_ptr[2];
3641             searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
3642             searches_input_ptr[1] = NULL;
3643             int searches_remaining = 1;
3644             int searches_result_line_number[2] = { -1, -999 };
3645             if (edit_state.StbState.select_start != edit_state.StbState.select_end)
3646             {
3647                 searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
3648                 searches_result_line_number[1] = -1;
3649                 searches_remaining++;
3650             }
3651 
3652             // Iterate all lines to find our line numbers
3653             // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
3654             searches_remaining += is_multiline ? 1 : 0;
3655             int line_count = 0;
3656             //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++)  // FIXME-OPT: Could use this when wchar_t are 16-bits
3657             for (const ImWchar* s = text_begin; *s != 0; s++)
3658                 if (*s == '\n')
3659                 {
3660                     line_count++;
3661                     if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
3662                     if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
3663                 }
3664             line_count++;
3665             if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
3666             if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
3667 
3668             // Calculate 2d position by finding the beginning of the line and measuring distance
3669             cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
3670             cursor_offset.y = searches_result_line_number[0] * g.FontSize;
3671             if (searches_result_line_number[1] >= 0)
3672             {
3673                 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
3674                 select_start_offset.y = searches_result_line_number[1] * g.FontSize;
3675             }
3676 
3677             // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
3678             if (is_multiline)
3679                 text_size = ImVec2(size.x, line_count * g.FontSize);
3680         }
3681 
3682         // Scroll
3683         if (edit_state.CursorFollow)
3684         {
3685             // Horizontal scroll in chunks of quarter width
3686             if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
3687             {
3688                 const float scroll_increment_x = size.x * 0.25f;
3689                 if (cursor_offset.x < edit_state.ScrollX)
3690                     edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
3691                 else if (cursor_offset.x - size.x >= edit_state.ScrollX)
3692                     edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
3693             }
3694             else
3695             {
3696                 edit_state.ScrollX = 0.0f;
3697             }
3698 
3699             // Vertical scroll
3700             if (is_multiline)
3701             {
3702                 float scroll_y = draw_window->Scroll.y;
3703                 if (cursor_offset.y - g.FontSize < scroll_y)
3704                     scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
3705                 else if (cursor_offset.y - size.y >= scroll_y)
3706                     scroll_y = cursor_offset.y - size.y;
3707                 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y);   // To avoid a frame of lag
3708                 draw_window->Scroll.y = scroll_y;
3709                 render_pos.y = draw_window->DC.CursorPos.y;
3710             }
3711         }
3712         edit_state.CursorFollow = false;
3713         const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
3714 
3715         // Draw selection
3716         if (edit_state.StbState.select_start != edit_state.StbState.select_end)
3717         {
3718             const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
3719             const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
3720 
3721             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.
3722             float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
3723             ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
3724             ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
3725             for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
3726             {
3727                 if (rect_pos.y > clip_rect.w + g.FontSize)
3728                     break;
3729                 if (rect_pos.y < clip_rect.y)
3730                 {
3731                     //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p);  // FIXME-OPT: Could use this when wchar_t are 16-bits
3732                     //p = p ? p + 1 : text_selected_end;
3733                     while (p < text_selected_end)
3734                         if (*p++ == '\n')
3735                             break;
3736                 }
3737                 else
3738                 {
3739                     ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
3740                     if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
3741                     ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
3742                     rect.ClipWith(clip_rect);
3743                     if (rect.Overlaps(clip_rect))
3744                         draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
3745                 }
3746                 rect_pos.x = render_pos.x - render_scroll.x;
3747                 rect_pos.y += g.FontSize;
3748             }
3749         }
3750 
3751         const int buf_display_len = edit_state.CurLenA;
3752         if (is_multiline || buf_display_len < buf_display_max_length)
3753             draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
3754 
3755         // Draw blinking cursor
3756         bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
3757         ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
3758         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);
3759         if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
3760             draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
3761 
3762         // 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.)
3763         if (is_editable)
3764             g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
3765     }
3766     else
3767     {
3768         // Render text only
3769         const char* buf_end = NULL;
3770         if (is_multiline)
3771             text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
3772         else
3773             buf_end = buf_display + strlen(buf_display);
3774         if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
3775             draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
3776     }
3777 
3778     if (is_multiline)
3779     {
3780         Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
3781         EndChildFrame();
3782         EndGroup();
3783     }
3784 
3785     if (is_password)
3786         PopFont();
3787 
3788     // Log as text
3789     if (g.LogEnabled && !is_password)
3790         LogRenderedText(&render_pos, buf_display, NULL);
3791 
3792     if (label_size.x > 0)
3793         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3794 
3795     if (value_changed)
3796         MarkItemEdited(id);
3797 
3798     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
3799     if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
3800         return enter_pressed;
3801     else
3802         return value_changed;
3803 }
3804 
3805 //-------------------------------------------------------------------------
3806 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
3807 //-------------------------------------------------------------------------
3808 // - ColorEdit3()
3809 // - ColorEdit4()
3810 // - ColorPicker3()
3811 // - RenderColorRectWithAlphaCheckerboard() [Internal]
3812 // - ColorPicker4()
3813 // - ColorButton()
3814 // - SetColorEditOptions()
3815 // - ColorTooltip() [Internal]
3816 // - ColorEditOptionsPopup() [Internal]
3817 // - ColorPickerOptionsPopup() [Internal]
3818 //-------------------------------------------------------------------------
3819 
ColorEdit3(const char * label,float col[3],ImGuiColorEditFlags flags)3820 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
3821 {
3822     return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
3823 }
3824 
3825 // Edit colors components (each component in 0.0f..1.0f range).
3826 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
3827 // With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
ColorEdit4(const char * label,float col[4],ImGuiColorEditFlags flags)3828 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
3829 {
3830     ImGuiWindow* window = GetCurrentWindow();
3831     if (window->SkipItems)
3832         return false;
3833 
3834     ImGuiContext& g = *GImGui;
3835     const ImGuiStyle& style = g.Style;
3836     const float square_sz = GetFrameHeight();
3837     const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
3838     const float w_items_all = CalcItemWidth() - w_extra;
3839     const char* label_display_end = FindRenderedTextEnd(label);
3840 
3841     BeginGroup();
3842     PushID(label);
3843 
3844     // If we're not showing any slider there's no point in doing any HSV conversions
3845     const ImGuiColorEditFlags flags_untouched = flags;
3846     if (flags & ImGuiColorEditFlags_NoInputs)
3847         flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
3848 
3849     // Context menu: display and modify options (before defaults are applied)
3850     if (!(flags & ImGuiColorEditFlags_NoOptions))
3851         ColorEditOptionsPopup(col, flags);
3852 
3853     // Read stored options
3854     if (!(flags & ImGuiColorEditFlags__InputsMask))
3855         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
3856     if (!(flags & ImGuiColorEditFlags__DataTypeMask))
3857         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
3858     if (!(flags & ImGuiColorEditFlags__PickerMask))
3859         flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
3860     flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
3861 
3862     const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
3863     const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
3864     const int components = alpha ? 4 : 3;
3865 
3866     // Convert to the formats we need
3867     float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
3868     if (flags & ImGuiColorEditFlags_HSV)
3869         ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
3870     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]) };
3871 
3872     bool value_changed = false;
3873     bool value_changed_as_float = false;
3874 
3875     if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
3876     {
3877         // RGB/HSV 0..255 Sliders
3878         const float w_item_one  = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
3879         const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
3880 
3881         const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
3882         const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
3883         const char* fmt_table_int[3][4] =
3884         {
3885             {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
3886             { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
3887             { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
3888         };
3889         const char* fmt_table_float[3][4] =
3890         {
3891             {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
3892             { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
3893             { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
3894         };
3895         const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
3896 
3897         PushItemWidth(w_item_one);
3898         for (int n = 0; n < components; n++)
3899         {
3900             if (n > 0)
3901                 SameLine(0, style.ItemInnerSpacing.x);
3902             if (n + 1 == components)
3903                 PushItemWidth(w_item_last);
3904             if (flags & ImGuiColorEditFlags_Float)
3905             {
3906                 value_changed |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
3907                 value_changed_as_float |= value_changed;
3908             }
3909             else
3910             {
3911                 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
3912             }
3913             if (!(flags & ImGuiColorEditFlags_NoOptions))
3914                 OpenPopupOnItemClick("context");
3915         }
3916         PopItemWidth();
3917         PopItemWidth();
3918     }
3919     else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
3920     {
3921         // RGB Hexadecimal Input
3922         char buf[64];
3923         if (alpha)
3924             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
3925         else
3926             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
3927         PushItemWidth(w_items_all);
3928         if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
3929         {
3930             value_changed = true;
3931             char* p = buf;
3932             while (*p == '#' || ImCharIsBlankA(*p))
3933                 p++;
3934             i[0] = i[1] = i[2] = i[3] = 0;
3935             if (alpha)
3936                 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)
3937             else
3938                 sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
3939         }
3940         if (!(flags & ImGuiColorEditFlags_NoOptions))
3941             OpenPopupOnItemClick("context");
3942         PopItemWidth();
3943     }
3944 
3945     ImGuiWindow* picker_active_window = NULL;
3946     if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
3947     {
3948         if (!(flags & ImGuiColorEditFlags_NoInputs))
3949             SameLine(0, style.ItemInnerSpacing.x);
3950 
3951         const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
3952         if (ColorButton("##ColorButton", col_v4, flags))
3953         {
3954             if (!(flags & ImGuiColorEditFlags_NoPicker))
3955             {
3956                 // Store current color and open a picker
3957                 g.ColorPickerRef = col_v4;
3958                 OpenPopup("picker");
3959                 SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
3960             }
3961         }
3962         if (!(flags & ImGuiColorEditFlags_NoOptions))
3963             OpenPopupOnItemClick("context");
3964 
3965         if (BeginPopup("picker"))
3966         {
3967             picker_active_window = g.CurrentWindow;
3968             if (label != label_display_end)
3969             {
3970                 TextUnformatted(label, label_display_end);
3971                 Spacing();
3972             }
3973             ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
3974             ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
3975             PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
3976             value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
3977             PopItemWidth();
3978             EndPopup();
3979         }
3980     }
3981 
3982     if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
3983     {
3984         SameLine(0, style.ItemInnerSpacing.x);
3985         TextUnformatted(label, label_display_end);
3986     }
3987 
3988     // Convert back
3989     if (picker_active_window == NULL)
3990     {
3991         if (!value_changed_as_float)
3992             for (int n = 0; n < 4; n++)
3993                 f[n] = i[n] / 255.0f;
3994         if (flags & ImGuiColorEditFlags_HSV)
3995             ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
3996         if (value_changed)
3997         {
3998             col[0] = f[0];
3999             col[1] = f[1];
4000             col[2] = f[2];
4001             if (alpha)
4002                 col[3] = f[3];
4003         }
4004     }
4005 
4006     PopID();
4007     EndGroup();
4008 
4009     // Drag and Drop Target
4010     // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
4011     if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
4012     {
4013         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
4014         {
4015             memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
4016             value_changed = true;
4017         }
4018         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
4019         {
4020             memcpy((float*)col, payload->Data, sizeof(float) * components);
4021             value_changed = true;
4022         }
4023         EndDragDropTarget();
4024     }
4025 
4026     // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
4027     if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
4028         window->DC.LastItemId = g.ActiveId;
4029 
4030     if (value_changed)
4031         MarkItemEdited(window->DC.LastItemId);
4032 
4033     return value_changed;
4034 }
4035 
ColorPicker3(const char * label,float col[3],ImGuiColorEditFlags flags)4036 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
4037 {
4038     float col4[4] = { col[0], col[1], col[2], 1.0f };
4039     if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
4040         return false;
4041     col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
4042     return true;
4043 }
4044 
ImAlphaBlendColor(ImU32 col_a,ImU32 col_b)4045 static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
4046 {
4047     float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
4048     int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
4049     int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
4050     int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
4051     return IM_COL32(r, g, b, 0xFF);
4052 }
4053 
4054 // Helper for ColorPicker4()
4055 // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
4056 // I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
RenderColorRectWithAlphaCheckerboard(ImVec2 p_min,ImVec2 p_max,ImU32 col,float grid_step,ImVec2 grid_off,float rounding,int rounding_corners_flags)4057 void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
4058 {
4059     ImGuiWindow* window = GetCurrentWindow();
4060     if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
4061     {
4062         ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
4063         ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
4064         window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
4065 
4066         int yi = 0;
4067         for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
4068         {
4069             float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
4070             if (y2 <= y1)
4071                 continue;
4072             for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
4073             {
4074                 float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
4075                 if (x2 <= x1)
4076                     continue;
4077                 int rounding_corners_flags_cell = 0;
4078                 if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
4079                 if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
4080                 rounding_corners_flags_cell &= rounding_corners_flags;
4081                 window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
4082             }
4083         }
4084     }
4085     else
4086     {
4087         window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
4088     }
4089 }
4090 
4091 // Helper for ColorPicker4()
RenderArrowsForVerticalBar(ImDrawList * draw_list,ImVec2 pos,ImVec2 half_sz,float bar_w)4092 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
4093 {
4094     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1,         pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK);
4095     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32_WHITE);
4096     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left,  IM_COL32_BLACK);
4097     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32_WHITE);
4098 }
4099 
4100 // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4101 // FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
ColorPicker4(const char * label,float col[4],ImGuiColorEditFlags flags,const float * ref_col)4102 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
4103 {
4104     ImGuiContext& g = *GImGui;
4105     ImGuiWindow* window = GetCurrentWindow();
4106     ImDrawList* draw_list = window->DrawList;
4107 
4108     ImGuiStyle& style = g.Style;
4109     ImGuiIO& io = g.IO;
4110 
4111     PushID(label);
4112     BeginGroup();
4113 
4114     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4115         flags |= ImGuiColorEditFlags_NoSmallPreview;
4116 
4117     // Context menu: display and store options.
4118     if (!(flags & ImGuiColorEditFlags_NoOptions))
4119         ColorPickerOptionsPopup(col, flags);
4120 
4121     // Read stored options
4122     if (!(flags & ImGuiColorEditFlags__PickerMask))
4123         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
4124     IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
4125     if (!(flags & ImGuiColorEditFlags_NoOptions))
4126         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
4127 
4128     // Setup
4129     int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
4130     bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
4131     ImVec2 picker_pos = window->DC.CursorPos;
4132     float square_sz = GetFrameHeight();
4133     float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
4134     float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
4135     float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
4136     float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
4137     float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
4138 
4139     float backup_initial_col[4];
4140     memcpy(backup_initial_col, col, components * sizeof(float));
4141 
4142     float wheel_thickness = sv_picker_size * 0.08f;
4143     float wheel_r_outer = sv_picker_size * 0.50f;
4144     float wheel_r_inner = wheel_r_outer - wheel_thickness;
4145     ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
4146 
4147     // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
4148     float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
4149     ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
4150     ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
4151     ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
4152 
4153     float H,S,V;
4154     ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
4155 
4156     bool value_changed = false, value_changed_h = false, value_changed_sv = false;
4157 
4158     PushItemFlag(ImGuiItemFlags_NoNav, true);
4159     if (flags & ImGuiColorEditFlags_PickerHueWheel)
4160     {
4161         // Hue wheel + SV triangle logic
4162         InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
4163         if (IsItemActive())
4164         {
4165             ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
4166             ImVec2 current_off = g.IO.MousePos - wheel_center;
4167             float initial_dist2 = ImLengthSqr(initial_off);
4168             if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
4169             {
4170                 // Interactive with Hue wheel
4171                 H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
4172                 if (H < 0.0f)
4173                     H += 1.0f;
4174                 value_changed = value_changed_h = true;
4175             }
4176             float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
4177             float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
4178             if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
4179             {
4180                 // Interacting with SV triangle
4181                 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
4182                 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
4183                     current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
4184                 float uu, vv, ww;
4185                 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
4186                 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
4187                 S = ImClamp(uu / V, 0.0001f, 1.0f);
4188                 value_changed = value_changed_sv = true;
4189             }
4190         }
4191         if (!(flags & ImGuiColorEditFlags_NoOptions))
4192             OpenPopupOnItemClick("context");
4193     }
4194     else if (flags & ImGuiColorEditFlags_PickerHueBar)
4195     {
4196         // SV rectangle logic
4197         InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
4198         if (IsItemActive())
4199         {
4200             S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
4201             V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4202             value_changed = value_changed_sv = true;
4203         }
4204         if (!(flags & ImGuiColorEditFlags_NoOptions))
4205             OpenPopupOnItemClick("context");
4206 
4207         // Hue bar logic
4208         SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
4209         InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
4210         if (IsItemActive())
4211         {
4212             H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4213             value_changed = value_changed_h = true;
4214         }
4215     }
4216 
4217     // Alpha bar logic
4218     if (alpha_bar)
4219     {
4220         SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
4221         InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
4222         if (IsItemActive())
4223         {
4224             col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4225             value_changed = true;
4226         }
4227     }
4228     PopItemFlag(); // ImGuiItemFlags_NoNav
4229 
4230     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4231     {
4232         SameLine(0, style.ItemInnerSpacing.x);
4233         BeginGroup();
4234     }
4235 
4236     if (!(flags & ImGuiColorEditFlags_NoLabel))
4237     {
4238         const char* label_display_end = FindRenderedTextEnd(label);
4239         if (label != label_display_end)
4240         {
4241             if ((flags & ImGuiColorEditFlags_NoSidePreview))
4242                 SameLine(0, style.ItemInnerSpacing.x);
4243             TextUnformatted(label, label_display_end);
4244         }
4245     }
4246 
4247     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4248     {
4249         PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
4250         ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
4251         if ((flags & ImGuiColorEditFlags_NoLabel))
4252             Text("Current");
4253         ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
4254         if (ref_col != NULL)
4255         {
4256             Text("Original");
4257             ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
4258             if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
4259             {
4260                 memcpy(col, ref_col, components * sizeof(float));
4261                 value_changed = true;
4262             }
4263         }
4264         PopItemFlag();
4265         EndGroup();
4266     }
4267 
4268     // Convert back color to RGB
4269     if (value_changed_h || value_changed_sv)
4270         ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
4271 
4272     // R,G,B and H,S,V slider color editor
4273     bool value_changed_fix_hue_wrap = false;
4274     if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
4275     {
4276         PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
4277         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
4278         ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
4279         if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4280             if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))
4281             {
4282                 // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
4283                 // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
4284                 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
4285                 value_changed = true;
4286             }
4287         if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4288             value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
4289         if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4290             value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
4291         PopItemWidth();
4292     }
4293 
4294     // Try to cancel hue wrap (after ColorEdit4 call), if any
4295     if (value_changed_fix_hue_wrap)
4296     {
4297         float new_H, new_S, new_V;
4298         ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
4299         if (new_H <= 0 && H > 0)
4300         {
4301             if (new_V <= 0 && V != new_V)
4302                 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
4303             else if (new_S <= 0)
4304                 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
4305         }
4306     }
4307 
4308     ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
4309     ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
4310     ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
4311 
4312     const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
4313     ImVec2 sv_cursor_pos;
4314 
4315     if (flags & ImGuiColorEditFlags_PickerHueWheel)
4316     {
4317         // Render Hue Wheel
4318         const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
4319         const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
4320         for (int n = 0; n < 6; n++)
4321         {
4322             const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
4323             const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
4324             const int vert_start_idx = draw_list->VtxBuffer.Size;
4325             draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
4326             draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
4327             const int vert_end_idx = draw_list->VtxBuffer.Size;
4328 
4329             // Paint colors over existing vertices
4330             ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
4331             ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
4332             ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
4333         }
4334 
4335         // Render Cursor + preview on Hue Wheel
4336         float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
4337         float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
4338         ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
4339         float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
4340         int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
4341         draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
4342         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
4343         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
4344 
4345         // Render SV triangle (rotated according to hue)
4346         ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
4347         ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
4348         ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
4349         ImVec2 uv_white = GetFontTexUvWhitePixel();
4350         draw_list->PrimReserve(6, 6);
4351         draw_list->PrimVtx(tra, uv_white, hue_color32);
4352         draw_list->PrimVtx(trb, uv_white, hue_color32);
4353         draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
4354         draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
4355         draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
4356         draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
4357         draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
4358         sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
4359     }
4360     else if (flags & ImGuiColorEditFlags_PickerHueBar)
4361     {
4362         // Render SV Square
4363         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
4364         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
4365         RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
4366         sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S)     * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
4367         sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
4368 
4369         // Render Hue Bar
4370         for (int i = 0; i < 6; ++i)
4371             draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
4372         float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
4373         RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
4374         RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
4375     }
4376 
4377     // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
4378     float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
4379     draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
4380     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
4381     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
4382 
4383     // Render alpha bar
4384     if (alpha_bar)
4385     {
4386         float alpha = ImSaturate(col[3]);
4387         ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
4388         RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
4389         draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
4390         float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
4391         RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
4392         RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
4393     }
4394 
4395     EndGroup();
4396 
4397     if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
4398         value_changed = false;
4399     if (value_changed)
4400         MarkItemEdited(window->DC.LastItemId);
4401 
4402     PopID();
4403 
4404     return value_changed;
4405 }
4406 
4407 // A little colored square. Return true when clicked.
4408 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
4409 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
ColorButton(const char * desc_id,const ImVec4 & col,ImGuiColorEditFlags flags,ImVec2 size)4410 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
4411 {
4412     ImGuiWindow* window = GetCurrentWindow();
4413     if (window->SkipItems)
4414         return false;
4415 
4416     ImGuiContext& g = *GImGui;
4417     const ImGuiID id = window->GetID(desc_id);
4418     float default_size = GetFrameHeight();
4419     if (size.x == 0.0f)
4420         size.x = default_size;
4421     if (size.y == 0.0f)
4422         size.y = default_size;
4423     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
4424     ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
4425     if (!ItemAdd(bb, id))
4426         return false;
4427 
4428     bool hovered, held;
4429     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
4430 
4431     if (flags & ImGuiColorEditFlags_NoAlpha)
4432         flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
4433 
4434     ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
4435     float grid_step = ImMin(size.x, size.y) / 2.99f;
4436     float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
4437     ImRect bb_inner = bb;
4438     float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
4439     bb_inner.Expand(off);
4440     if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
4441     {
4442         float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
4443         RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
4444         window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
4445     }
4446     else
4447     {
4448         // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
4449         ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
4450         if (col_source.w < 1.0f)
4451             RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
4452         else
4453             window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
4454     }
4455     RenderNavHighlight(bb, id);
4456     if (g.Style.FrameBorderSize > 0.0f)
4457         RenderFrameBorder(bb.Min, bb.Max, rounding);
4458     else
4459         window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
4460 
4461     // Drag and Drop Source
4462     // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
4463     if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
4464     {
4465         if (flags & ImGuiColorEditFlags_NoAlpha)
4466             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
4467         else
4468             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
4469         ColorButton(desc_id, col, flags);
4470         SameLine();
4471         TextUnformatted("Color");
4472         EndDragDropSource();
4473     }
4474 
4475     // Tooltip
4476     if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
4477         ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
4478 
4479     if (pressed)
4480         MarkItemEdited(id);
4481 
4482     return pressed;
4483 }
4484 
SetColorEditOptions(ImGuiColorEditFlags flags)4485 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
4486 {
4487     ImGuiContext& g = *GImGui;
4488     if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
4489         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
4490     if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
4491         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
4492     if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
4493         flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
4494     IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask)));   // Check only 1 option is selected
4495     IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
4496     IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask)));   // Check only 1 option is selected
4497     g.ColorEditOptions = flags;
4498 }
4499 
4500 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
ColorTooltip(const char * text,const float * col,ImGuiColorEditFlags flags)4501 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
4502 {
4503     ImGuiContext& g = *GImGui;
4504 
4505     int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
4506     BeginTooltipEx(0, true);
4507 
4508     const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
4509     if (text_end > text)
4510     {
4511         TextUnformatted(text, text_end);
4512         Separator();
4513     }
4514 
4515     ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
4516     ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
4517     SameLine();
4518     if (flags & ImGuiColorEditFlags_NoAlpha)
4519         Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
4520     else
4521         Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
4522     EndTooltip();
4523 }
4524 
ColorEditOptionsPopup(const float * col,ImGuiColorEditFlags flags)4525 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
4526 {
4527     bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
4528     bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
4529     if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
4530         return;
4531     ImGuiContext& g = *GImGui;
4532     ImGuiColorEditFlags opts = g.ColorEditOptions;
4533     if (allow_opt_inputs)
4534     {
4535         if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
4536         if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
4537         if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
4538     }
4539     if (allow_opt_datatype)
4540     {
4541         if (allow_opt_inputs) Separator();
4542         if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
4543         if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
4544     }
4545 
4546     if (allow_opt_inputs || allow_opt_datatype)
4547         Separator();
4548     if (Button("Copy as..", ImVec2(-1,0)))
4549         OpenPopup("Copy");
4550     if (BeginPopup("Copy"))
4551     {
4552         int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
4553         char buf[64];
4554         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
4555         if (Selectable(buf))
4556             SetClipboardText(buf);
4557         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
4558         if (Selectable(buf))
4559             SetClipboardText(buf);
4560         if (flags & ImGuiColorEditFlags_NoAlpha)
4561             ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
4562         else
4563             ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
4564         if (Selectable(buf))
4565             SetClipboardText(buf);
4566         EndPopup();
4567     }
4568 
4569     g.ColorEditOptions = opts;
4570     EndPopup();
4571 }
4572 
ColorPickerOptionsPopup(const float * ref_col,ImGuiColorEditFlags flags)4573 void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
4574 {
4575     bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
4576     bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
4577     if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
4578         return;
4579     ImGuiContext& g = *GImGui;
4580     if (allow_opt_picker)
4581     {
4582         ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
4583         PushItemWidth(picker_size.x);
4584         for (int picker_type = 0; picker_type < 2; picker_type++)
4585         {
4586             // Draw small/thumbnail version of each picker type (over an invisible button for selection)
4587             if (picker_type > 0) Separator();
4588             PushID(picker_type);
4589             ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
4590             if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
4591             if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
4592             ImVec2 backup_pos = GetCursorScreenPos();
4593             if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
4594                 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
4595             SetCursorScreenPos(backup_pos);
4596             ImVec4 dummy_ref_col;
4597             memcpy(&dummy_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
4598             ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
4599             PopID();
4600         }
4601         PopItemWidth();
4602     }
4603     if (allow_opt_alpha_bar)
4604     {
4605         if (allow_opt_picker) Separator();
4606         CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
4607     }
4608     EndPopup();
4609 }
4610 
4611 //-------------------------------------------------------------------------
4612 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
4613 //-------------------------------------------------------------------------
4614 // - TreeNode()
4615 // - TreeNodeV()
4616 // - TreeNodeEx()
4617 // - TreeNodeExV()
4618 // - TreeNodeBehavior() [Internal]
4619 // - TreePush()
4620 // - TreePop()
4621 // - TreeAdvanceToLabelPos()
4622 // - GetTreeNodeToLabelSpacing()
4623 // - SetNextTreeNodeOpen()
4624 // - CollapsingHeader()
4625 //-------------------------------------------------------------------------
4626 
TreeNode(const char * str_id,const char * fmt,...)4627 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
4628 {
4629     va_list args;
4630     va_start(args, fmt);
4631     bool is_open = TreeNodeExV(str_id, 0, fmt, args);
4632     va_end(args);
4633     return is_open;
4634 }
4635 
TreeNode(const void * ptr_id,const char * fmt,...)4636 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
4637 {
4638     va_list args;
4639     va_start(args, fmt);
4640     bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
4641     va_end(args);
4642     return is_open;
4643 }
4644 
TreeNode(const char * label)4645 bool ImGui::TreeNode(const char* label)
4646 {
4647     ImGuiWindow* window = GetCurrentWindow();
4648     if (window->SkipItems)
4649         return false;
4650     return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
4651 }
4652 
TreeNodeV(const char * str_id,const char * fmt,va_list args)4653 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
4654 {
4655     return TreeNodeExV(str_id, 0, fmt, args);
4656 }
4657 
TreeNodeV(const void * ptr_id,const char * fmt,va_list args)4658 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
4659 {
4660     return TreeNodeExV(ptr_id, 0, fmt, args);
4661 }
4662 
TreeNodeEx(const char * label,ImGuiTreeNodeFlags flags)4663 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
4664 {
4665     ImGuiWindow* window = GetCurrentWindow();
4666     if (window->SkipItems)
4667         return false;
4668 
4669     return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
4670 }
4671 
TreeNodeEx(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,...)4672 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
4673 {
4674     va_list args;
4675     va_start(args, fmt);
4676     bool is_open = TreeNodeExV(str_id, flags, fmt, args);
4677     va_end(args);
4678     return is_open;
4679 }
4680 
TreeNodeEx(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,...)4681 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
4682 {
4683     va_list args;
4684     va_start(args, fmt);
4685     bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
4686     va_end(args);
4687     return is_open;
4688 }
4689 
TreeNodeExV(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)4690 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
4691 {
4692     ImGuiWindow* window = GetCurrentWindow();
4693     if (window->SkipItems)
4694         return false;
4695 
4696     ImGuiContext& g = *GImGui;
4697     const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
4698     return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
4699 }
4700 
TreeNodeExV(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)4701 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
4702 {
4703     ImGuiWindow* window = GetCurrentWindow();
4704     if (window->SkipItems)
4705         return false;
4706 
4707     ImGuiContext& g = *GImGui;
4708     const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
4709     return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
4710 }
4711 
TreeNodeBehaviorIsOpen(ImGuiID id,ImGuiTreeNodeFlags flags)4712 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
4713 {
4714     if (flags & ImGuiTreeNodeFlags_Leaf)
4715         return true;
4716 
4717     // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
4718     ImGuiContext& g = *GImGui;
4719     ImGuiWindow* window = g.CurrentWindow;
4720     ImGuiStorage* storage = window->DC.StateStorage;
4721 
4722     bool is_open;
4723     if (g.NextTreeNodeOpenCond != 0)
4724     {
4725         if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
4726         {
4727             is_open = g.NextTreeNodeOpenVal;
4728             storage->SetInt(id, is_open);
4729         }
4730         else
4731         {
4732             // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
4733             const int stored_value = storage->GetInt(id, -1);
4734             if (stored_value == -1)
4735             {
4736                 is_open = g.NextTreeNodeOpenVal;
4737                 storage->SetInt(id, is_open);
4738             }
4739             else
4740             {
4741                 is_open = stored_value != 0;
4742             }
4743         }
4744         g.NextTreeNodeOpenCond = 0;
4745     }
4746     else
4747     {
4748         is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
4749     }
4750 
4751     // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
4752     // NB- If we are above max depth we still allow manually opened nodes to be logged.
4753     if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
4754         is_open = true;
4755 
4756     return is_open;
4757 }
4758 
TreeNodeBehavior(ImGuiID id,ImGuiTreeNodeFlags flags,const char * label,const char * label_end)4759 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
4760 {
4761     ImGuiWindow* window = GetCurrentWindow();
4762     if (window->SkipItems)
4763         return false;
4764 
4765     ImGuiContext& g = *GImGui;
4766     const ImGuiStyle& style = g.Style;
4767     const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
4768     const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
4769 
4770     if (!label_end)
4771         label_end = FindRenderedTextEnd(label);
4772     const ImVec2 label_size = CalcTextSize(label, label_end, false);
4773 
4774     // We vertically grow up to current line height up the typical widget height.
4775     const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
4776     const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
4777     ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
4778     if (display_frame)
4779     {
4780         // Framed header expand a little outside the default padding
4781         frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
4782         frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
4783     }
4784 
4785     const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2));   // Collapser arrow width + Spacing
4786     const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f);   // Include collapser
4787     ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
4788 
4789     // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
4790     // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
4791     const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
4792     bool is_open = TreeNodeBehaviorIsOpen(id, flags);
4793     bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
4794 
4795     // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
4796     // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
4797     // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
4798     if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4799         window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
4800 
4801     bool item_add = ItemAdd(interact_bb, id);
4802     window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
4803     window->DC.LastItemDisplayRect = frame_bb;
4804 
4805     if (!item_add)
4806     {
4807         if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4808             TreePushRawID(id);
4809         IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
4810         return is_open;
4811     }
4812 
4813     // Flags that affects opening behavior:
4814     // - 0 (default) .................... single-click anywhere to open
4815     // - OpenOnDoubleClick .............. double-click anywhere to open
4816     // - OpenOnArrow .................... single-click on arrow to open
4817     // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
4818     ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers;
4819     if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
4820         button_flags |= ImGuiButtonFlags_AllowItemOverlap;
4821     if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
4822         button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
4823     if (!is_leaf)
4824         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
4825 
4826     bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
4827     bool hovered, held;
4828     bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
4829     bool toggled = false;
4830     if (!is_leaf)
4831     {
4832         if (pressed)
4833         {
4834             toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
4835             if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
4836                 toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
4837             if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
4838                 toggled |= g.IO.MouseDoubleClicked[0];
4839             if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
4840                 toggled = false;
4841         }
4842 
4843         if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
4844         {
4845             toggled = true;
4846             NavMoveRequestCancel();
4847         }
4848         if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
4849         {
4850             toggled = true;
4851             NavMoveRequestCancel();
4852         }
4853 
4854         if (toggled)
4855         {
4856             is_open = !is_open;
4857             window->DC.StateStorage->SetInt(id, is_open);
4858         }
4859     }
4860     if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
4861         SetItemAllowOverlap();
4862 
4863     // Render
4864     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
4865     const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
4866     ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
4867     if (display_frame)
4868     {
4869         // Framed type
4870         RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
4871         RenderNavHighlight(frame_bb, id, nav_highlight_flags);
4872         RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
4873         if (g.LogEnabled)
4874         {
4875             // 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.
4876             const char log_prefix[] = "\n##";
4877             const char log_suffix[] = "##";
4878             LogRenderedText(&text_pos, log_prefix, log_prefix+3);
4879             RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
4880             LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
4881         }
4882         else
4883         {
4884             RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
4885         }
4886     }
4887     else
4888     {
4889         // Unframed typed for tree nodes
4890         if (hovered || selected)
4891         {
4892             RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
4893             RenderNavHighlight(frame_bb, id, nav_highlight_flags);
4894         }
4895 
4896         if (flags & ImGuiTreeNodeFlags_Bullet)
4897             RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
4898         else if (!is_leaf)
4899             RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
4900         if (g.LogEnabled)
4901             LogRenderedText(&text_pos, ">");
4902         RenderText(text_pos, label, label_end, false);
4903     }
4904 
4905     if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4906         TreePushRawID(id);
4907     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
4908     return is_open;
4909 }
4910 
TreePush(const char * str_id)4911 void ImGui::TreePush(const char* str_id)
4912 {
4913     ImGuiWindow* window = GetCurrentWindow();
4914     Indent();
4915     window->DC.TreeDepth++;
4916     PushID(str_id ? str_id : "#TreePush");
4917 }
4918 
TreePush(const void * ptr_id)4919 void ImGui::TreePush(const void* ptr_id)
4920 {
4921     ImGuiWindow* window = GetCurrentWindow();
4922     Indent();
4923     window->DC.TreeDepth++;
4924     PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
4925 }
4926 
TreePushRawID(ImGuiID id)4927 void ImGui::TreePushRawID(ImGuiID id)
4928 {
4929     ImGuiWindow* window = GetCurrentWindow();
4930     Indent();
4931     window->DC.TreeDepth++;
4932     window->IDStack.push_back(id);
4933 }
4934 
TreePop()4935 void ImGui::TreePop()
4936 {
4937     ImGuiContext& g = *GImGui;
4938     ImGuiWindow* window = g.CurrentWindow;
4939     Unindent();
4940 
4941     window->DC.TreeDepth--;
4942     if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
4943         if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
4944         {
4945             SetNavID(window->IDStack.back(), g.NavLayer);
4946             NavMoveRequestCancel();
4947         }
4948     window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
4949 
4950     IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
4951     PopID();
4952 }
4953 
TreeAdvanceToLabelPos()4954 void ImGui::TreeAdvanceToLabelPos()
4955 {
4956     ImGuiContext& g = *GImGui;
4957     g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
4958 }
4959 
4960 // Horizontal distance preceding label when using TreeNode() or Bullet()
GetTreeNodeToLabelSpacing()4961 float ImGui::GetTreeNodeToLabelSpacing()
4962 {
4963     ImGuiContext& g = *GImGui;
4964     return g.FontSize + (g.Style.FramePadding.x * 2.0f);
4965 }
4966 
SetNextTreeNodeOpen(bool is_open,ImGuiCond cond)4967 void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
4968 {
4969     ImGuiContext& g = *GImGui;
4970     if (g.CurrentWindow->SkipItems)
4971         return;
4972     g.NextTreeNodeOpenVal = is_open;
4973     g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
4974 }
4975 
4976 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
4977 // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
CollapsingHeader(const char * label,ImGuiTreeNodeFlags flags)4978 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
4979 {
4980     ImGuiWindow* window = GetCurrentWindow();
4981     if (window->SkipItems)
4982         return false;
4983 
4984     return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
4985 }
4986 
CollapsingHeader(const char * label,bool * p_open,ImGuiTreeNodeFlags flags)4987 bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
4988 {
4989     ImGuiWindow* window = GetCurrentWindow();
4990     if (window->SkipItems)
4991         return false;
4992 
4993     if (p_open && !*p_open)
4994         return false;
4995 
4996     ImGuiID id = window->GetID(label);
4997     bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
4998     if (p_open)
4999     {
5000         // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
5001         ImGuiContext& g = *GImGui;
5002         ImGuiItemHoveredDataBackup last_item_backup;
5003         float button_radius = g.FontSize * 0.5f;
5004         ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
5005         if (CloseButton(window->GetID((void*)((intptr_t)id+1)), button_center, button_radius))
5006             *p_open = false;
5007         last_item_backup.Restore();
5008     }
5009 
5010     return is_open;
5011 }
5012 
5013 //-------------------------------------------------------------------------
5014 // [SECTION] Widgets: Selectable
5015 //-------------------------------------------------------------------------
5016 // - Selectable()
5017 //-------------------------------------------------------------------------
5018 
5019 // Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.
5020 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
Selectable(const char * label,bool selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)5021 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
5022 {
5023     ImGuiWindow* window = GetCurrentWindow();
5024     if (window->SkipItems)
5025         return false;
5026 
5027     ImGuiContext& g = *GImGui;
5028     const ImGuiStyle& style = g.Style;
5029 
5030     if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
5031         PopClipRect();
5032 
5033     ImGuiID id = window->GetID(label);
5034     ImVec2 label_size = CalcTextSize(label, NULL, true);
5035     ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
5036     ImVec2 pos = window->DC.CursorPos;
5037     pos.y += window->DC.CurrentLineTextBaseOffset;
5038     ImRect bb_inner(pos, pos + size);
5039     ItemSize(bb_inner);
5040 
5041     // Fill horizontal space.
5042     ImVec2 window_padding = window->WindowPadding;
5043     float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
5044     float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);
5045     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);
5046     ImRect bb(pos, pos + size_draw);
5047     if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
5048         bb.Max.x += window_padding.x;
5049 
5050     // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
5051     float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
5052     float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
5053     float spacing_R = style.ItemSpacing.x - spacing_L;
5054     float spacing_D = style.ItemSpacing.y - spacing_U;
5055     bb.Min.x -= spacing_L;
5056     bb.Min.y -= spacing_U;
5057     bb.Max.x += spacing_R;
5058     bb.Max.y += spacing_D;
5059     if (!ItemAdd(bb, id))
5060     {
5061         if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
5062             PushColumnClipRect();
5063         return false;
5064     }
5065 
5066     // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
5067     ImGuiButtonFlags button_flags = 0;
5068     if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
5069     if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
5070     if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
5071     if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
5072     if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
5073     if (flags & ImGuiSelectableFlags_Disabled)
5074         selected = false;
5075 
5076     bool hovered, held;
5077     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
5078     // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
5079     if (pressed || hovered)
5080         if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
5081         {
5082             g.NavDisableHighlight = true;
5083             SetNavID(id, window->DC.NavLayerCurrent);
5084         }
5085     if (pressed)
5086         MarkItemEdited(id);
5087 
5088     // Render
5089     if (hovered || selected)
5090     {
5091         const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5092         RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
5093         RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
5094     }
5095 
5096     if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
5097     {
5098         PushColumnClipRect();
5099         bb.Max.x -= (GetContentRegionMax().x - max_x);
5100     }
5101 
5102     if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
5103     RenderTextClipped(bb_inner.Min, bb_inner.Max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
5104     if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
5105 
5106     // Automatically close popups
5107     if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
5108         CloseCurrentPopup();
5109     return pressed;
5110 }
5111 
Selectable(const char * label,bool * p_selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)5112 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
5113 {
5114     if (Selectable(label, *p_selected, flags, size_arg))
5115     {
5116         *p_selected = !*p_selected;
5117         return true;
5118     }
5119     return false;
5120 }
5121 
5122 //-------------------------------------------------------------------------
5123 // [SECTION] Widgets: ListBox
5124 //-------------------------------------------------------------------------
5125 // - ListBox()
5126 // - ListBoxHeader()
5127 // - ListBoxFooter()
5128 //-------------------------------------------------------------------------
5129 
5130 // FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5131 // Helper to calculate the size of a listbox and display a label on the right.
5132 // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
ListBoxHeader(const char * label,const ImVec2 & size_arg)5133 bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
5134 {
5135     ImGuiWindow* window = GetCurrentWindow();
5136     if (window->SkipItems)
5137         return false;
5138 
5139     const ImGuiStyle& style = GetStyle();
5140     const ImGuiID id = GetID(label);
5141     const ImVec2 label_size = CalcTextSize(label, NULL, true);
5142 
5143     // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
5144     ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
5145     ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
5146     ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
5147     ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
5148     window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
5149 
5150     if (!IsRectVisible(bb.Min, bb.Max))
5151     {
5152         ItemSize(bb.GetSize(), style.FramePadding.y);
5153         ItemAdd(bb, 0, &frame_bb);
5154         return false;
5155     }
5156 
5157     BeginGroup();
5158     if (label_size.x > 0)
5159         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
5160 
5161     BeginChildFrame(id, frame_bb.GetSize());
5162     return true;
5163 }
5164 
5165 // FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
ListBoxHeader(const char * label,int items_count,int height_in_items)5166 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
5167 {
5168     // Size default to hold ~7.25 items.
5169     // We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar.
5170     // We don't add this extra bit 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.
5171     // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
5172     if (height_in_items < 0)
5173         height_in_items = ImMin(items_count, 7);
5174     const ImGuiStyle& style = GetStyle();
5175     float height_in_items_f = (height_in_items < items_count) ? (height_in_items + 0.25f) : (height_in_items + 0.00f);
5176 
5177     // 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().
5178     ImVec2 size;
5179     size.x = 0.0f;
5180     size.y = GetTextLineHeightWithSpacing() * height_in_items_f + style.FramePadding.y * 2.0f;
5181     return ListBoxHeader(label, size);
5182 }
5183 
5184 // FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
ListBoxFooter()5185 void ImGui::ListBoxFooter()
5186 {
5187     ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
5188     const ImRect bb = parent_window->DC.LastItemRect;
5189     const ImGuiStyle& style = GetStyle();
5190 
5191     EndChildFrame();
5192 
5193     // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
5194     // We call SameLine() to restore DC.CurrentLine* data
5195     SameLine();
5196     parent_window->DC.CursorPos = bb.Min;
5197     ItemSize(bb, style.FramePadding.y);
5198     EndGroup();
5199 }
5200 
ListBox(const char * label,int * current_item,const char * const items[],int items_count,int height_items)5201 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
5202 {
5203     const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
5204     return value_changed;
5205 }
5206 
ListBox(const char * label,int * current_item,bool (* items_getter)(void *,int,const char **),void * data,int items_count,int height_in_items)5207 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)
5208 {
5209     if (!ListBoxHeader(label, items_count, height_in_items))
5210         return false;
5211 
5212     // 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.
5213     ImGuiContext& g = *GImGui;
5214     bool value_changed = false;
5215     ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
5216     while (clipper.Step())
5217         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
5218         {
5219             const bool item_selected = (i == *current_item);
5220             const char* item_text;
5221             if (!items_getter(data, i, &item_text))
5222                 item_text = "*Unknown item*";
5223 
5224             PushID(i);
5225             if (Selectable(item_text, item_selected))
5226             {
5227                 *current_item = i;
5228                 value_changed = true;
5229             }
5230             if (item_selected)
5231                 SetItemDefaultFocus();
5232             PopID();
5233         }
5234     ListBoxFooter();
5235     if (value_changed)
5236         MarkItemEdited(g.CurrentWindow->DC.LastItemId);
5237 
5238     return value_changed;
5239 }
5240 
5241 //-------------------------------------------------------------------------
5242 // [SECTION] Widgets: PlotLines, PlotHistogram
5243 //-------------------------------------------------------------------------
5244 // - PlotEx() [Internal]
5245 // - PlotLines()
5246 // - PlotHistogram()
5247 //-------------------------------------------------------------------------
5248 
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 frame_size)5249 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 frame_size)
5250 {
5251     ImGuiWindow* window = GetCurrentWindow();
5252     if (window->SkipItems)
5253         return;
5254 
5255     ImGuiContext& g = *GImGui;
5256     const ImGuiStyle& style = g.Style;
5257     const ImGuiID id = window->GetID(label);
5258 
5259     const ImVec2 label_size = CalcTextSize(label, NULL, true);
5260     if (frame_size.x == 0.0f)
5261         frame_size.x = CalcItemWidth();
5262     if (frame_size.y == 0.0f)
5263         frame_size.y = label_size.y + (style.FramePadding.y * 2);
5264 
5265     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
5266     const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
5267     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));
5268     ItemSize(total_bb, style.FramePadding.y);
5269     if (!ItemAdd(total_bb, 0, &frame_bb))
5270         return;
5271     const bool hovered = ItemHoverable(frame_bb, id);
5272 
5273     // Determine scale from values if not specified
5274     if (scale_min == FLT_MAX || scale_max == FLT_MAX)
5275     {
5276         float v_min = FLT_MAX;
5277         float v_max = -FLT_MAX;
5278         for (int i = 0; i < values_count; i++)
5279         {
5280             const float v = values_getter(data, i);
5281             v_min = ImMin(v_min, v);
5282             v_max = ImMax(v_max, v);
5283         }
5284         if (scale_min == FLT_MAX)
5285             scale_min = v_min;
5286         if (scale_max == FLT_MAX)
5287             scale_max = v_max;
5288     }
5289 
5290     RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
5291 
5292     if (values_count > 0)
5293     {
5294         int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
5295         int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
5296 
5297         // Tooltip on hover
5298         int v_hovered = -1;
5299         if (hovered && inner_bb.Contains(g.IO.MousePos))
5300         {
5301             const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
5302             const int v_idx = (int)(t * item_count);
5303             IM_ASSERT(v_idx >= 0 && v_idx < values_count);
5304 
5305             const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
5306             const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
5307             if (plot_type == ImGuiPlotType_Lines)
5308                 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
5309             else if (plot_type == ImGuiPlotType_Histogram)
5310                 SetTooltip("%d: %8.4g", v_idx, v0);
5311             v_hovered = v_idx;
5312         }
5313 
5314         const float t_step = 1.0f / (float)res_w;
5315         const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
5316 
5317         float v0 = values_getter(data, (0 + values_offset) % values_count);
5318         float t0 = 0.0f;
5319         ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
5320         float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands
5321 
5322         const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
5323         const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
5324 
5325         for (int n = 0; n < res_w; n++)
5326         {
5327             const float t1 = t0 + t_step;
5328             const int v1_idx = (int)(t0 * item_count + 0.5f);
5329             IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
5330             const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
5331             const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
5332 
5333             // 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.
5334             ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
5335             ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
5336             if (plot_type == ImGuiPlotType_Lines)
5337             {
5338                 window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
5339             }
5340             else if (plot_type == ImGuiPlotType_Histogram)
5341             {
5342                 if (pos1.x >= pos0.x + 2.0f)
5343                     pos1.x -= 1.0f;
5344                 window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
5345             }
5346 
5347             t0 = t1;
5348             tp0 = tp1;
5349         }
5350     }
5351 
5352     // Text overlay
5353     if (overlay_text)
5354         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));
5355 
5356     if (label_size.x > 0.0f)
5357         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
5358 }
5359 
5360 struct ImGuiPlotArrayGetterData
5361 {
5362     const float* Values;
5363     int Stride;
5364 
ImGuiPlotArrayGetterDataImGuiPlotArrayGetterData5365     ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
5366 };
5367 
Plot_ArrayGetter(void * data,int idx)5368 static float Plot_ArrayGetter(void* data, int idx)
5369 {
5370     ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
5371     const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
5372     return v;
5373 }
5374 
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)5375 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)
5376 {
5377     ImGuiPlotArrayGetterData data(values, stride);
5378     PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5379 }
5380 
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)5381 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)
5382 {
5383     PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5384 }
5385 
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)5386 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)
5387 {
5388     ImGuiPlotArrayGetterData data(values, stride);
5389     PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5390 }
5391 
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)5392 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)
5393 {
5394     PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5395 }
5396 
5397 //-------------------------------------------------------------------------
5398 // [SECTION] Widgets: Value helpers
5399 // Those is not very useful, legacy API.
5400 //-------------------------------------------------------------------------
5401 // - Value()
5402 //-------------------------------------------------------------------------
5403 
Value(const char * prefix,bool b)5404 void ImGui::Value(const char* prefix, bool b)
5405 {
5406     Text("%s: %s", prefix, (b ? "true" : "false"));
5407 }
5408 
Value(const char * prefix,int v)5409 void ImGui::Value(const char* prefix, int v)
5410 {
5411     Text("%s: %d", prefix, v);
5412 }
5413 
Value(const char * prefix,unsigned int v)5414 void ImGui::Value(const char* prefix, unsigned int v)
5415 {
5416     Text("%s: %d", prefix, v);
5417 }
5418 
Value(const char * prefix,float v,const char * float_format)5419 void ImGui::Value(const char* prefix, float v, const char* float_format)
5420 {
5421     if (float_format)
5422     {
5423         char fmt[64];
5424         ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
5425         Text(fmt, prefix, v);
5426     }
5427     else
5428     {
5429         Text("%s: %.3f", prefix, v);
5430     }
5431 }
5432 
5433 //-------------------------------------------------------------------------
5434 // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
5435 //-------------------------------------------------------------------------
5436 // - ImGuiMenuColumns [Internal]
5437 // - BeginMainMenuBar()
5438 // - EndMainMenuBar()
5439 // - BeginMenuBar()
5440 // - EndMenuBar()
5441 // - BeginMenu()
5442 // - EndMenu()
5443 // - MenuItem()
5444 //-------------------------------------------------------------------------
5445 
5446 // Helpers for internal use
ImGuiMenuColumns()5447 ImGuiMenuColumns::ImGuiMenuColumns()
5448 {
5449     Count = 0;
5450     Spacing = Width = NextWidth = 0.0f;
5451     memset(Pos, 0, sizeof(Pos));
5452     memset(NextWidths, 0, sizeof(NextWidths));
5453 }
5454 
Update(int count,float spacing,bool clear)5455 void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
5456 {
5457     IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
5458     Count = count;
5459     Width = NextWidth = 0.0f;
5460     Spacing = spacing;
5461     if (clear) memset(NextWidths, 0, sizeof(NextWidths));
5462     for (int i = 0; i < Count; i++)
5463     {
5464         if (i > 0 && NextWidths[i] > 0.0f)
5465             Width += Spacing;
5466         Pos[i] = (float)(int)Width;
5467         Width += NextWidths[i];
5468         NextWidths[i] = 0.0f;
5469     }
5470 }
5471 
DeclColumns(float w0,float w1,float w2)5472 float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
5473 {
5474     NextWidth = 0.0f;
5475     NextWidths[0] = ImMax(NextWidths[0], w0);
5476     NextWidths[1] = ImMax(NextWidths[1], w1);
5477     NextWidths[2] = ImMax(NextWidths[2], w2);
5478     for (int i = 0; i < 3; i++)
5479         NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
5480     return ImMax(Width, NextWidth);
5481 }
5482 
CalcExtraSpace(float avail_w)5483 float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
5484 {
5485     return ImMax(0.0f, avail_w - Width);
5486 }
5487 
5488 // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
BeginMainMenuBar()5489 bool ImGui::BeginMainMenuBar()
5490 {
5491     ImGuiContext& g = *GImGui;
5492     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
5493     SetNextWindowPos(ImVec2(0.0f, 0.0f));
5494     SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
5495     PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
5496     PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
5497     ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
5498     bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
5499     PopStyleVar(2);
5500     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
5501     if (!is_open)
5502     {
5503         End();
5504         return false;
5505     }
5506     return true; //-V1020
5507 }
5508 
EndMainMenuBar()5509 void ImGui::EndMainMenuBar()
5510 {
5511     EndMenuBar();
5512 
5513     // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
5514     ImGuiContext& g = *GImGui;
5515     if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
5516         FocusPreviousWindowIgnoringOne(g.NavWindow);
5517 
5518     End();
5519 }
5520 
BeginMenuBar()5521 bool ImGui::BeginMenuBar()
5522 {
5523     ImGuiWindow* window = GetCurrentWindow();
5524     if (window->SkipItems)
5525         return false;
5526     if (!(window->Flags & ImGuiWindowFlags_MenuBar))
5527         return false;
5528 
5529     IM_ASSERT(!window->DC.MenuBarAppending);
5530     BeginGroup(); // Backup position on layer 0
5531     PushID("##menubar");
5532 
5533     // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
5534     // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
5535     ImRect bar_rect = window->MenuBarRect();
5536     ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
5537     clip_rect.ClipWith(window->OuterRectClipped);
5538     PushClipRect(clip_rect.Min, clip_rect.Max, false);
5539 
5540     window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
5541     window->DC.LayoutType = ImGuiLayoutType_Horizontal;
5542     window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
5543     window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
5544     window->DC.MenuBarAppending = true;
5545     AlignTextToFramePadding();
5546     return true;
5547 }
5548 
EndMenuBar()5549 void ImGui::EndMenuBar()
5550 {
5551     ImGuiWindow* window = GetCurrentWindow();
5552     if (window->SkipItems)
5553         return;
5554     ImGuiContext& g = *GImGui;
5555 
5556     // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
5557     if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
5558     {
5559         ImGuiWindow* nav_earliest_child = g.NavWindow;
5560         while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
5561             nav_earliest_child = nav_earliest_child->ParentWindow;
5562         if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
5563         {
5564             // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
5565             // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
5566             IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
5567             FocusWindow(window);
5568             SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
5569             g.NavLayer = ImGuiNavLayer_Menu;
5570             g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
5571             g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
5572             NavMoveRequestCancel();
5573         }
5574     }
5575 
5576     IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
5577     IM_ASSERT(window->DC.MenuBarAppending);
5578     PopClipRect();
5579     PopID();
5580     window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
5581     window->DC.GroupStack.back().AdvanceCursor = false;
5582     EndGroup(); // Restore position on layer 0
5583     window->DC.LayoutType = ImGuiLayoutType_Vertical;
5584     window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
5585     window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
5586     window->DC.MenuBarAppending = false;
5587 }
5588 
BeginMenu(const char * label,bool enabled)5589 bool ImGui::BeginMenu(const char* label, bool enabled)
5590 {
5591     ImGuiWindow* window = GetCurrentWindow();
5592     if (window->SkipItems)
5593         return false;
5594 
5595     ImGuiContext& g = *GImGui;
5596     const ImGuiStyle& style = g.Style;
5597     const ImGuiID id = window->GetID(label);
5598 
5599     ImVec2 label_size = CalcTextSize(label, NULL, true);
5600 
5601     bool pressed;
5602     bool menu_is_open = IsPopupOpen(id);
5603     bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
5604     ImGuiWindow* backed_nav_window = g.NavWindow;
5605     if (menuset_is_open)
5606         g.NavWindow = window;  // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
5607 
5608     // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
5609     // However the final position is going to be different! It is choosen by FindBestWindowPosForPopup().
5610     // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
5611     ImVec2 popup_pos, pos = window->DC.CursorPos;
5612     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
5613     {
5614         // Menu inside an horizontal menu bar
5615         // Selectable extend their highlight by half ItemSpacing in each direction.
5616         // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
5617         popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
5618         window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
5619         PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
5620         float w = label_size.x;
5621         pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
5622         PopStyleVar();
5623         window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
5624     }
5625     else
5626     {
5627         // Menu inside a menu
5628         popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
5629         float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
5630         float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
5631         pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
5632         if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
5633         RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
5634         if (!enabled) PopStyleColor();
5635     }
5636 
5637     const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
5638     if (menuset_is_open)
5639         g.NavWindow = backed_nav_window;
5640 
5641     bool want_open = false, want_close = false;
5642     if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5643     {
5644         // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
5645         bool moving_within_opened_triangle = false;
5646         if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
5647         {
5648             if (ImGuiWindow* next_window = g.OpenPopupStack[g.BeginPopupStack.Size].Window)
5649             {
5650                 // FIXME-DPI: Values should be derived from a master "scale" factor.
5651                 ImRect next_window_rect = next_window->Rect();
5652                 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
5653                 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
5654                 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
5655                 float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
5656                 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f;    // to avoid numerical issues
5657                 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?
5658                 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
5659                 moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
5660                 //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
5661             }
5662         }
5663 
5664         want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
5665         want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
5666 
5667         if (g.NavActivateId == id)
5668         {
5669             want_close = menu_is_open;
5670             want_open = !menu_is_open;
5671         }
5672         if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
5673         {
5674             want_open = true;
5675             NavMoveRequestCancel();
5676         }
5677     }
5678     else
5679     {
5680         // Menu bar
5681         if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
5682         {
5683             want_close = true;
5684             want_open = menu_is_open = false;
5685         }
5686         else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
5687         {
5688             want_open = true;
5689         }
5690         else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
5691         {
5692             want_open = true;
5693             NavMoveRequestCancel();
5694         }
5695     }
5696 
5697     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.. }'
5698         want_close = true;
5699     if (want_close && IsPopupOpen(id))
5700         ClosePopupToLevel(g.BeginPopupStack.Size, true);
5701 
5702     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
5703 
5704     if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
5705     {
5706         // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
5707         OpenPopup(label);
5708         return false;
5709     }
5710 
5711     menu_is_open |= want_open;
5712     if (want_open)
5713         OpenPopup(label);
5714 
5715     if (menu_is_open)
5716     {
5717         // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
5718         SetNextWindowPos(popup_pos, ImGuiCond_Always);
5719         ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
5720         if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5721             flags |= ImGuiWindowFlags_ChildWindow;
5722         menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5723     }
5724 
5725     return menu_is_open;
5726 }
5727 
EndMenu()5728 void ImGui::EndMenu()
5729 {
5730     // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
5731     // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
5732     // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
5733     ImGuiContext& g = *GImGui;
5734     ImGuiWindow* window = g.CurrentWindow;
5735     if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
5736     {
5737         ClosePopupToLevel(g.BeginPopupStack.Size, true);
5738         NavMoveRequestCancel();
5739     }
5740 
5741     EndPopup();
5742 }
5743 
MenuItem(const char * label,const char * shortcut,bool selected,bool enabled)5744 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
5745 {
5746     ImGuiWindow* window = GetCurrentWindow();
5747     if (window->SkipItems)
5748         return false;
5749 
5750     ImGuiContext& g = *GImGui;
5751     ImGuiStyle& style = g.Style;
5752     ImVec2 pos = window->DC.CursorPos;
5753     ImVec2 label_size = CalcTextSize(label, NULL, true);
5754 
5755     ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
5756     bool pressed;
5757     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
5758     {
5759         // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
5760         // Note that in this situation we render neither the shortcut neither the selected tick mark
5761         float w = label_size.x;
5762         window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
5763         PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
5764         pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
5765         PopStyleVar();
5766         window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
5767     }
5768     else
5769     {
5770         ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
5771         float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
5772         float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
5773         pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
5774         if (shortcut_size.x > 0.0f)
5775         {
5776             PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
5777             RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
5778             PopStyleColor();
5779         }
5780         if (selected)
5781             RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize  * 0.866f);
5782     }
5783 
5784     IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
5785     return pressed;
5786 }
5787 
MenuItem(const char * label,const char * shortcut,bool * p_selected,bool enabled)5788 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
5789 {
5790     if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
5791     {
5792         if (p_selected)
5793             *p_selected = !*p_selected;
5794         return true;
5795     }
5796     return false;
5797 }
5798 
5799 //-------------------------------------------------------------------------
5800 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
5801 //-------------------------------------------------------------------------
5802 // [BETA API] API may evolve! This code has been extracted out of the Docking branch,
5803 // and some of the construct which are not used in Master may be left here to facilitate merging.
5804 //-------------------------------------------------------------------------
5805 // - BeginTabBar()
5806 // - BeginTabBarEx() [Internal]
5807 // - EndTabBar()
5808 // - TabBarLayout() [Internal]
5809 // - TabBarCalcTabID() [Internal]
5810 // - TabBarCalcMaxTabWidth() [Internal]
5811 // - TabBarFindTabById() [Internal]
5812 // - TabBarRemoveTab() [Internal]
5813 // - TabBarCloseTab() [Internal]
5814 // - TabBarScrollClamp()v
5815 // - TabBarScrollToTab() [Internal]
5816 // - TabBarQueueChangeTabOrder() [Internal]
5817 // - TabBarScrollingButtons() [Internal]
5818 // - TabBarTabListPopupButton() [Internal]
5819 //-------------------------------------------------------------------------
5820 
5821 namespace ImGui
5822 {
5823     static void             TabBarLayout(ImGuiTabBar* tab_bar);
5824     static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
5825     static float            TabBarCalcMaxTabWidth();
5826     static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
5827     static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
5828     static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
5829     static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
5830 }
5831 
ImGuiTabBar()5832 ImGuiTabBar::ImGuiTabBar()
5833 {
5834     ID = 0;
5835     SelectedTabId = NextSelectedTabId = VisibleTabId = 0;
5836     CurrFrameVisible = PrevFrameVisible = -1;
5837     ContentsHeight = 0.0f;
5838     OffsetMax = OffsetNextTab = 0.0f;
5839     ScrollingAnim = ScrollingTarget = 0.0f;
5840     Flags = ImGuiTabBarFlags_None;
5841     ReorderRequestTabId = 0;
5842     ReorderRequestDir = 0;
5843     WantLayout = VisibleTabWasSubmitted = false;
5844     LastTabItemIdx = -1;
5845 }
5846 
TabItemComparerByVisibleOffset(const void * lhs,const void * rhs)5847 static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs)
5848 {
5849     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
5850     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
5851     return (int)(a->Offset - b->Offset);
5852 }
5853 
TabBarSortItemComparer(const void * lhs,const void * rhs)5854 static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)
5855 {
5856     const ImGuiTabBarSortItem* a = (const ImGuiTabBarSortItem*)lhs;
5857     const ImGuiTabBarSortItem* b = (const ImGuiTabBarSortItem*)rhs;
5858     if (int d = (int)(b->Width - a->Width))
5859         return d;
5860     return (b->Index - a->Index);
5861 }
5862 
BeginTabBar(const char * str_id,ImGuiTabBarFlags flags)5863 bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
5864 {
5865     ImGuiContext& g = *GImGui;
5866     ImGuiWindow* window = g.CurrentWindow;
5867     if (window->SkipItems)
5868         return false;
5869 
5870     ImGuiID id = window->GetID(str_id);
5871     ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
5872     ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->InnerClipRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
5873     tab_bar->ID = id;
5874     return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
5875 }
5876 
BeginTabBarEx(ImGuiTabBar * tab_bar,const ImRect & tab_bar_bb,ImGuiTabBarFlags flags)5877 bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
5878 {
5879     ImGuiContext& g = *GImGui;
5880     ImGuiWindow* window = g.CurrentWindow;
5881     if (window->SkipItems)
5882         return false;
5883 
5884     if ((flags & ImGuiTabBarFlags_DockNode) == 0)
5885         window->IDStack.push_back(tab_bar->ID);
5886 
5887     g.CurrentTabBar.push_back(tab_bar);
5888     if (tab_bar->CurrFrameVisible == g.FrameCount)
5889     {
5890         //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
5891         IM_ASSERT(0);
5892         return true;
5893     }
5894 
5895     // When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.
5896     // Otherwise, the most recently inserted tabs would move at the end of visible list which can be a little too confusing or magic for the user.
5897     if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1)
5898         ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset);
5899 
5900     // Flags
5901     if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
5902         flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
5903 
5904     tab_bar->Flags = flags;
5905     tab_bar->BarRect = tab_bar_bb;
5906     tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
5907     tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
5908     tab_bar->CurrFrameVisible = g.FrameCount;
5909     tab_bar->FramePadding = g.Style.FramePadding;
5910 
5911     // Layout
5912     ItemSize(ImVec2(tab_bar->OffsetMax, tab_bar->BarRect.GetHeight()));
5913     window->DC.CursorPos.x = tab_bar->BarRect.Min.x;
5914 
5915     // Draw separator
5916     const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab);
5917     const float y = tab_bar->BarRect.Max.y - 1.0f;
5918     {
5919         const float separator_min_x = tab_bar->BarRect.Min.x - window->WindowPadding.x;
5920         const float separator_max_x = tab_bar->BarRect.Max.x + window->WindowPadding.x;
5921         window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
5922     }
5923     return true;
5924 }
5925 
EndTabBar()5926 void    ImGui::EndTabBar()
5927 {
5928     ImGuiContext& g = *GImGui;
5929     ImGuiWindow* window = g.CurrentWindow;
5930     if (window->SkipItems)
5931         return;
5932 
5933     IM_ASSERT(!g.CurrentTabBar.empty());      // Mismatched BeginTabBar/EndTabBar
5934     ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
5935     if (tab_bar->WantLayout)
5936         TabBarLayout(tab_bar);
5937 
5938     // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
5939     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
5940     if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
5941         tab_bar->ContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f);
5942     else
5943         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->ContentsHeight;
5944 
5945     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
5946         PopID();
5947     g.CurrentTabBar.pop_back();
5948 }
5949 
5950 // This is called only once a frame before by the first call to ItemTab()
5951 // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
TabBarLayout(ImGuiTabBar * tab_bar)5952 static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
5953 {
5954     ImGuiContext& g = *GImGui;
5955     tab_bar->WantLayout = false;
5956 
5957     // Garbage collect
5958     int tab_dst_n = 0;
5959     for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
5960     {
5961         ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
5962         if (tab->LastFrameVisible < tab_bar->PrevFrameVisible)
5963         {
5964             if (tab->ID == tab_bar->SelectedTabId)
5965                 tab_bar->SelectedTabId = 0;
5966             continue;
5967         }
5968         if (tab_dst_n != tab_src_n)
5969             tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
5970         tab_dst_n++;
5971     }
5972     if (tab_bar->Tabs.Size != tab_dst_n)
5973         tab_bar->Tabs.resize(tab_dst_n);
5974 
5975     // Setup next selected tab
5976     ImGuiID scroll_track_selected_tab_id = 0;
5977     if (tab_bar->NextSelectedTabId)
5978     {
5979         tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
5980         tab_bar->NextSelectedTabId = 0;
5981         scroll_track_selected_tab_id = tab_bar->SelectedTabId;
5982     }
5983 
5984     // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
5985     if (tab_bar->ReorderRequestTabId != 0)
5986     {
5987         if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId))
5988         {
5989             //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
5990             int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;
5991             if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size)
5992             {
5993                 ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
5994                 ImGuiTabItem item_tmp = *tab1;
5995                 *tab1 = *tab2;
5996                 *tab2 = item_tmp;
5997                 if (tab2->ID == tab_bar->SelectedTabId)
5998                     scroll_track_selected_tab_id = tab2->ID;
5999                 tab1 = tab2 = NULL;
6000             }
6001             if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
6002                 MarkIniSettingsDirty();
6003         }
6004         tab_bar->ReorderRequestTabId = 0;
6005     }
6006 
6007     // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
6008     const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
6009     if (tab_list_popup_button)
6010         if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x!
6011             scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
6012 
6013     ImVector<ImGuiTabBarSortItem>& width_sort_buffer = g.TabSortByWidthBuffer;
6014     width_sort_buffer.resize(tab_bar->Tabs.Size);
6015 
6016     // Compute ideal widths
6017     float width_total_contents = 0.0f;
6018     ImGuiTabItem* most_recently_selected_tab = NULL;
6019     bool found_selected_tab_id = false;
6020     for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6021     {
6022         ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6023         IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
6024 
6025         if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
6026             most_recently_selected_tab = tab;
6027         if (tab->ID == tab_bar->SelectedTabId)
6028             found_selected_tab_id = true;
6029 
6030         // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
6031         // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
6032         // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
6033         const char* tab_name = tab_bar->GetTabName(tab);
6034         tab->WidthContents = TabItemCalcSize(tab_name, (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true).x;
6035 
6036         width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->WidthContents;
6037 
6038         // Store data so we can build an array sorted by width if we need to shrink tabs down
6039         width_sort_buffer[tab_n].Index = tab_n;
6040         width_sort_buffer[tab_n].Width = tab->WidthContents;
6041     }
6042 
6043     // Compute width
6044     const float width_avail = tab_bar->BarRect.GetWidth();
6045     float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f;
6046     if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown))
6047     {
6048         // If we don't have enough room, resize down the largest tabs first
6049         if (tab_bar->Tabs.Size > 1)
6050             ImQsort(width_sort_buffer.Data, (size_t)width_sort_buffer.Size, sizeof(ImGuiTabBarSortItem), TabBarSortItemComparer);
6051         int tab_count_same_width = 1;
6052         while (width_excess > 0.0f && tab_count_same_width < tab_bar->Tabs.Size)
6053         {
6054             while (tab_count_same_width < tab_bar->Tabs.Size && width_sort_buffer[0].Width == width_sort_buffer[tab_count_same_width].Width)
6055                 tab_count_same_width++;
6056             float width_to_remove_per_tab_max = (tab_count_same_width < tab_bar->Tabs.Size) ? (width_sort_buffer[0].Width - width_sort_buffer[tab_count_same_width].Width) : (width_sort_buffer[0].Width - 1.0f);
6057             float width_to_remove_per_tab = ImMin(width_excess / tab_count_same_width, width_to_remove_per_tab_max);
6058             for (int tab_n = 0; tab_n < tab_count_same_width; tab_n++)
6059                 width_sort_buffer[tab_n].Width -= width_to_remove_per_tab;
6060             width_excess -= width_to_remove_per_tab * tab_count_same_width;
6061         }
6062         for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6063             tab_bar->Tabs[width_sort_buffer[tab_n].Index].Width = (float)(int)width_sort_buffer[tab_n].Width;
6064     }
6065     else
6066     {
6067         const float tab_max_width = TabBarCalcMaxTabWidth();
6068         for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6069         {
6070             ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6071             tab->Width = ImMin(tab->WidthContents, tab_max_width);
6072         }
6073     }
6074 
6075     // Layout all active tabs
6076     float offset_x = 0.0f;
6077     for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6078     {
6079         ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6080         tab->Offset = offset_x;
6081         if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID)
6082             scroll_track_selected_tab_id = tab->ID;
6083         offset_x += tab->Width + g.Style.ItemInnerSpacing.x;
6084     }
6085     tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f);
6086     tab_bar->OffsetNextTab = 0.0f;
6087 
6088     // Horizontal scrolling buttons
6089     const bool scrolling_buttons = (tab_bar->OffsetMax > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll);
6090     if (scrolling_buttons)
6091         if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x!
6092             scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
6093 
6094     // If we have lost the selected tab, select the next most recently active one
6095     if (found_selected_tab_id == false)
6096         tab_bar->SelectedTabId = 0;
6097     if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
6098         scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
6099 
6100     // Lock in visible tab
6101     tab_bar->VisibleTabId = tab_bar->SelectedTabId;
6102     tab_bar->VisibleTabWasSubmitted = false;
6103 
6104     // Update scrolling
6105     if (scroll_track_selected_tab_id)
6106         if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id))
6107             TabBarScrollToTab(tab_bar, scroll_track_selected_tab);
6108     tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
6109     tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
6110     const float scrolling_speed = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) ? FLT_MAX : (g.IO.DeltaTime * g.FontSize * 70.0f);
6111     if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
6112         tab_bar->ScrollingAnim = ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, scrolling_speed);
6113 
6114     // Clear name buffers
6115     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
6116         tab_bar->TabsNames.Buf.resize(0);
6117 }
6118 
6119 // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
TabBarCalcTabID(ImGuiTabBar * tab_bar,const char * label)6120 static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
6121 {
6122     if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
6123     {
6124         ImGuiID id = ImHashStr(label, 0);
6125         KeepAliveID(id);
6126         return id;
6127     }
6128     else
6129     {
6130         ImGuiWindow* window = GImGui->CurrentWindow;
6131         return window->GetID(label);
6132     }
6133 }
6134 
TabBarCalcMaxTabWidth()6135 static float ImGui::TabBarCalcMaxTabWidth()
6136 {
6137     ImGuiContext& g = *GImGui;
6138     return g.FontSize * 20.0f;
6139 }
6140 
TabBarFindTabByID(ImGuiTabBar * tab_bar,ImGuiID tab_id)6141 ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
6142 {
6143     if (tab_id != 0)
6144         for (int n = 0; n < tab_bar->Tabs.Size; n++)
6145             if (tab_bar->Tabs[n].ID == tab_id)
6146                 return &tab_bar->Tabs[n];
6147     return NULL;
6148 }
6149 
6150 // The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
TabBarRemoveTab(ImGuiTabBar * tab_bar,ImGuiID tab_id)6151 void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
6152 {
6153     if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
6154         tab_bar->Tabs.erase(tab);
6155     if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
6156     if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
6157     if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
6158 }
6159 
6160 // Called on manual closure attempt
TabBarCloseTab(ImGuiTabBar * tab_bar,ImGuiTabItem * tab)6161 void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
6162 {
6163     if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
6164     {
6165         // This will remove a frame of lag for selecting another tab on closure.
6166         // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
6167         tab->LastFrameVisible = -1;
6168         tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
6169     }
6170     else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
6171     {
6172         // Actually select before expecting closure
6173         tab_bar->NextSelectedTabId = tab->ID;
6174     }
6175 }
6176 
TabBarScrollClamp(ImGuiTabBar * tab_bar,float scrolling)6177 static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
6178 {
6179     scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth());
6180     return ImMax(scrolling, 0.0f);
6181 }
6182 
TabBarScrollToTab(ImGuiTabBar * tab_bar,ImGuiTabItem * tab)6183 static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
6184 {
6185     ImGuiContext& g = *GImGui;
6186     float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
6187     int order = tab_bar->GetTabOrder(tab);
6188     float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f);
6189     float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f);
6190     if (tab_bar->ScrollingTarget > tab_x1)
6191         tab_bar->ScrollingTarget = tab_x1;
6192     if (tab_bar->ScrollingTarget + tab_bar->BarRect.GetWidth() < tab_x2)
6193         tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth();
6194 }
6195 
TabBarQueueChangeTabOrder(ImGuiTabBar * tab_bar,const ImGuiTabItem * tab,int dir)6196 void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)
6197 {
6198     IM_ASSERT(dir == -1 || dir == +1);
6199     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
6200     tab_bar->ReorderRequestTabId = tab->ID;
6201     tab_bar->ReorderRequestDir = dir;
6202 }
6203 
TabBarScrollingButtons(ImGuiTabBar * tab_bar)6204 static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
6205 {
6206     ImGuiContext& g = *GImGui;
6207     ImGuiWindow* window = g.CurrentWindow;
6208 
6209     const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
6210     const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
6211 
6212     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
6213     //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
6214 
6215     const ImRect avail_bar_rect = tab_bar->BarRect;
6216     bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f)));
6217     if (want_clip_rect)
6218         PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true);
6219 
6220     ImGuiTabItem* tab_to_select = NULL;
6221 
6222     int select_dir = 0;
6223     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
6224     arrow_col.w *= 0.5f;
6225 
6226     PushStyleColor(ImGuiCol_Text, arrow_col);
6227     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
6228     const float backup_repeat_delay = g.IO.KeyRepeatDelay;
6229     const float backup_repeat_rate = g.IO.KeyRepeatRate;
6230     g.IO.KeyRepeatDelay = 0.250f;
6231     g.IO.KeyRepeatRate = 0.200f;
6232     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y);
6233     if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
6234         select_dir = -1;
6235     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y);
6236     if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
6237         select_dir = +1;
6238     PopStyleColor(2);
6239     g.IO.KeyRepeatRate = backup_repeat_rate;
6240     g.IO.KeyRepeatDelay = backup_repeat_delay;
6241 
6242     if (want_clip_rect)
6243         PopClipRect();
6244 
6245     if (select_dir != 0)
6246         if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
6247         {
6248             int selected_order = tab_bar->GetTabOrder(tab_item);
6249             int target_order = selected_order + select_dir;
6250             tab_to_select = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible
6251         }
6252     window->DC.CursorPos = backup_cursor_pos;
6253     tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
6254 
6255     return tab_to_select;
6256 }
6257 
TabBarTabListPopupButton(ImGuiTabBar * tab_bar)6258 static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
6259 {
6260     ImGuiContext& g = *GImGui;
6261     ImGuiWindow* window = g.CurrentWindow;
6262 
6263     // We use g.Style.FramePadding.y to match the square ArrowButton size
6264     const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
6265     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
6266     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
6267     tab_bar->BarRect.Min.x += tab_list_popup_button_width;
6268 
6269     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
6270     arrow_col.w *= 0.5f;
6271     PushStyleColor(ImGuiCol_Text, arrow_col);
6272     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
6273     bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview);
6274     PopStyleColor(2);
6275 
6276     ImGuiTabItem* tab_to_select = NULL;
6277     if (open)
6278     {
6279         for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6280         {
6281             ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6282             const char* tab_name = tab_bar->GetTabName(tab);
6283             if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
6284                 tab_to_select = tab;
6285         }
6286         EndCombo();
6287     }
6288 
6289     window->DC.CursorPos = backup_cursor_pos;
6290     return tab_to_select;
6291 }
6292 
6293 //-------------------------------------------------------------------------
6294 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
6295 //-------------------------------------------------------------------------
6296 // [BETA API] API may evolve! This code has been extracted out of the Docking branch,
6297 // and some of the construct which are not used in Master may be left here to facilitate merging.
6298 //-------------------------------------------------------------------------
6299 // - BeginTabItem()
6300 // - EndTabItem()
6301 // - TabItemEx() [Internal]
6302 // - SetTabItemClosed()
6303 // - TabItemCalcSize() [Internal]
6304 // - TabItemBackground() [Internal]
6305 // - TabItemLabelAndCloseButton() [Internal]
6306 //-------------------------------------------------------------------------
6307 
BeginTabItem(const char * label,bool * p_open,ImGuiTabItemFlags flags)6308 bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
6309 {
6310     ImGuiContext& g = *GImGui;
6311     if (g.CurrentWindow->SkipItems)
6312         return false;
6313 
6314     IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
6315     ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
6316     bool ret = TabItemEx(tab_bar, label, p_open, flags);
6317     if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
6318     {
6319         ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
6320         g.CurrentWindow->IDStack.push_back(tab->ID);    // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
6321     }
6322     return ret;
6323 }
6324 
EndTabItem()6325 void    ImGui::EndTabItem()
6326 {
6327     ImGuiContext& g = *GImGui;
6328     if (g.CurrentWindow->SkipItems)
6329         return;
6330 
6331     IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
6332     ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
6333     IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()");
6334     ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
6335     if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
6336         g.CurrentWindow->IDStack.pop_back();
6337 }
6338 
TabItemEx(ImGuiTabBar * tab_bar,const char * label,bool * p_open,ImGuiTabItemFlags flags)6339 bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
6340 {
6341     // Layout whole tab bar if not already done
6342     if (tab_bar->WantLayout)
6343         TabBarLayout(tab_bar);
6344 
6345     ImGuiContext& g = *GImGui;
6346     ImGuiWindow* window = g.CurrentWindow;
6347     if (window->SkipItems)
6348         return false;
6349 
6350     const ImGuiStyle& style = g.Style;
6351     const ImGuiID id = TabBarCalcTabID(tab_bar, label);
6352 
6353     // If the user called us with *p_open == false, we early out and don't render. We make a dummy call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
6354     if (p_open && !*p_open)
6355     {
6356         PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
6357         ItemAdd(ImRect(), id);
6358         PopItemFlag();
6359         return false;
6360     }
6361 
6362     // Calculate tab contents size
6363     ImVec2 size = TabItemCalcSize(label, p_open != NULL);
6364 
6365     // Acquire tab data
6366     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
6367     bool tab_is_new = false;
6368     if (tab == NULL)
6369     {
6370         tab_bar->Tabs.push_back(ImGuiTabItem());
6371         tab = &tab_bar->Tabs.back();
6372         tab->ID = id;
6373         tab->Width = size.x;
6374         tab_is_new = true;
6375     }
6376     tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab);
6377     tab->WidthContents = size.x;
6378 
6379     if (p_open == NULL)
6380         flags |= ImGuiTabItemFlags_NoCloseButton;
6381 
6382     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
6383     const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
6384     const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
6385     tab->LastFrameVisible = g.FrameCount;
6386     tab->Flags = flags;
6387 
6388     // Append name with zero-terminator
6389     tab->NameOffset = tab_bar->TabsNames.size();
6390     tab_bar->TabsNames.append(label, label + strlen(label) + 1);
6391 
6392     // If we are not reorderable, always reset offset based on submission order.
6393     // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)
6394     if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
6395     {
6396         tab->Offset = tab_bar->OffsetNextTab;
6397         tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x;
6398     }
6399 
6400     // Update selected tab
6401     if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
6402         if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
6403             tab_bar->NextSelectedTabId = id;  // New tabs gets activated
6404 
6405     // Lock visibility
6406     bool tab_contents_visible = (tab_bar->VisibleTabId == id);
6407     if (tab_contents_visible)
6408         tab_bar->VisibleTabWasSubmitted = true;
6409 
6410     // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
6411     if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
6412         if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
6413             tab_contents_visible = true;
6414 
6415     if (tab_appearing && !(tab_bar_appearing && !tab_is_new))
6416     {
6417         PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
6418         ItemAdd(ImRect(), id);
6419         PopItemFlag();
6420         return tab_contents_visible;
6421     }
6422 
6423     if (tab_bar->SelectedTabId == id)
6424         tab->LastFrameSelected = g.FrameCount;
6425 
6426     // Backup current layout position
6427     const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
6428 
6429     // Layout
6430     size.x = tab->Width;
6431     window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2((float)(int)tab->Offset - tab_bar->ScrollingAnim, 0.0f);
6432     ImVec2 pos = window->DC.CursorPos;
6433     ImRect bb(pos, pos + size);
6434 
6435     // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
6436     bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x >= tab_bar->BarRect.Max.x);
6437     if (want_clip_rect)
6438         PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true);
6439 
6440     ItemSize(bb, style.FramePadding.y);
6441     if (!ItemAdd(bb, id))
6442     {
6443         if (want_clip_rect)
6444             PopClipRect();
6445         window->DC.CursorPos = backup_main_cursor_pos;
6446         return tab_contents_visible;
6447     }
6448 
6449     // Click to Select a tab
6450     ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap);
6451     if (g.DragDropActive)
6452         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
6453     bool hovered, held;
6454     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
6455     hovered |= (g.HoveredId == id);
6456     if (pressed || ((flags & ImGuiTabItemFlags_SetSelected) && !tab_contents_visible)) // SetSelected can only be passed on explicit tab bar
6457         tab_bar->NextSelectedTabId = id;
6458 
6459     // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
6460     if (!held)
6461         SetItemAllowOverlap();
6462 
6463     // Drag and drop: re-order tabs
6464     if (held && !tab_appearing && IsMouseDragging(0))
6465     {
6466         if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
6467         {
6468             // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
6469             if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
6470             {
6471                 if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
6472                     TabBarQueueChangeTabOrder(tab_bar, tab, -1);
6473             }
6474             else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
6475             {
6476                 if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
6477                     TabBarQueueChangeTabOrder(tab_bar, tab, +1);
6478             }
6479         }
6480     }
6481 
6482 #if 0
6483     if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->WidthContents)
6484     {
6485         // Enlarge tab display when hovering
6486         bb.Max.x = bb.Min.x + (float)(int)ImLerp(bb.GetWidth(), tab->WidthContents, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f));
6487         display_draw_list = GetOverlayDrawList(window);
6488         TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
6489     }
6490 #endif
6491 
6492     // Render tab shape
6493     ImDrawList* display_draw_list = window->DrawList;
6494     const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused));
6495     TabItemBackground(display_draw_list, bb, flags, tab_col);
6496     RenderNavHighlight(bb, id);
6497 
6498     // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
6499     const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
6500     if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
6501         tab_bar->NextSelectedTabId = id;
6502 
6503     if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
6504         flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
6505 
6506     // Render tab label, process close button
6507     const ImGuiID close_button_id = p_open ? window->GetID((void*)((intptr_t)id + 1)) : 0;
6508     bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id);
6509     if (just_closed && p_open != NULL)
6510     {
6511         *p_open = false;
6512         TabBarCloseTab(tab_bar, tab);
6513     }
6514 
6515     // Restore main window position so user can draw there
6516     if (want_clip_rect)
6517         PopClipRect();
6518     window->DC.CursorPos = backup_main_cursor_pos;
6519 
6520     // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
6521     if (g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f)
6522         if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip))
6523             SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
6524 
6525     return tab_contents_visible;
6526 }
6527 
6528 // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
6529 // To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()
SetTabItemClosed(const char * label)6530 void    ImGui::SetTabItemClosed(const char* label)
6531 {
6532     ImGuiContext& g = *GImGui;
6533     bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode);
6534     if (is_within_manual_tab_bar)
6535     {
6536         ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
6537         IM_ASSERT(tab_bar->WantLayout);         // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()
6538         ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
6539         TabBarRemoveTab(tab_bar, tab_id);
6540     }
6541 }
6542 
TabItemCalcSize(const char * label,bool has_close_button)6543 ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
6544 {
6545     ImGuiContext& g = *GImGui;
6546     ImVec2 label_size = CalcTextSize(label, NULL, true);
6547     ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
6548     if (has_close_button)
6549         size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
6550     else
6551         size.x += g.Style.FramePadding.x + 1.0f;
6552     return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
6553 }
6554 
TabItemBackground(ImDrawList * draw_list,const ImRect & bb,ImGuiTabItemFlags flags,ImU32 col)6555 void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
6556 {
6557     // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
6558     ImGuiContext& g = *GImGui;
6559     const float width = bb.GetWidth();
6560     IM_UNUSED(flags);
6561     IM_ASSERT(width > 0.0f);
6562     const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f));
6563     const float y1 = bb.Min.y + 1.0f;
6564     const float y2 = bb.Max.y - 1.0f;
6565     draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
6566     draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
6567     draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
6568     draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
6569     draw_list->PathFillConvex(col);
6570     if (g.Style.TabBorderSize > 0.0f)
6571     {
6572         draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
6573         draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
6574         draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
6575         draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
6576         draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize);
6577     }
6578 }
6579 
6580 // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
6581 // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
TabItemLabelAndCloseButton(ImDrawList * draw_list,const ImRect & bb,ImGuiTabItemFlags flags,ImVec2 frame_padding,const char * label,ImGuiID tab_id,ImGuiID close_button_id)6582 bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id)
6583 {
6584     ImGuiContext& g = *GImGui;
6585     ImVec2 label_size = CalcTextSize(label, NULL, true);
6586     if (bb.GetWidth() <= 1.0f)
6587         return false;
6588 
6589     // Render text label (with clipping + alpha gradient) + unsaved marker
6590     const char* TAB_UNSAVED_MARKER = "*";
6591     ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
6592     if (flags & ImGuiTabItemFlags_UnsavedDocument)
6593     {
6594         text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
6595         ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + (float)(int)(-g.FontSize * 0.25f));
6596         RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);
6597     }
6598     ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
6599 
6600     // Close Button
6601     // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
6602     //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
6603     //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
6604     //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
6605     bool close_button_pressed = false;
6606     bool close_button_visible = false;
6607     if (close_button_id != 0)
6608         if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id)
6609             close_button_visible = true;
6610     if (close_button_visible)
6611     {
6612         ImGuiItemHoveredDataBackup last_item_backup;
6613         const float close_button_sz = g.FontSize * 0.5f;
6614         if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x - close_button_sz, bb.Min.y + frame_padding.y + close_button_sz), close_button_sz))
6615             close_button_pressed = true;
6616         last_item_backup.Restore();
6617 
6618         // Close with middle mouse button
6619         if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
6620             close_button_pressed = true;
6621 
6622         text_pixel_clip_bb.Max.x -= close_button_sz * 2.0f;
6623     }
6624 
6625     // Label with ellipsis
6626     // FIXME: This should be extracted into a helper but the use of text_pixel_clip_bb and !close_button_visible makes it tricky to abstract at the moment
6627     const char* label_display_end = FindRenderedTextEnd(label);
6628     if (label_size.x > text_ellipsis_clip_bb.GetWidth())
6629     {
6630         const int ellipsis_dot_count = 3;
6631         const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f;
6632         const char* label_end = NULL;
6633         float label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, text_ellipsis_clip_bb.GetWidth() - ellipsis_width + 1.0f, 0.0f, label, label_display_end, &label_end).x;
6634         if (label_end == label && label_end < label_display_end)    // Always display at least 1 character if there's no room for character + ellipsis
6635         {
6636             label_end = label + ImTextCountUtf8BytesFromChar(label, label_display_end);
6637             label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label, label_end).x;
6638         }
6639         while (label_end > label && ImCharIsBlankA(label_end[-1])) // Trim trailing space
6640         {
6641             label_end--;
6642             label_size_clipped_x -= g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label_end, label_end + 1).x; // Ascii blanks are always 1 byte
6643         }
6644         RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_end, &label_size, ImVec2(0.0f, 0.0f));
6645 
6646         const float ellipsis_x = text_pixel_clip_bb.Min.x + label_size_clipped_x + 1.0f;
6647         if (!close_button_visible && ellipsis_x + ellipsis_width <= bb.Max.x)
6648             RenderPixelEllipsis(draw_list, ImVec2(ellipsis_x, text_pixel_clip_bb.Min.y), ellipsis_dot_count, GetColorU32(ImGuiCol_Text));
6649     }
6650     else
6651     {
6652         RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_display_end, &label_size, ImVec2(0.0f, 0.0f));
6653     }
6654 
6655     return close_button_pressed;
6656 }
6657