1 // dear imgui, v1.85 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 // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
28 
29 */
30 
31 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
32 #define _CRT_SECURE_NO_WARNINGS
33 #endif
34 
35 #include "imgui.h"
36 #ifndef IMGUI_DISABLE
37 
38 #ifndef IMGUI_DEFINE_MATH_OPERATORS
39 #define IMGUI_DEFINE_MATH_OPERATORS
40 #endif
41 #include "imgui_internal.h"
42 
43 // System includes
44 #include <ctype.h>      // toupper
45 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
46 #include <stddef.h>     // intptr_t
47 #else
48 #include <stdint.h>     // intptr_t
49 #endif
50 
51 //-------------------------------------------------------------------------
52 // Warnings
53 //-------------------------------------------------------------------------
54 
55 // Visual Studio warnings
56 #ifdef _MSC_VER
57 #pragma warning (disable: 4127)     // condition expression is constant
58 #pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
59 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
60 #pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types
61 #endif
62 #pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
63 #pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
64 #endif
65 
66 // Clang/GCC warnings with -Weverything
67 #if defined(__clang__)
68 #if __has_warning("-Wunknown-warning-option")
69 #pragma clang diagnostic ignored "-Wunknown-warning-option"         // warning: unknown warning group 'xxx'                      // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
70 #endif
71 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
72 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
73 #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.
74 #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.
75 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
76 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
77 #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.
78 #pragma clang diagnostic ignored "-Wenum-enum-conversion"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
79 #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
80 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
81 #elif defined(__GNUC__)
82 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
83 #pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
84 #pragma GCC diagnostic ignored "-Wclass-memaccess"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
85 #endif
86 
87 //-------------------------------------------------------------------------
88 // Data
89 //-------------------------------------------------------------------------
90 
91 // Widgets
92 static const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
93 static const float          DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f;    // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags.
94 
95 // Those MIN/MAX values are not define because we need to point to them
96 static const signed char    IM_S8_MIN  = -128;
97 static const signed char    IM_S8_MAX  = 127;
98 static const unsigned char  IM_U8_MIN  = 0;
99 static const unsigned char  IM_U8_MAX  = 0xFF;
100 static const signed short   IM_S16_MIN = -32768;
101 static const signed short   IM_S16_MAX = 32767;
102 static const unsigned short IM_U16_MIN = 0;
103 static const unsigned short IM_U16_MAX = 0xFFFF;
104 static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
105 static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
106 static const ImU32          IM_U32_MIN = 0;
107 static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
108 #ifdef LLONG_MIN
109 static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
110 static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
111 #else
112 static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
113 static const ImS64          IM_S64_MAX = 9223372036854775807LL;
114 #endif
115 static const ImU64          IM_U64_MIN = 0;
116 #ifdef ULLONG_MAX
117 static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
118 #else
119 static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
120 #endif
121 
122 //-------------------------------------------------------------------------
123 // [SECTION] Forward Declarations
124 //-------------------------------------------------------------------------
125 
126 // For InputTextEx()
127 static bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
128 static int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
129 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);
130 
131 //-------------------------------------------------------------------------
132 // [SECTION] Widgets: Text, etc.
133 //-------------------------------------------------------------------------
134 // - TextEx() [Internal]
135 // - TextUnformatted()
136 // - Text()
137 // - TextV()
138 // - TextColored()
139 // - TextColoredV()
140 // - TextDisabled()
141 // - TextDisabledV()
142 // - TextWrapped()
143 // - TextWrappedV()
144 // - LabelText()
145 // - LabelTextV()
146 // - BulletText()
147 // - BulletTextV()
148 //-------------------------------------------------------------------------
149 
TextEx(const char * text,const char * text_end,ImGuiTextFlags flags)150 void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
151 {
152     ImGuiWindow* window = GetCurrentWindow();
153     if (window->SkipItems)
154         return;
155     ImGuiContext& g = *GImGui;
156 
157     // Accept null ranges
158     if (text == text_end)
159         text = text_end = "";
160 
161     // Calculate length
162     const char* text_begin = text;
163     if (text_end == NULL)
164         text_end = text + strlen(text); // FIXME-OPT
165 
166     const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
167     const float wrap_pos_x = window->DC.TextWrapPos;
168     const bool wrap_enabled = (wrap_pos_x >= 0.0f);
169     if (text_end - text > 2000 && !wrap_enabled)
170     {
171         // Long text!
172         // Perform manual coarse clipping to optimize for long multi-line text
173         // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
174         // - 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.
175         // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
176         const char* line = text;
177         const float line_height = GetTextLineHeight();
178         ImVec2 text_size(0, 0);
179 
180         // Lines to skip (can't skip when logging text)
181         ImVec2 pos = text_pos;
182         if (!g.LogEnabled)
183         {
184             int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
185             if (lines_skippable > 0)
186             {
187                 int lines_skipped = 0;
188                 while (line < text_end && lines_skipped < lines_skippable)
189                 {
190                     const char* line_end = (const char*)memchr(line, '\n', text_end - line);
191                     if (!line_end)
192                         line_end = text_end;
193                     if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
194                         text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
195                     line = line_end + 1;
196                     lines_skipped++;
197                 }
198                 pos.y += lines_skipped * line_height;
199             }
200         }
201 
202         // Lines to render
203         if (line < text_end)
204         {
205             ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
206             while (line < text_end)
207             {
208                 if (IsClippedEx(line_rect, 0, false))
209                     break;
210 
211                 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
212                 if (!line_end)
213                     line_end = text_end;
214                 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
215                 RenderText(pos, line, line_end, false);
216                 line = line_end + 1;
217                 line_rect.Min.y += line_height;
218                 line_rect.Max.y += line_height;
219                 pos.y += line_height;
220             }
221 
222             // Count remaining lines
223             int lines_skipped = 0;
224             while (line < text_end)
225             {
226                 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
227                 if (!line_end)
228                     line_end = text_end;
229                 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
230                     text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
231                 line = line_end + 1;
232                 lines_skipped++;
233             }
234             pos.y += lines_skipped * line_height;
235         }
236         text_size.y = (pos - text_pos).y;
237 
238         ImRect bb(text_pos, text_pos + text_size);
239         ItemSize(text_size, 0.0f);
240         ItemAdd(bb, 0);
241     }
242     else
243     {
244         const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
245         const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
246 
247         ImRect bb(text_pos, text_pos + text_size);
248         ItemSize(text_size, 0.0f);
249         if (!ItemAdd(bb, 0))
250             return;
251 
252         // Render (we don't hide text after ## in this end-user function)
253         RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
254     }
255 }
256 
TextUnformatted(const char * text,const char * text_end)257 void ImGui::TextUnformatted(const char* text, const char* text_end)
258 {
259     TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
260 }
261 
Text(const char * fmt,...)262 void ImGui::Text(const char* fmt, ...)
263 {
264     va_list args;
265     va_start(args, fmt);
266     TextV(fmt, args);
267     va_end(args);
268 }
269 
TextV(const char * fmt,va_list args)270 void ImGui::TextV(const char* fmt, va_list args)
271 {
272     ImGuiWindow* window = GetCurrentWindow();
273     if (window->SkipItems)
274         return;
275 
276     ImGuiContext& g = *GImGui;
277     const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
278     TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
279 }
280 
TextColored(const ImVec4 & col,const char * fmt,...)281 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
282 {
283     va_list args;
284     va_start(args, fmt);
285     TextColoredV(col, fmt, args);
286     va_end(args);
287 }
288 
TextColoredV(const ImVec4 & col,const char * fmt,va_list args)289 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
290 {
291     PushStyleColor(ImGuiCol_Text, col);
292     if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
293         TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
294     else
295         TextV(fmt, args);
296     PopStyleColor();
297 }
298 
TextDisabled(const char * fmt,...)299 void ImGui::TextDisabled(const char* fmt, ...)
300 {
301     va_list args;
302     va_start(args, fmt);
303     TextDisabledV(fmt, args);
304     va_end(args);
305 }
306 
TextDisabledV(const char * fmt,va_list args)307 void ImGui::TextDisabledV(const char* fmt, va_list args)
308 {
309     ImGuiContext& g = *GImGui;
310     PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
311     if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
312         TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
313     else
314         TextV(fmt, args);
315     PopStyleColor();
316 }
317 
TextWrapped(const char * fmt,...)318 void ImGui::TextWrapped(const char* fmt, ...)
319 {
320     va_list args;
321     va_start(args, fmt);
322     TextWrappedV(fmt, args);
323     va_end(args);
324 }
325 
TextWrappedV(const char * fmt,va_list args)326 void ImGui::TextWrappedV(const char* fmt, va_list args)
327 {
328     ImGuiContext& g = *GImGui;
329     bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
330     if (need_backup)
331         PushTextWrapPos(0.0f);
332     if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
333         TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
334     else
335         TextV(fmt, args);
336     if (need_backup)
337         PopTextWrapPos();
338 }
339 
LabelText(const char * label,const char * fmt,...)340 void ImGui::LabelText(const char* label, const char* fmt, ...)
341 {
342     va_list args;
343     va_start(args, fmt);
344     LabelTextV(label, fmt, args);
345     va_end(args);
346 }
347 
348 // Add a label+text combo aligned to other label+value widgets
LabelTextV(const char * label,const char * fmt,va_list args)349 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
350 {
351     ImGuiWindow* window = GetCurrentWindow();
352     if (window->SkipItems)
353         return;
354 
355     ImGuiContext& g = *GImGui;
356     const ImGuiStyle& style = g.Style;
357     const float w = CalcItemWidth();
358 
359     const char* value_text_begin = &g.TempBuffer[0];
360     const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
361     const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
362     const ImVec2 label_size = CalcTextSize(label, NULL, true);
363 
364     const ImVec2 pos = window->DC.CursorPos;
365     const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
366     const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2));
367     ItemSize(total_bb, style.FramePadding.y);
368     if (!ItemAdd(total_bb, 0))
369         return;
370 
371     // Render
372     RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
373     if (label_size.x > 0.0f)
374         RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
375 }
376 
BulletText(const char * fmt,...)377 void ImGui::BulletText(const char* fmt, ...)
378 {
379     va_list args;
380     va_start(args, fmt);
381     BulletTextV(fmt, args);
382     va_end(args);
383 }
384 
385 // Text with a little bullet aligned to the typical tree node.
BulletTextV(const char * fmt,va_list args)386 void ImGui::BulletTextV(const char* fmt, va_list args)
387 {
388     ImGuiWindow* window = GetCurrentWindow();
389     if (window->SkipItems)
390         return;
391 
392     ImGuiContext& g = *GImGui;
393     const ImGuiStyle& style = g.Style;
394 
395     const char* text_begin = g.TempBuffer;
396     const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
397     const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
398     const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y);  // Empty text doesn't add padding
399     ImVec2 pos = window->DC.CursorPos;
400     pos.y += window->DC.CurrLineTextBaseOffset;
401     ItemSize(total_size, 0.0f);
402     const ImRect bb(pos, pos + total_size);
403     if (!ItemAdd(bb, 0))
404         return;
405 
406     // Render
407     ImU32 text_col = GetColorU32(ImGuiCol_Text);
408     RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
409     RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
410 }
411 
412 //-------------------------------------------------------------------------
413 // [SECTION] Widgets: Main
414 //-------------------------------------------------------------------------
415 // - ButtonBehavior() [Internal]
416 // - Button()
417 // - SmallButton()
418 // - InvisibleButton()
419 // - ArrowButton()
420 // - CloseButton() [Internal]
421 // - CollapseButton() [Internal]
422 // - GetWindowScrollbarID() [Internal]
423 // - GetWindowScrollbarRect() [Internal]
424 // - Scrollbar() [Internal]
425 // - ScrollbarEx() [Internal]
426 // - Image()
427 // - ImageButton()
428 // - Checkbox()
429 // - CheckboxFlagsT() [Internal]
430 // - CheckboxFlags()
431 // - RadioButton()
432 // - ProgressBar()
433 // - Bullet()
434 //-------------------------------------------------------------------------
435 
436 // The ButtonBehavior() function is key to many interactions and used by many/most widgets.
437 // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
438 // this code is a little complex.
439 // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
440 // See the series of events below and the corresponding state reported by dear imgui:
441 //------------------------------------------------------------------------------------------------------------------------------------------------
442 // with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
443 //   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
444 //   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
445 //   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
446 //   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
447 //   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
448 //   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
449 //   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
450 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
451 //   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
452 //------------------------------------------------------------------------------------------------------------------------------------------------
453 // with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
454 //   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
455 //   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
456 //   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
457 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
458 //------------------------------------------------------------------------------------------------------------------------------------------------
459 // with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
460 //   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
461 //   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
462 //   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
463 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
464 //------------------------------------------------------------------------------------------------------------------------------------------------
465 // with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
466 //   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
467 //   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
468 //   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
469 //   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
470 //   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
471 //   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
472 //   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
473 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
474 //------------------------------------------------------------------------------------------------------------------------------------------------
475 // Note that some combinations are supported,
476 // - PressedOnDragDropHold can generally be associated with any flag.
477 // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
478 //------------------------------------------------------------------------------------------------------------------------------------------------
479 // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
480 //                                         Repeat+                  Repeat+           Repeat+             Repeat+
481 //                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
482 //-------------------------------------------------------------------------------------------------------------------------------------------------
483 //   Frame N+0 (mouse button is down)       -                        true              -                   true
484 //   ...                                    -                        -                 -                   -
485 //   Frame N + RepeatDelay                  true                     true              -                   true
486 //   ...                                    -                        -                 -                   -
487 //   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
488 //-------------------------------------------------------------------------------------------------------------------------------------------------
489 
ButtonBehavior(const ImRect & bb,ImGuiID id,bool * out_hovered,bool * out_held,ImGuiButtonFlags flags)490 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
491 {
492     ImGuiContext& g = *GImGui;
493     ImGuiWindow* window = GetCurrentWindow();
494 
495     // Default only reacts to left mouse button
496     if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
497         flags |= ImGuiButtonFlags_MouseButtonDefault_;
498 
499     // Default behavior requires click + release inside bounding box
500     if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
501         flags |= ImGuiButtonFlags_PressedOnDefault_;
502 
503     ImGuiWindow* backup_hovered_window = g.HoveredWindow;
504     const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;
505     if (flatten_hovered_children)
506         g.HoveredWindow = window;
507 
508 #ifdef IMGUI_ENABLE_TEST_ENGINE
509     if (id != 0 && g.LastItemData.ID != id)
510         IMGUI_TEST_ENGINE_ITEM_ADD(bb, id);
511 #endif
512 
513     bool pressed = false;
514     bool hovered = ItemHoverable(bb, id);
515 
516     // Drag source doesn't report as hovered
517     if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
518         hovered = false;
519 
520     // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
521     if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
522         if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
523         {
524             hovered = true;
525             SetHoveredID(id);
526             if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
527             {
528                 pressed = true;
529                 g.DragDropHoldJustPressedId = id;
530                 FocusWindow(window);
531             }
532         }
533 
534     if (flatten_hovered_children)
535         g.HoveredWindow = backup_hovered_window;
536 
537     // 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.
538     if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
539         hovered = false;
540 
541     // Mouse handling
542     if (hovered)
543     {
544         if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
545         {
546             // Poll buttons
547             int mouse_button_clicked = -1;
548             int mouse_button_released = -1;
549             if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0])         { mouse_button_clicked = 0; }
550             else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1])   { mouse_button_clicked = 1; }
551             else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2])  { mouse_button_clicked = 2; }
552             if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0])        { mouse_button_released = 0; }
553             else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1])  { mouse_button_released = 1; }
554             else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; }
555 
556             if (mouse_button_clicked != -1 && g.ActiveId != id)
557             {
558                 if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
559                 {
560                     SetActiveID(id, window);
561                     g.ActiveIdMouseButton = mouse_button_clicked;
562                     if (!(flags & ImGuiButtonFlags_NoNavFocus))
563                         SetFocusID(id, window);
564                     FocusWindow(window);
565                 }
566                 if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[mouse_button_clicked]))
567                 {
568                     pressed = true;
569                     if (flags & ImGuiButtonFlags_NoHoldingActiveId)
570                         ClearActiveID();
571                     else
572                         SetActiveID(id, window); // Hold on ID
573                     g.ActiveIdMouseButton = mouse_button_clicked;
574                     FocusWindow(window);
575                 }
576             }
577             if ((flags & ImGuiButtonFlags_PressedOnRelease) && mouse_button_released != -1)
578             {
579                 // Repeat mode trumps on release behavior
580                 const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay;
581                 if (!has_repeated_at_least_once)
582                     pressed = true;
583                 ClearActiveID();
584             }
585 
586             // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
587             // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
588             if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat))
589                 if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true))
590                     pressed = true;
591         }
592 
593         if (pressed)
594             g.NavDisableHighlight = true;
595     }
596 
597     // Gamepad/Keyboard navigation
598     // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
599     if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
600         if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
601             hovered = true;
602     if (g.NavActivateDownId == id)
603     {
604         bool nav_activated_by_code = (g.NavActivateId == id);
605         bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
606         if (nav_activated_by_code || nav_activated_by_inputs)
607             pressed = true;
608         if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
609         {
610             // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
611             g.NavActivateId = id; // This is so SetActiveId assign a Nav source
612             SetActiveID(id, window);
613             if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))
614                 SetFocusID(id, window);
615         }
616     }
617 
618     // Process while held
619     bool held = false;
620     if (g.ActiveId == id)
621     {
622         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
623         {
624             if (g.ActiveIdIsJustActivated)
625                 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
626 
627             const int mouse_button = g.ActiveIdMouseButton;
628             IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);
629             if (g.IO.MouseDown[mouse_button])
630             {
631                 held = true;
632             }
633             else
634             {
635                 bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
636                 bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
637                 if ((release_in || release_anywhere) && !g.DragDropActive)
638                 {
639                     // Report as pressed when releasing the mouse (this is the most common path)
640                     bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[mouse_button];
641                     bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
642                     if (!is_double_click_release && !is_repeating_already)
643                         pressed = true;
644                 }
645                 ClearActiveID();
646             }
647             if (!(flags & ImGuiButtonFlags_NoNavFocus))
648                 g.NavDisableHighlight = true;
649         }
650         else if (g.ActiveIdSource == ImGuiInputSource_Nav)
651         {
652             // When activated using Nav, we hold on the ActiveID until activation button is released
653             if (g.NavActivateDownId != id)
654                 ClearActiveID();
655         }
656         if (pressed)
657             g.ActiveIdHasBeenPressedBefore = true;
658     }
659 
660     if (out_hovered) *out_hovered = hovered;
661     if (out_held) *out_held = held;
662 
663     return pressed;
664 }
665 
ButtonEx(const char * label,const ImVec2 & size_arg,ImGuiButtonFlags flags)666 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
667 {
668     ImGuiWindow* window = GetCurrentWindow();
669     if (window->SkipItems)
670         return false;
671 
672     ImGuiContext& g = *GImGui;
673     const ImGuiStyle& style = g.Style;
674     const ImGuiID id = window->GetID(label);
675     const ImVec2 label_size = CalcTextSize(label, NULL, true);
676 
677     ImVec2 pos = window->DC.CursorPos;
678     if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // 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)
679         pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
680     ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
681 
682     const ImRect bb(pos, pos + size);
683     ItemSize(size, style.FramePadding.y);
684     if (!ItemAdd(bb, id))
685         return false;
686 
687     if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
688         flags |= ImGuiButtonFlags_Repeat;
689 
690     bool hovered, held;
691     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
692 
693     // Render
694     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
695     RenderNavHighlight(bb, id);
696     RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
697 
698     if (g.LogEnabled)
699         LogSetNextTextDecoration("[", "]");
700     RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
701 
702     // Automatically close popups
703     //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
704     //    CloseCurrentPopup();
705 
706     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
707     return pressed;
708 }
709 
Button(const char * label,const ImVec2 & size_arg)710 bool ImGui::Button(const char* label, const ImVec2& size_arg)
711 {
712     return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
713 }
714 
715 // Small buttons fits within text without additional vertical spacing.
SmallButton(const char * label)716 bool ImGui::SmallButton(const char* label)
717 {
718     ImGuiContext& g = *GImGui;
719     float backup_padding_y = g.Style.FramePadding.y;
720     g.Style.FramePadding.y = 0.0f;
721     bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
722     g.Style.FramePadding.y = backup_padding_y;
723     return pressed;
724 }
725 
726 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
727 // 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,ImGuiButtonFlags flags)728 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
729 {
730     ImGuiWindow* window = GetCurrentWindow();
731     if (window->SkipItems)
732         return false;
733 
734     // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
735     IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
736 
737     const ImGuiID id = window->GetID(str_id);
738     ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
739     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
740     ItemSize(size);
741     if (!ItemAdd(bb, id))
742         return false;
743 
744     bool hovered, held;
745     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
746 
747     return pressed;
748 }
749 
ArrowButtonEx(const char * str_id,ImGuiDir dir,ImVec2 size,ImGuiButtonFlags flags)750 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
751 {
752     ImGuiWindow* window = GetCurrentWindow();
753     if (window->SkipItems)
754         return false;
755 
756     ImGuiContext& g = *GImGui;
757     const ImGuiID id = window->GetID(str_id);
758     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
759     const float default_size = GetFrameHeight();
760     ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
761     if (!ItemAdd(bb, id))
762         return false;
763 
764     if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
765         flags |= ImGuiButtonFlags_Repeat;
766 
767     bool hovered, held;
768     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
769 
770     // Render
771     const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
772     const ImU32 text_col = GetColorU32(ImGuiCol_Text);
773     RenderNavHighlight(bb, id);
774     RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
775     RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir);
776 
777     return pressed;
778 }
779 
ArrowButton(const char * str_id,ImGuiDir dir)780 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
781 {
782     float sz = GetFrameHeight();
783     return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
784 }
785 
786 // Button to close a window
CloseButton(ImGuiID id,const ImVec2 & pos)787 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
788 {
789     ImGuiContext& g = *GImGui;
790     ImGuiWindow* window = g.CurrentWindow;
791 
792     // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825)
793     // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible?
794     const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
795     ImRect bb_interact = bb;
796     const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
797     if (area_to_visible_ratio < 1.5f)
798         bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f));
799 
800     // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
801     // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
802     bool is_clipped = !ItemAdd(bb_interact, id);
803 
804     bool hovered, held;
805     bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
806     if (is_clipped)
807         return pressed;
808 
809     // Render
810     // FIXME: Clarify this mess
811     ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
812     ImVec2 center = bb.GetCenter();
813     if (hovered)
814         window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
815 
816     float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
817     ImU32 cross_col = GetColorU32(ImGuiCol_Text);
818     center -= ImVec2(0.5f, 0.5f);
819     window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
820     window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
821 
822     return pressed;
823 }
824 
CollapseButton(ImGuiID id,const ImVec2 & pos)825 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
826 {
827     ImGuiContext& g = *GImGui;
828     ImGuiWindow* window = g.CurrentWindow;
829 
830     ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
831     ItemAdd(bb, id);
832     bool hovered, held;
833     bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
834 
835     // Render
836     ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
837     ImU32 text_col = GetColorU32(ImGuiCol_Text);
838     ImVec2 center = bb.GetCenter();
839     if (hovered || held)
840         window->DrawList->AddCircleFilled(center/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12);
841     RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
842 
843     // Switch to moving the window after mouse is moved beyond the initial drag threshold
844     if (IsItemActive() && IsMouseDragging(0))
845         StartMouseMovingWindow(window);
846 
847     return pressed;
848 }
849 
GetWindowScrollbarID(ImGuiWindow * window,ImGuiAxis axis)850 ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
851 {
852     return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
853 }
854 
855 // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
GetWindowScrollbarRect(ImGuiWindow * window,ImGuiAxis axis)856 ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
857 {
858     const ImRect outer_rect = window->Rect();
859     const ImRect inner_rect = window->InnerRect;
860     const float border_size = window->WindowBorderSize;
861     const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
862     IM_ASSERT(scrollbar_size > 0.0f);
863     if (axis == ImGuiAxis_X)
864         return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x, outer_rect.Max.y);
865     else
866         return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y);
867 }
868 
Scrollbar(ImGuiAxis axis)869 void ImGui::Scrollbar(ImGuiAxis axis)
870 {
871     ImGuiContext& g = *GImGui;
872     ImGuiWindow* window = g.CurrentWindow;
873 
874     const ImGuiID id = GetWindowScrollbarID(window, axis);
875     KeepAliveID(id);
876 
877     // Calculate scrollbar bounding box
878     ImRect bb = GetWindowScrollbarRect(window, axis);
879     ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
880     if (axis == ImGuiAxis_X)
881     {
882         rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
883         if (!window->ScrollbarY)
884             rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
885     }
886     else
887     {
888         if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
889             rounding_corners |= ImDrawFlags_RoundCornersTopRight;
890         if (!window->ScrollbarX)
891             rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
892     }
893     float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
894     float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
895     ScrollbarEx(bb, id, axis, &window->Scroll[axis], size_avail, size_contents, rounding_corners);
896 }
897 
898 // Vertical/Horizontal scrollbar
899 // The entire piece of code below is rather confusing because:
900 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
901 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
902 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
903 // Still, the code should probably be made simpler..
ScrollbarEx(const ImRect & bb_frame,ImGuiID id,ImGuiAxis axis,float * p_scroll_v,float size_avail_v,float size_contents_v,ImDrawFlags flags)904 bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawFlags flags)
905 {
906     ImGuiContext& g = *GImGui;
907     ImGuiWindow* window = g.CurrentWindow;
908     if (window->SkipItems)
909         return false;
910 
911     const float bb_frame_width = bb_frame.GetWidth();
912     const float bb_frame_height = bb_frame.GetHeight();
913     if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
914         return false;
915 
916     // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab)
917     float alpha = 1.0f;
918     if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
919         alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
920     if (alpha <= 0.0f)
921         return false;
922 
923     const ImGuiStyle& style = g.Style;
924     const bool allow_interaction = (alpha >= 1.0f);
925 
926     ImRect bb = bb_frame;
927     bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
928 
929     // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
930     const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
931 
932     // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
933     // But we maintain a minimum size in pixel to allow for the user to still aim inside.
934     IM_ASSERT(ImMax(size_contents_v, 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.
935     const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f);
936     const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
937     const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
938 
939     // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
940     bool held = false;
941     bool hovered = false;
942     ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
943 
944     float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v);
945     float scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
946     float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
947     if (held && allow_interaction && grab_h_norm < 1.0f)
948     {
949         float scrollbar_pos_v = bb.Min[axis];
950         float mouse_pos_v = g.IO.MousePos[axis];
951 
952         // Click position in scrollbar normalized space (0.0f->1.0f)
953         const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
954         SetHoveredID(id);
955 
956         bool seek_absolute = false;
957         if (g.ActiveIdIsJustActivated)
958         {
959             // On initial click calculate the distance between mouse and the center of the grab
960             seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
961             if (seek_absolute)
962                 g.ScrollbarClickDeltaToGrabCenter = 0.0f;
963             else
964                 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
965         }
966 
967         // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
968         // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position
969         const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
970         *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
971 
972         // Update values for rendering
973         scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
974         grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
975 
976         // Update distance to grab now that we have seeked and saturated
977         if (seek_absolute)
978             g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
979     }
980 
981     // Render
982     const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
983     const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
984     window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
985     ImRect grab_rect;
986     if (axis == ImGuiAxis_X)
987         grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y);
988     else
989         grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels);
990     window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
991 
992     return held;
993 }
994 
Image(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec4 & tint_col,const ImVec4 & border_col)995 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
996 {
997     ImGuiWindow* window = GetCurrentWindow();
998     if (window->SkipItems)
999         return;
1000 
1001     ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1002     if (border_col.w > 0.0f)
1003         bb.Max += ImVec2(2, 2);
1004     ItemSize(bb);
1005     if (!ItemAdd(bb, 0))
1006         return;
1007 
1008     if (border_col.w > 0.0f)
1009     {
1010         window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
1011         window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
1012     }
1013     else
1014     {
1015         window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
1016     }
1017 }
1018 
1019 // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
1020 // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
ImageButtonEx(ImGuiID id,ImTextureID texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec2 & padding,const ImVec4 & bg_col,const ImVec4 & tint_col)1021 bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col)
1022 {
1023     ImGuiContext& g = *GImGui;
1024     ImGuiWindow* window = GetCurrentWindow();
1025     if (window->SkipItems)
1026         return false;
1027 
1028     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
1029     ItemSize(bb);
1030     if (!ItemAdd(bb, id))
1031         return false;
1032 
1033     bool hovered, held;
1034     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1035 
1036     // Render
1037     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1038     RenderNavHighlight(bb, id);
1039     RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
1040     if (bg_col.w > 0.0f)
1041         window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
1042     window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1043 
1044     return pressed;
1045 }
1046 
1047 // frame_padding < 0: uses FramePadding from style (default)
1048 // frame_padding = 0: no framing
1049 // frame_padding > 0: set framing size
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)1050 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)
1051 {
1052     ImGuiContext& g = *GImGui;
1053     ImGuiWindow* window = g.CurrentWindow;
1054     if (window->SkipItems)
1055         return false;
1056 
1057     // Default to using texture ID as ID. User can still push string/integer prefixes.
1058     PushID((void*)(intptr_t)user_texture_id);
1059     const ImGuiID id = window->GetID("#image");
1060     PopID();
1061 
1062     const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding;
1063     return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col);
1064 }
1065 
Checkbox(const char * label,bool * v)1066 bool ImGui::Checkbox(const char* label, bool* v)
1067 {
1068     ImGuiWindow* window = GetCurrentWindow();
1069     if (window->SkipItems)
1070         return false;
1071 
1072     ImGuiContext& g = *GImGui;
1073     const ImGuiStyle& style = g.Style;
1074     const ImGuiID id = window->GetID(label);
1075     const ImVec2 label_size = CalcTextSize(label, NULL, true);
1076 
1077     const float square_sz = GetFrameHeight();
1078     const ImVec2 pos = window->DC.CursorPos;
1079     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));
1080     ItemSize(total_bb, style.FramePadding.y);
1081     if (!ItemAdd(total_bb, id))
1082     {
1083         IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1084         return false;
1085     }
1086 
1087     bool hovered, held;
1088     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1089     if (pressed)
1090     {
1091         *v = !(*v);
1092         MarkItemEdited(id);
1093     }
1094 
1095     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1096     RenderNavHighlight(total_bb, id);
1097     RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
1098     ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1099     bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
1100     if (mixed_value)
1101     {
1102         // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1103         // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1104         ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)));
1105         window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1106     }
1107     else if (*v)
1108     {
1109         const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
1110         RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1111     }
1112 
1113     ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1114     if (g.LogEnabled)
1115         LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1116     if (label_size.x > 0.0f)
1117         RenderText(label_pos, label);
1118 
1119     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1120     return pressed;
1121 }
1122 
1123 template<typename T>
CheckboxFlagsT(const char * label,T * flags,T flags_value)1124 bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1125 {
1126     bool all_on = (*flags & flags_value) == flags_value;
1127     bool any_on = (*flags & flags_value) != 0;
1128     bool pressed;
1129     if (!all_on && any_on)
1130     {
1131         ImGuiContext& g = *GImGui;
1132         ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
1133         g.CurrentItemFlags |= ImGuiItemFlags_MixedValue;
1134         pressed = Checkbox(label, &all_on);
1135         g.CurrentItemFlags = backup_item_flags;
1136     }
1137     else
1138     {
1139         pressed = Checkbox(label, &all_on);
1140 
1141     }
1142     if (pressed)
1143     {
1144         if (all_on)
1145             *flags |= flags_value;
1146         else
1147             *flags &= ~flags_value;
1148     }
1149     return pressed;
1150 }
1151 
CheckboxFlags(const char * label,int * flags,int flags_value)1152 bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1153 {
1154     return CheckboxFlagsT(label, flags, flags_value);
1155 }
1156 
CheckboxFlags(const char * label,unsigned int * flags,unsigned int flags_value)1157 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1158 {
1159     return CheckboxFlagsT(label, flags, flags_value);
1160 }
1161 
CheckboxFlags(const char * label,ImS64 * flags,ImS64 flags_value)1162 bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1163 {
1164     return CheckboxFlagsT(label, flags, flags_value);
1165 }
1166 
CheckboxFlags(const char * label,ImU64 * flags,ImU64 flags_value)1167 bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1168 {
1169     return CheckboxFlagsT(label, flags, flags_value);
1170 }
1171 
RadioButton(const char * label,bool active)1172 bool ImGui::RadioButton(const char* label, bool active)
1173 {
1174     ImGuiWindow* window = GetCurrentWindow();
1175     if (window->SkipItems)
1176         return false;
1177 
1178     ImGuiContext& g = *GImGui;
1179     const ImGuiStyle& style = g.Style;
1180     const ImGuiID id = window->GetID(label);
1181     const ImVec2 label_size = CalcTextSize(label, NULL, true);
1182 
1183     const float square_sz = GetFrameHeight();
1184     const ImVec2 pos = window->DC.CursorPos;
1185     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1186     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));
1187     ItemSize(total_bb, style.FramePadding.y);
1188     if (!ItemAdd(total_bb, id))
1189         return false;
1190 
1191     ImVec2 center = check_bb.GetCenter();
1192     center.x = IM_ROUND(center.x);
1193     center.y = IM_ROUND(center.y);
1194     const float radius = (square_sz - 1.0f) * 0.5f;
1195 
1196     bool hovered, held;
1197     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1198     if (pressed)
1199         MarkItemEdited(id);
1200 
1201     RenderNavHighlight(total_bb, id);
1202     window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
1203     if (active)
1204     {
1205         const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
1206         window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
1207     }
1208 
1209     if (style.FrameBorderSize > 0.0f)
1210     {
1211         window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
1212         window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
1213     }
1214 
1215     ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1216     if (g.LogEnabled)
1217         LogRenderedText(&label_pos, active ? "(x)" : "( )");
1218     if (label_size.x > 0.0f)
1219         RenderText(label_pos, label);
1220 
1221     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1222     return pressed;
1223 }
1224 
1225 // FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it..
RadioButton(const char * label,int * v,int v_button)1226 bool ImGui::RadioButton(const char* label, int* v, int v_button)
1227 {
1228     const bool pressed = RadioButton(label, *v == v_button);
1229     if (pressed)
1230         *v = v_button;
1231     return pressed;
1232 }
1233 
1234 // 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)1235 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1236 {
1237     ImGuiWindow* window = GetCurrentWindow();
1238     if (window->SkipItems)
1239         return;
1240 
1241     ImGuiContext& g = *GImGui;
1242     const ImGuiStyle& style = g.Style;
1243 
1244     ImVec2 pos = window->DC.CursorPos;
1245     ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1246     ImRect bb(pos, pos + size);
1247     ItemSize(size, style.FramePadding.y);
1248     if (!ItemAdd(bb, 0))
1249         return;
1250 
1251     // Render
1252     fraction = ImSaturate(fraction);
1253     RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1254     bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1255     const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
1256     RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
1257 
1258     // Default displaying the fraction as percentage string, but user can override it
1259     char overlay_buf[32];
1260     if (!overlay)
1261     {
1262         ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1263         overlay = overlay_buf;
1264     }
1265 
1266     ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1267     if (overlay_size.x > 0.0f)
1268         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);
1269 }
1270 
Bullet()1271 void ImGui::Bullet()
1272 {
1273     ImGuiWindow* window = GetCurrentWindow();
1274     if (window->SkipItems)
1275         return;
1276 
1277     ImGuiContext& g = *GImGui;
1278     const ImGuiStyle& style = g.Style;
1279     const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
1280     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1281     ItemSize(bb);
1282     if (!ItemAdd(bb, 0))
1283     {
1284         SameLine(0, style.FramePadding.x * 2);
1285         return;
1286     }
1287 
1288     // Render and stay on same line
1289     ImU32 text_col = GetColorU32(ImGuiCol_Text);
1290     RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
1291     SameLine(0, style.FramePadding.x * 2.0f);
1292 }
1293 
1294 //-------------------------------------------------------------------------
1295 // [SECTION] Widgets: Low-level Layout helpers
1296 //-------------------------------------------------------------------------
1297 // - Spacing()
1298 // - Dummy()
1299 // - NewLine()
1300 // - AlignTextToFramePadding()
1301 // - SeparatorEx() [Internal]
1302 // - Separator()
1303 // - SplitterBehavior() [Internal]
1304 // - ShrinkWidths() [Internal]
1305 //-------------------------------------------------------------------------
1306 
Spacing()1307 void ImGui::Spacing()
1308 {
1309     ImGuiWindow* window = GetCurrentWindow();
1310     if (window->SkipItems)
1311         return;
1312     ItemSize(ImVec2(0, 0));
1313 }
1314 
Dummy(const ImVec2 & size)1315 void ImGui::Dummy(const ImVec2& size)
1316 {
1317     ImGuiWindow* window = GetCurrentWindow();
1318     if (window->SkipItems)
1319         return;
1320 
1321     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1322     ItemSize(size);
1323     ItemAdd(bb, 0);
1324 }
1325 
NewLine()1326 void ImGui::NewLine()
1327 {
1328     ImGuiWindow* window = GetCurrentWindow();
1329     if (window->SkipItems)
1330         return;
1331 
1332     ImGuiContext& g = *GImGui;
1333     const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1334     window->DC.LayoutType = ImGuiLayoutType_Vertical;
1335     if (window->DC.CurrLineSize.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.
1336         ItemSize(ImVec2(0, 0));
1337     else
1338         ItemSize(ImVec2(0.0f, g.FontSize));
1339     window->DC.LayoutType = backup_layout_type;
1340 }
1341 
AlignTextToFramePadding()1342 void ImGui::AlignTextToFramePadding()
1343 {
1344     ImGuiWindow* window = GetCurrentWindow();
1345     if (window->SkipItems)
1346         return;
1347 
1348     ImGuiContext& g = *GImGui;
1349     window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1350     window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1351 }
1352 
1353 // Horizontal/vertical separating line
SeparatorEx(ImGuiSeparatorFlags flags)1354 void ImGui::SeparatorEx(ImGuiSeparatorFlags flags)
1355 {
1356     ImGuiWindow* window = GetCurrentWindow();
1357     if (window->SkipItems)
1358         return;
1359 
1360     ImGuiContext& g = *GImGui;
1361     IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
1362 
1363     float thickness_draw = 1.0f;
1364     float thickness_layout = 0.0f;
1365     if (flags & ImGuiSeparatorFlags_Vertical)
1366     {
1367         // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout.
1368         float y1 = window->DC.CursorPos.y;
1369         float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1370         const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2));
1371         ItemSize(ImVec2(thickness_layout, 0.0f));
1372         if (!ItemAdd(bb, 0))
1373             return;
1374 
1375         // Draw
1376         window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
1377         if (g.LogEnabled)
1378             LogText(" |");
1379     }
1380     else if (flags & ImGuiSeparatorFlags_Horizontal)
1381     {
1382         // Horizontal Separator
1383         float x1 = window->Pos.x;
1384         float x2 = window->Pos.x + window->Size.x;
1385 
1386         // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator
1387         if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID)
1388             x1 += window->DC.Indent.x;
1389 
1390         ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1391         if (columns)
1392             PushColumnsBackground();
1393 
1394         // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1395         const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw));
1396         ItemSize(ImVec2(0.0f, thickness_layout));
1397         const bool item_visible = ItemAdd(bb, 0);
1398         if (item_visible)
1399         {
1400             // Draw
1401             window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
1402             if (g.LogEnabled)
1403                 LogRenderedText(&bb.Min, "--------------------------------\n");
1404 
1405         }
1406         if (columns)
1407         {
1408             PopColumnsBackground();
1409             columns->LineMinY = window->DC.CursorPos.y;
1410         }
1411     }
1412 }
1413 
Separator()1414 void ImGui::Separator()
1415 {
1416     ImGuiContext& g = *GImGui;
1417     ImGuiWindow* window = g.CurrentWindow;
1418     if (window->SkipItems)
1419         return;
1420 
1421     // Those flags should eventually be overridable by the user
1422     ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1423     flags |= ImGuiSeparatorFlags_SpanAllColumns;
1424     SeparatorEx(flags);
1425 }
1426 
1427 // 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)1428 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)
1429 {
1430     ImGuiContext& g = *GImGui;
1431     ImGuiWindow* window = g.CurrentWindow;
1432 
1433     const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
1434     g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
1435     bool item_add = ItemAdd(bb, id);
1436     g.CurrentItemFlags = item_flags_backup;
1437     if (!item_add)
1438         return false;
1439 
1440     bool hovered, held;
1441     ImRect bb_interact = bb;
1442     bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1443     ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
1444     if (hovered)
1445         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1446     if (g.ActiveId != id)
1447         SetItemAllowOverlap();
1448 
1449     if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1450         SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1451 
1452     ImRect bb_render = bb;
1453     if (held)
1454     {
1455         ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
1456         float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
1457 
1458         // Minimum pane size
1459         float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1460         float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1461         if (mouse_delta < -size_1_maximum_delta)
1462             mouse_delta = -size_1_maximum_delta;
1463         if (mouse_delta > size_2_maximum_delta)
1464             mouse_delta = size_2_maximum_delta;
1465 
1466         // Apply resize
1467         if (mouse_delta != 0.0f)
1468         {
1469             if (mouse_delta < 0.0f)
1470                 IM_ASSERT(*size1 + mouse_delta >= min_size1);
1471             if (mouse_delta > 0.0f)
1472                 IM_ASSERT(*size2 - mouse_delta >= min_size2);
1473             *size1 += mouse_delta;
1474             *size2 -= mouse_delta;
1475             bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1476             MarkItemEdited(id);
1477         }
1478     }
1479 
1480     // Render
1481     const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1482     window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1483 
1484     return held;
1485 }
1486 
ShrinkWidthItemComparer(const void * lhs,const void * rhs)1487 static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1488 {
1489     const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1490     const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1491     if (int d = (int)(b->Width - a->Width))
1492         return d;
1493     return (b->Index - a->Index);
1494 }
1495 
1496 // Shrink excess width from a set of item, by removing width from the larger items first.
1497 // Set items Width to -1.0f to disable shrinking this item.
ShrinkWidths(ImGuiShrinkWidthItem * items,int count,float width_excess)1498 void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1499 {
1500     if (count == 1)
1501     {
1502         if (items[0].Width >= 0.0f)
1503             items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
1504         return;
1505     }
1506     ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
1507     int count_same_width = 1;
1508     while (width_excess > 0.0f && count_same_width < count)
1509     {
1510         while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1511             count_same_width++;
1512         float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);
1513         if (max_width_to_remove_per_item <= 0.0f)
1514             break;
1515         float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
1516         for (int item_n = 0; item_n < count_same_width; item_n++)
1517             items[item_n].Width -= width_to_remove_per_item;
1518         width_excess -= width_to_remove_per_item * count_same_width;
1519     }
1520 
1521     // Round width and redistribute remainder left-to-right (could make it an option of the function?)
1522     // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.
1523     width_excess = 0.0f;
1524     for (int n = 0; n < count; n++)
1525     {
1526         float width_rounded = ImFloor(items[n].Width);
1527         width_excess += items[n].Width - width_rounded;
1528         items[n].Width = width_rounded;
1529     }
1530     if (width_excess > 0.0f)
1531         for (int n = 0; n < count; n++)
1532             if (items[n].Index < (int)(width_excess + 0.01f))
1533                 items[n].Width += 1.0f;
1534 }
1535 
1536 //-------------------------------------------------------------------------
1537 // [SECTION] Widgets: ComboBox
1538 //-------------------------------------------------------------------------
1539 // - CalcMaxPopupHeightFromItemCount() [Internal]
1540 // - BeginCombo()
1541 // - BeginComboPopup() [Internal]
1542 // - EndCombo()
1543 // - BeginComboPreview() [Internal]
1544 // - EndComboPreview() [Internal]
1545 // - Combo()
1546 //-------------------------------------------------------------------------
1547 
CalcMaxPopupHeightFromItemCount(int items_count)1548 static float CalcMaxPopupHeightFromItemCount(int items_count)
1549 {
1550     ImGuiContext& g = *GImGui;
1551     if (items_count <= 0)
1552         return FLT_MAX;
1553     return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1554 }
1555 
BeginCombo(const char * label,const char * preview_value,ImGuiComboFlags flags)1556 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1557 {
1558     ImGuiContext& g = *GImGui;
1559     ImGuiWindow* window = GetCurrentWindow();
1560 
1561     ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1562     g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1563     if (window->SkipItems)
1564         return false;
1565 
1566     const ImGuiStyle& style = g.Style;
1567     const ImGuiID id = window->GetID(label);
1568     IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1569 
1570     const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1571     const ImVec2 label_size = CalcTextSize(label, NULL, true);
1572     const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
1573     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1574     const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1575     ItemSize(total_bb, style.FramePadding.y);
1576     if (!ItemAdd(total_bb, id, &bb))
1577         return false;
1578 
1579     // Open on click
1580     bool hovered, held;
1581     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1582     const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1583     bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1584     if ((pressed || g.NavActivateId == id) && !popup_open)
1585     {
1586         OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1587         popup_open = true;
1588     }
1589 
1590     // Render shape
1591     const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1592     const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1593     RenderNavHighlight(bb, id);
1594     if (!(flags & ImGuiComboFlags_NoPreview))
1595         window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1596     if (!(flags & ImGuiComboFlags_NoArrowButton))
1597     {
1598         ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1599         ImU32 text_col = GetColorU32(ImGuiCol_Text);
1600         window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1601         if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1602             RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
1603     }
1604     RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1605 
1606     // Custom preview
1607     if (flags & ImGuiComboFlags_CustomPreview)
1608     {
1609         g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1610         IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1611         preview_value = NULL;
1612     }
1613 
1614     // Render preview and label
1615     if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1616     {
1617         if (g.LogEnabled)
1618             LogSetNextTextDecoration("{", "}");
1619         RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
1620     }
1621     if (label_size.x > 0)
1622         RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
1623 
1624     if (!popup_open)
1625         return false;
1626 
1627     g.NextWindowData.Flags = backup_next_window_data_flags;
1628     return BeginComboPopup(popup_id, bb, flags);
1629 }
1630 
BeginComboPopup(ImGuiID popup_id,const ImRect & bb,ImGuiComboFlags flags)1631 bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1632 {
1633     ImGuiContext& g = *GImGui;
1634     if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
1635     {
1636         g.NextWindowData.ClearFlags();
1637         return false;
1638     }
1639 
1640     // Set popup size
1641     float w = bb.GetWidth();
1642     if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1643     {
1644         g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1645     }
1646     else
1647     {
1648         if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1649             flags |= ImGuiComboFlags_HeightRegular;
1650         IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1651         int popup_max_height_in_items = -1;
1652         if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
1653         else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
1654         else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
1655         SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1656     }
1657 
1658     // This is essentially a specialized version of BeginPopupEx()
1659     char name[16];
1660     ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
1661 
1662     // Set position given a custom constraint (peak into expected window size so we can position it)
1663     // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1664     // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1665     if (ImGuiWindow* popup_window = FindWindowByName(name))
1666         if (popup_window->WasActive)
1667         {
1668             // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1669             ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
1670             popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1671             ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
1672             ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
1673             SetNextWindowPos(pos);
1674         }
1675 
1676     // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1677     ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1678     PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text
1679     bool ret = Begin(name, NULL, window_flags);
1680     PopStyleVar();
1681     if (!ret)
1682     {
1683         EndPopup();
1684         IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
1685         return false;
1686     }
1687     return true;
1688 }
1689 
EndCombo()1690 void ImGui::EndCombo()
1691 {
1692     EndPopup();
1693 }
1694 
1695 // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1696 // (Experimental, see GitHub issues: #1658, #4168)
BeginComboPreview()1697 bool ImGui::BeginComboPreview()
1698 {
1699     ImGuiContext& g = *GImGui;
1700     ImGuiWindow* window = g.CurrentWindow;
1701     ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1702 
1703     if (window->SkipItems || !window->ClipRect.Overlaps(g.LastItemData.Rect)) // FIXME: Because we don't have a ImGuiItemStatusFlags_Visible flag to test last ItemAdd() result
1704         return false;
1705     IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag?
1706     if (!window->ClipRect.Contains(preview_data->PreviewRect)) // Narrower test (optional)
1707         return false;
1708 
1709     // FIXME: This could be contained in a PushWorkRect() api
1710     preview_data->BackupCursorPos = window->DC.CursorPos;
1711     preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1712     preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1713     preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1714     preview_data->BackupLayout = window->DC.LayoutType;
1715     window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1716     window->DC.CursorMaxPos = window->DC.CursorPos;
1717     window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1718     PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
1719 
1720     return true;
1721 }
1722 
EndComboPreview()1723 void ImGui::EndComboPreview()
1724 {
1725     ImGuiContext& g = *GImGui;
1726     ImGuiWindow* window = g.CurrentWindow;
1727     ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1728 
1729     // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
1730     ImDrawList* draw_list = window->DrawList;
1731     if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
1732         if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1733         {
1734             draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
1735             draw_list->_TryMergeDrawCmds();
1736         }
1737     PopClipRect();
1738     window->DC.CursorPos = preview_data->BackupCursorPos;
1739     window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
1740     window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1741     window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1742     window->DC.LayoutType = preview_data->BackupLayout;
1743     preview_data->PreviewRect = ImRect();
1744 }
1745 
1746 // Getter for the old Combo() API: const char*[]
Items_ArrayGetter(void * data,int idx,const char ** out_text)1747 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
1748 {
1749     const char* const* items = (const char* const*)data;
1750     if (out_text)
1751         *out_text = items[idx];
1752     return true;
1753 }
1754 
1755 // Getter for the old Combo() API: "item1\0item2\0item3\0"
Items_SingleStringGetter(void * data,int idx,const char ** out_text)1756 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
1757 {
1758     // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
1759     const char* items_separated_by_zeros = (const char*)data;
1760     int items_count = 0;
1761     const char* p = items_separated_by_zeros;
1762     while (*p)
1763     {
1764         if (idx == items_count)
1765             break;
1766         p += strlen(p) + 1;
1767         items_count++;
1768     }
1769     if (!*p)
1770         return false;
1771     if (out_text)
1772         *out_text = p;
1773     return true;
1774 }
1775 
1776 // 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)1777 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)
1778 {
1779     ImGuiContext& g = *GImGui;
1780 
1781     // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1782     const char* preview_value = NULL;
1783     if (*current_item >= 0 && *current_item < items_count)
1784         items_getter(data, *current_item, &preview_value);
1785 
1786     // 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.
1787     if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1788         SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1789 
1790     if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1791         return false;
1792 
1793     // Display items
1794     // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1795     bool value_changed = false;
1796     for (int i = 0; i < items_count; i++)
1797     {
1798         PushID((void*)(intptr_t)i);
1799         const bool item_selected = (i == *current_item);
1800         const char* item_text;
1801         if (!items_getter(data, i, &item_text))
1802             item_text = "*Unknown item*";
1803         if (Selectable(item_text, item_selected))
1804         {
1805             value_changed = true;
1806             *current_item = i;
1807         }
1808         if (item_selected)
1809             SetItemDefaultFocus();
1810         PopID();
1811     }
1812 
1813     EndCombo();
1814 
1815     if (value_changed)
1816         MarkItemEdited(g.LastItemData.ID);
1817 
1818     return value_changed;
1819 }
1820 
1821 // 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)1822 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1823 {
1824     const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
1825     return value_changed;
1826 }
1827 
1828 // 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)1829 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1830 {
1831     int items_count = 0;
1832     const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
1833     while (*p)
1834     {
1835         p += strlen(p) + 1;
1836         items_count++;
1837     }
1838     bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
1839     return value_changed;
1840 }
1841 
1842 //-------------------------------------------------------------------------
1843 // [SECTION] Data Type and Data Formatting Helpers [Internal]
1844 //-------------------------------------------------------------------------
1845 // - PatchFormatStringFloatToInt()
1846 // - DataTypeGetInfo()
1847 // - DataTypeFormatString()
1848 // - DataTypeApplyOp()
1849 // - DataTypeApplyOpFromText()
1850 // - DataTypeClamp()
1851 // - GetMinimumStepAtDecimalPrecision
1852 // - RoundScalarWithFormat<>()
1853 //-------------------------------------------------------------------------
1854 
1855 static const ImGuiDataTypeInfo GDataTypeInfo[] =
1856 {
1857     { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
1858     { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
1859     { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
1860     { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
1861     { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
1862     { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
1863 #ifdef _MSC_VER
1864     { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
1865     { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
1866 #else
1867     { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
1868     { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
1869 #endif
1870     { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
1871     { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
1872 };
1873 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1874 
1875 // 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".
1876 // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
1877 // 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)1878 static const char* PatchFormatStringFloatToInt(const char* fmt)
1879 {
1880     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.
1881         return "%d";
1882     const char* fmt_start = ImParseFormatFindStart(fmt);    // Find % (if any, and ignore %%)
1883     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).
1884     if (fmt_end > fmt_start && fmt_end[-1] == 'f')
1885     {
1886 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1887         if (fmt_start == fmt && fmt_end[0] == 0)
1888             return "%d";
1889         ImGuiContext& g = *GImGui;
1890         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.
1891         return g.TempBuffer;
1892 #else
1893         IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
1894 #endif
1895     }
1896     return fmt;
1897 }
1898 
DataTypeGetInfo(ImGuiDataType data_type)1899 const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
1900 {
1901     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
1902     return &GDataTypeInfo[data_type];
1903 }
1904 
DataTypeFormatString(char * buf,int buf_size,ImGuiDataType data_type,const void * p_data,const char * format)1905 int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
1906 {
1907     // Signedness doesn't matter when pushing integer arguments
1908     if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
1909         return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
1910     if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1911         return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
1912     if (data_type == ImGuiDataType_Float)
1913         return ImFormatString(buf, buf_size, format, *(const float*)p_data);
1914     if (data_type == ImGuiDataType_Double)
1915         return ImFormatString(buf, buf_size, format, *(const double*)p_data);
1916     if (data_type == ImGuiDataType_S8)
1917         return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
1918     if (data_type == ImGuiDataType_U8)
1919         return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
1920     if (data_type == ImGuiDataType_S16)
1921         return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
1922     if (data_type == ImGuiDataType_U16)
1923         return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
1924     IM_ASSERT(0);
1925     return 0;
1926 }
1927 
DataTypeApplyOp(ImGuiDataType data_type,int op,void * output,const void * arg1,const void * arg2)1928 void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
1929 {
1930     IM_ASSERT(op == '+' || op == '-');
1931     switch (data_type)
1932     {
1933         case ImGuiDataType_S8:
1934             if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
1935             if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
1936             return;
1937         case ImGuiDataType_U8:
1938             if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
1939             if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
1940             return;
1941         case ImGuiDataType_S16:
1942             if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
1943             if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
1944             return;
1945         case ImGuiDataType_U16:
1946             if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
1947             if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
1948             return;
1949         case ImGuiDataType_S32:
1950             if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
1951             if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
1952             return;
1953         case ImGuiDataType_U32:
1954             if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
1955             if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
1956             return;
1957         case ImGuiDataType_S64:
1958             if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
1959             if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
1960             return;
1961         case ImGuiDataType_U64:
1962             if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
1963             if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
1964             return;
1965         case ImGuiDataType_Float:
1966             if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
1967             if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
1968             return;
1969         case ImGuiDataType_Double:
1970             if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
1971             if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
1972             return;
1973         case ImGuiDataType_COUNT: break;
1974     }
1975     IM_ASSERT(0);
1976 }
1977 
1978 // User can input math operators (e.g. +100) to edit a numerical values.
1979 // 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 * p_data,const char * format)1980 bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format)
1981 {
1982     while (ImCharIsBlankA(*buf))
1983         buf++;
1984 
1985     // We don't support '-' op because it would conflict with inputing negative value.
1986     // Instead you can use +-100 to subtract from an existing value
1987     char op = buf[0];
1988     if (op == '+' || op == '*' || op == '/')
1989     {
1990         buf++;
1991         while (ImCharIsBlankA(*buf))
1992             buf++;
1993     }
1994     else
1995     {
1996         op = 0;
1997     }
1998     if (!buf[0])
1999         return false;
2000 
2001     // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2002     const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2003     ImGuiDataTypeTempStorage data_backup;
2004     memcpy(&data_backup, p_data, type_info->Size);
2005 
2006     if (format == NULL)
2007         format = type_info->ScanFmt;
2008 
2009     // FIXME-LEGACY: The aim is to remove those operators and write a proper expression evaluator at some point..
2010     int arg1i = 0;
2011     if (data_type == ImGuiDataType_S32)
2012     {
2013         int* v = (int*)p_data;
2014         int arg0i = *v;
2015         float arg1f = 0.0f;
2016         if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
2017             return false;
2018         // 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
2019         if (op == '+')      { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); }                   // Add (use "+-" to subtract)
2020         else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); }                   // Multiply
2021         else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }  // Divide
2022         else                { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; }                           // Assign constant
2023     }
2024     else if (data_type == ImGuiDataType_Float)
2025     {
2026         // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
2027         format = "%f";
2028         float* v = (float*)p_data;
2029         float arg0f = *v, arg1f = 0.0f;
2030         if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
2031             return false;
2032         if (sscanf(buf, format, &arg1f) < 1)
2033             return false;
2034         if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
2035         else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
2036         else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
2037         else                { *v = arg1f; }                            // Assign constant
2038     }
2039     else if (data_type == ImGuiDataType_Double)
2040     {
2041         format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
2042         double* v = (double*)p_data;
2043         double arg0f = *v, arg1f = 0.0;
2044         if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
2045             return false;
2046         if (sscanf(buf, format, &arg1f) < 1)
2047             return false;
2048         if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
2049         else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
2050         else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
2051         else                { *v = arg1f; }                            // Assign constant
2052     }
2053     else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2054     {
2055         // All other types assign constant
2056         // We don't bother handling support for legacy operators since they are a little too crappy. Instead we will later implement a proper expression evaluator in the future.
2057         if (sscanf(buf, format, p_data) < 1)
2058             return false;
2059     }
2060     else
2061     {
2062         // Small types need a 32-bit buffer to receive the result from scanf()
2063         int v32;
2064         if (sscanf(buf, format, &v32) < 1)
2065             return false;
2066         if (data_type == ImGuiDataType_S8)
2067             *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
2068         else if (data_type == ImGuiDataType_U8)
2069             *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
2070         else if (data_type == ImGuiDataType_S16)
2071             *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
2072         else if (data_type == ImGuiDataType_U16)
2073             *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
2074         else
2075             IM_ASSERT(0);
2076     }
2077 
2078     return memcmp(&data_backup, p_data, type_info->Size) != 0;
2079 }
2080 
2081 template<typename T>
DataTypeCompareT(const T * lhs,const T * rhs)2082 static int DataTypeCompareT(const T* lhs, const T* rhs)
2083 {
2084     if (*lhs < *rhs) return -1;
2085     if (*lhs > *rhs) return +1;
2086     return 0;
2087 }
2088 
DataTypeCompare(ImGuiDataType data_type,const void * arg_1,const void * arg_2)2089 int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2090 {
2091     switch (data_type)
2092     {
2093     case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
2094     case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
2095     case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
2096     case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
2097     case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
2098     case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
2099     case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
2100     case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
2101     case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
2102     case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
2103     case ImGuiDataType_COUNT:  break;
2104     }
2105     IM_ASSERT(0);
2106     return 0;
2107 }
2108 
2109 template<typename T>
DataTypeClampT(T * v,const T * v_min,const T * v_max)2110 static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2111 {
2112     // Clamp, both sides are optional, return true if modified
2113     if (v_min && *v < *v_min) { *v = *v_min; return true; }
2114     if (v_max && *v > *v_max) { *v = *v_max; return true; }
2115     return false;
2116 }
2117 
DataTypeClamp(ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max)2118 bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2119 {
2120     switch (data_type)
2121     {
2122     case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);
2123     case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);
2124     case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
2125     case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
2126     case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
2127     case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
2128     case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
2129     case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
2130     case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
2131     case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
2132     case ImGuiDataType_COUNT:  break;
2133     }
2134     IM_ASSERT(0);
2135     return false;
2136 }
2137 
GetMinimumStepAtDecimalPrecision(int decimal_precision)2138 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2139 {
2140     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 };
2141     if (decimal_precision < 0)
2142         return FLT_MIN;
2143     return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
2144 }
2145 
2146 template<typename TYPE>
ImAtoi(const char * src,TYPE * output)2147 static const char* ImAtoi(const char* src, TYPE* output)
2148 {
2149     int negative = 0;
2150     if (*src == '-') { negative = 1; src++; }
2151     if (*src == '+') { src++; }
2152     TYPE v = 0;
2153     while (*src >= '0' && *src <= '9')
2154         v = (v * 10) + (*src++ - '0');
2155     *output = negative ? -v : v;
2156     return src;
2157 }
2158 
2159 // Sanitize format
2160 // - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
2161 // - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
SanitizeFormatString(const char * fmt,char * fmt_out,size_t fmt_out_size)2162 static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size)
2163 {
2164     IM_UNUSED(fmt_out_size);
2165     const char* fmt_end = ImParseFormatFindEnd(fmt);
2166     IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
2167     while (fmt < fmt_end)
2168     {
2169         char c = *(fmt++);
2170         if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
2171             *(fmt_out++) = c;
2172     }
2173     *fmt_out = 0; // Zero-terminate
2174 }
2175 
2176 template<typename TYPE, typename SIGNEDTYPE>
2177 TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2178 {
2179     const char* fmt_start = ImParseFormatFindStart(format);
2180     if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2181         return v;
2182 
2183     // Sanitize format
2184     char fmt_sanitized[32];
2185     SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
2186     fmt_start = fmt_sanitized;
2187 
2188     // Format value with our rounding, and read back
2189     char v_str[64];
2190     ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
2191     const char* p = v_str;
2192     while (*p == ' ')
2193         p++;
2194     if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2195         v = (TYPE)ImAtof(p);
2196     else
2197         ImAtoi(p, (SIGNEDTYPE*)&v);
2198     return v;
2199 }
2200 
2201 //-------------------------------------------------------------------------
2202 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2203 //-------------------------------------------------------------------------
2204 // - DragBehaviorT<>() [Internal]
2205 // - DragBehavior() [Internal]
2206 // - DragScalar()
2207 // - DragScalarN()
2208 // - DragFloat()
2209 // - DragFloat2()
2210 // - DragFloat3()
2211 // - DragFloat4()
2212 // - DragFloatRange2()
2213 // - DragInt()
2214 // - DragInt2()
2215 // - DragInt3()
2216 // - DragInt4()
2217 // - DragIntRange2()
2218 //-------------------------------------------------------------------------
2219 
2220 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2221 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,ImGuiSliderFlags flags)2222 bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2223 {
2224     ImGuiContext& g = *GImGui;
2225     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2226     const bool is_clamped = (v_min < v_max);
2227     const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2228     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2229 
2230     // Default tweak speed
2231     if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
2232         v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2233 
2234     // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2235     float adjust_delta = 0.0f;
2236     if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2237     {
2238         adjust_delta = g.IO.MouseDelta[axis];
2239         if (g.IO.KeyAlt)
2240             adjust_delta *= 1.0f / 100.0f;
2241         if (g.IO.KeyShift)
2242             adjust_delta *= 10.0f;
2243     }
2244     else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2245     {
2246         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2247         adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
2248         v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
2249     }
2250     adjust_delta *= v_speed;
2251 
2252     // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2253     if (axis == ImGuiAxis_Y)
2254         adjust_delta = -adjust_delta;
2255 
2256     // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2257     if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2258         adjust_delta /= (float)(v_max - v_min);
2259 
2260     // Clear current value on activation
2261     // 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.
2262     bool is_just_activated = g.ActiveIdIsJustActivated;
2263     bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
2264     if (is_just_activated || is_already_past_limits_and_pushing_outward)
2265     {
2266         g.DragCurrentAccum = 0.0f;
2267         g.DragCurrentAccumDirty = false;
2268     }
2269     else if (adjust_delta != 0.0f)
2270     {
2271         g.DragCurrentAccum += adjust_delta;
2272         g.DragCurrentAccumDirty = true;
2273     }
2274 
2275     if (!g.DragCurrentAccumDirty)
2276         return false;
2277 
2278     TYPE v_cur = *v;
2279     FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2280 
2281     float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2282     const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2283     if (is_logarithmic)
2284     {
2285         // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
2286         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2287         logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2288 
2289         // Convert to parametric space, apply delta, convert back
2290         float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2291         float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2292         v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2293         v_old_ref_for_accum_remainder = v_old_parametric;
2294     }
2295     else
2296     {
2297         v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2298     }
2299 
2300     // Round to user desired precision based on format string
2301     if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
2302         v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
2303 
2304     // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2305     g.DragCurrentAccumDirty = false;
2306     if (is_logarithmic)
2307     {
2308         // Convert to parametric space, apply delta, convert back
2309         float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2310         g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2311     }
2312     else
2313     {
2314         g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2315     }
2316 
2317     // Lose zero sign for float/double
2318     if (v_cur == (TYPE)-0)
2319         v_cur = (TYPE)0;
2320 
2321     // Clamp values (+ handle overflow/wrap-around for integer types)
2322     if (*v != v_cur && is_clamped)
2323     {
2324         if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2325             v_cur = v_min;
2326         if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2327             v_cur = v_max;
2328     }
2329 
2330     // Apply result
2331     if (*v == v_cur)
2332         return false;
2333     *v = v_cur;
2334     return true;
2335 }
2336 
DragBehavior(ImGuiID id,ImGuiDataType data_type,void * p_v,float v_speed,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2337 bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2338 {
2339     // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2340     IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
2341 
2342     ImGuiContext& g = *GImGui;
2343     if (g.ActiveId == id)
2344     {
2345         if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2346             ClearActiveID();
2347         else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2348             ClearActiveID();
2349     }
2350     if (g.ActiveId != id)
2351         return false;
2352     if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2353         return false;
2354 
2355     switch (data_type)
2356     {
2357     case ImGuiDataType_S8:     { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN,  p_max ? *(const ImS8*)p_max  : IM_S8_MAX,  format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
2358     case ImGuiDataType_U8:     { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN,  p_max ? *(const ImU8*)p_max  : IM_U8_MAX,  format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
2359     case ImGuiDataType_S16:    { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
2360     case ImGuiDataType_U16:    { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
2361     case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)p_v,  v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags);
2362     case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)p_v,  v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags);
2363     case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)p_v,  v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags);
2364     case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)p_v,  v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags);
2365     case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)p_v,  v_speed, p_min ? *(const float* )p_min : -FLT_MAX,   p_max ? *(const float* )p_max : FLT_MAX,    format, flags);
2366     case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX,   p_max ? *(const double*)p_max : DBL_MAX,    format, flags);
2367     case ImGuiDataType_COUNT:  break;
2368     }
2369     IM_ASSERT(0);
2370     return false;
2371 }
2372 
2373 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.
2374 // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
DragScalar(const char * label,ImGuiDataType data_type,void * p_data,float v_speed,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2375 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2376 {
2377     ImGuiWindow* window = GetCurrentWindow();
2378     if (window->SkipItems)
2379         return false;
2380 
2381     ImGuiContext& g = *GImGui;
2382     const ImGuiStyle& style = g.Style;
2383     const ImGuiID id = window->GetID(label);
2384     const float w = CalcItemWidth();
2385 
2386     const ImVec2 label_size = CalcTextSize(label, NULL, true);
2387     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2388     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));
2389 
2390     const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2391     ItemSize(total_bb, style.FramePadding.y);
2392     if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0))
2393         return false;
2394 
2395     // Default format string when passing NULL
2396     if (format == NULL)
2397         format = DataTypeGetInfo(data_type)->PrintFmt;
2398     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
2399         format = PatchFormatStringFloatToInt(format);
2400 
2401     // Tabbing or CTRL-clicking on Drag turns it into an InputText
2402     const bool hovered = ItemHoverable(frame_bb, id);
2403     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2404     if (!temp_input_is_active)
2405     {
2406         const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0;
2407         const bool clicked = (hovered && g.IO.MouseClicked[0]);
2408         const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]);
2409         if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id)
2410         {
2411             SetActiveID(id, window);
2412             SetFocusID(id, window);
2413             FocusWindow(window);
2414             g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2415             if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id))
2416                 temp_input_is_active = true;
2417         }
2418         // Experimental: simple click (without moving) turns Drag into an InputText
2419         // FIXME: Currently polling ImGuiConfigFlags_IsTouchScreen, may either poll an hypothetical ImGuiBackendFlags_HasKeyboard and/or an explicit drag settings.
2420         if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2421             if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2422             {
2423                 g.NavInputId = id;
2424                 temp_input_is_active = true;
2425             }
2426     }
2427 
2428     if (temp_input_is_active)
2429     {
2430         // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
2431         const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0);
2432         return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
2433     }
2434 
2435     // Draw frame
2436     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2437     RenderNavHighlight(frame_bb, id);
2438     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
2439 
2440     // Drag behavior
2441     const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
2442     if (value_changed)
2443         MarkItemEdited(id);
2444 
2445     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2446     char value_buf[64];
2447     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
2448     if (g.LogEnabled)
2449         LogSetNextTextDecoration("{", "}");
2450     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
2451 
2452     if (label_size.x > 0.0f)
2453         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
2454 
2455     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
2456     return value_changed;
2457 }
2458 
DragScalarN(const char * label,ImGuiDataType data_type,void * p_data,int components,float v_speed,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2459 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2460 {
2461     ImGuiWindow* window = GetCurrentWindow();
2462     if (window->SkipItems)
2463         return false;
2464 
2465     ImGuiContext& g = *GImGui;
2466     bool value_changed = false;
2467     BeginGroup();
2468     PushID(label);
2469     PushMultiItemsWidths(components, CalcItemWidth());
2470     size_t type_size = GDataTypeInfo[data_type].Size;
2471     for (int i = 0; i < components; i++)
2472     {
2473         PushID(i);
2474         if (i > 0)
2475             SameLine(0, g.Style.ItemInnerSpacing.x);
2476         value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
2477         PopID();
2478         PopItemWidth();
2479         p_data = (void*)((char*)p_data + type_size);
2480     }
2481     PopID();
2482 
2483     const char* label_end = FindRenderedTextEnd(label);
2484     if (label != label_end)
2485     {
2486         SameLine(0, g.Style.ItemInnerSpacing.x);
2487         TextEx(label, label_end);
2488     }
2489 
2490     EndGroup();
2491     return value_changed;
2492 }
2493 
DragFloat(const char * label,float * v,float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2494 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2495 {
2496     return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
2497 }
2498 
DragFloat2(const char * label,float v[2],float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2499 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2500 {
2501     return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
2502 }
2503 
DragFloat3(const char * label,float v[3],float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2504 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2505 {
2506     return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
2507 }
2508 
DragFloat4(const char * label,float v[4],float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2509 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2510 {
2511     return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
2512 }
2513 
2514 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
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,ImGuiSliderFlags flags)2515 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, ImGuiSliderFlags flags)
2516 {
2517     ImGuiWindow* window = GetCurrentWindow();
2518     if (window->SkipItems)
2519         return false;
2520 
2521     ImGuiContext& g = *GImGui;
2522     PushID(label);
2523     BeginGroup();
2524     PushMultiItemsWidths(2, CalcItemWidth());
2525 
2526     float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2527     float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2528     ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2529     bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
2530     PopItemWidth();
2531     SameLine(0, g.Style.ItemInnerSpacing.x);
2532 
2533     float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2534     float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2535     ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2536     value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
2537     PopItemWidth();
2538     SameLine(0, g.Style.ItemInnerSpacing.x);
2539 
2540     TextEx(label, FindRenderedTextEnd(label));
2541     EndGroup();
2542     PopID();
2543 
2544     return value_changed;
2545 }
2546 
2547 // 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,ImGuiSliderFlags flags)2548 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2549 {
2550     return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
2551 }
2552 
DragInt2(const char * label,int v[2],float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2553 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2554 {
2555     return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
2556 }
2557 
DragInt3(const char * label,int v[3],float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2558 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2559 {
2560     return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
2561 }
2562 
DragInt4(const char * label,int v[4],float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2563 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2564 {
2565     return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
2566 }
2567 
2568 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
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,ImGuiSliderFlags flags)2569 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, ImGuiSliderFlags flags)
2570 {
2571     ImGuiWindow* window = GetCurrentWindow();
2572     if (window->SkipItems)
2573         return false;
2574 
2575     ImGuiContext& g = *GImGui;
2576     PushID(label);
2577     BeginGroup();
2578     PushMultiItemsWidths(2, CalcItemWidth());
2579 
2580     int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2581     int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2582     ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2583     bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
2584     PopItemWidth();
2585     SameLine(0, g.Style.ItemInnerSpacing.x);
2586 
2587     int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2588     int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2589     ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2590     value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
2591     PopItemWidth();
2592     SameLine(0, g.Style.ItemInnerSpacing.x);
2593 
2594     TextEx(label, FindRenderedTextEnd(label));
2595     EndGroup();
2596     PopID();
2597 
2598     return value_changed;
2599 }
2600 
2601 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2602 
2603 // Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
DragScalar(const char * label,ImGuiDataType data_type,void * p_data,float v_speed,const void * p_min,const void * p_max,const char * format,float power)2604 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
2605 {
2606     ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
2607     if (power != 1.0f)
2608     {
2609         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
2610         IM_ASSERT(p_min != NULL && p_max != NULL);  // When using a power curve the drag needs to have known bounds
2611         drag_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
2612     }
2613     return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags);
2614 }
2615 
DragScalarN(const char * label,ImGuiDataType data_type,void * p_data,int components,float v_speed,const void * p_min,const void * p_max,const char * format,float power)2616 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
2617 {
2618     ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
2619     if (power != 1.0f)
2620     {
2621         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
2622         IM_ASSERT(p_min != NULL && p_max != NULL);  // When using a power curve the drag needs to have known bounds
2623         drag_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
2624     }
2625     return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags);
2626 }
2627 
2628 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2629 
2630 //-------------------------------------------------------------------------
2631 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2632 //-------------------------------------------------------------------------
2633 // - ScaleRatioFromValueT<> [Internal]
2634 // - ScaleValueFromRatioT<> [Internal]
2635 // - SliderBehaviorT<>() [Internal]
2636 // - SliderBehavior() [Internal]
2637 // - SliderScalar()
2638 // - SliderScalarN()
2639 // - SliderFloat()
2640 // - SliderFloat2()
2641 // - SliderFloat3()
2642 // - SliderFloat4()
2643 // - SliderAngle()
2644 // - SliderInt()
2645 // - SliderInt2()
2646 // - SliderInt3()
2647 // - SliderInt4()
2648 // - VSliderScalar()
2649 // - VSliderFloat()
2650 // - VSliderInt()
2651 //-------------------------------------------------------------------------
2652 
2653 // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2654 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
ScaleRatioFromValueT(ImGuiDataType data_type,TYPE v,TYPE v_min,TYPE v_max,bool is_logarithmic,float logarithmic_zero_epsilon,float zero_deadzone_halfsize)2655 float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2656 {
2657     if (v_min == v_max)
2658         return 0.0f;
2659     IM_UNUSED(data_type);
2660 
2661     const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2662     if (is_logarithmic)
2663     {
2664         bool flipped = v_max < v_min;
2665 
2666         if (flipped) // Handle the case where the range is backwards
2667             ImSwap(v_min, v_max);
2668 
2669         // Fudge min/max to avoid getting close to log(0)
2670         FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2671         FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2672 
2673         // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2674         if ((v_min == 0.0f) && (v_max < 0.0f))
2675             v_min_fudged = -logarithmic_zero_epsilon;
2676         else if ((v_max == 0.0f) && (v_min < 0.0f))
2677             v_max_fudged = -logarithmic_zero_epsilon;
2678 
2679         float result;
2680 
2681         if (v_clamped <= v_min_fudged)
2682             result = 0.0f; // Workaround for values that are in-range but below our fudge
2683         else if (v_clamped >= v_max_fudged)
2684             result = 1.0f; // Workaround for values that are in-range but above our fudge
2685         else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
2686         {
2687             float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space.  There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine)
2688             float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2689             float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2690             if (v == 0.0f)
2691                 result = zero_point_center; // Special case for exactly zero
2692             else if (v < 0.0f)
2693                 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
2694             else
2695                 result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R));
2696         }
2697         else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2698             result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
2699         else
2700             result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
2701 
2702         return flipped ? (1.0f - result) : result;
2703     }
2704 
2705     // Linear slider
2706     return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
2707 }
2708 
2709 // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
2710 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2711 TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2712 {
2713     if (v_min == v_max)
2714         return v_min;
2715     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2716 
2717     TYPE result;
2718     if (is_logarithmic)
2719     {
2720         // We special-case the extents because otherwise our fudging can lead to "mathematically correct" but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value
2721         if (t <= 0.0f)
2722             result = v_min;
2723         else if (t >= 1.0f)
2724             result = v_max;
2725         else
2726         {
2727             bool flipped = v_max < v_min; // Check if range is "backwards"
2728 
2729             // Fudge min/max to avoid getting silly results close to zero
2730             FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2731             FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2732 
2733             if (flipped)
2734                 ImSwap(v_min_fudged, v_max_fudged);
2735 
2736             // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2737             if ((v_max == 0.0f) && (v_min < 0.0f))
2738                 v_max_fudged = -logarithmic_zero_epsilon;
2739 
2740             float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
2741 
2742             if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
2743             {
2744                 float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
2745                 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2746                 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2747                 if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
2748                     result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
2749                 else if (t_with_flip < zero_point_center)
2750                     result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
2751                 else
2752                     result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
2753             }
2754             else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2755                 result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
2756             else
2757                 result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
2758         }
2759     }
2760     else
2761     {
2762         // Linear slider
2763         if (is_floating_point)
2764         {
2765             result = ImLerp(v_min, v_max, t);
2766         }
2767         else
2768         {
2769             // - For integer values we want the clicking position to match the grab box so we round above
2770             //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2771             // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64
2772             //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
2773             if (t < 1.0)
2774             {
2775                 FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
2776                 result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
2777             }
2778             else
2779             {
2780                 result = v_max;
2781             }
2782         }
2783     }
2784 
2785     return result;
2786 }
2787 
2788 // FIXME: Move more of the code into SliderBehavior()
2789 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,ImGuiSliderFlags flags,ImRect * out_grab_bb)2790 bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2791 {
2792     ImGuiContext& g = *GImGui;
2793     const ImGuiStyle& style = g.Style;
2794 
2795     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2796     const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2797     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2798 
2799     const float grab_padding = 2.0f;
2800     const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
2801     float grab_sz = style.GrabMinSize;
2802     SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
2803     if (!is_floating_point && v_range >= 0)                                             // v_range < 0 may happen on integer overflows
2804         grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize);  // For integer sliders: if possible have the grab size represent 1 unit
2805     grab_sz = ImMin(grab_sz, slider_sz);
2806     const float slider_usable_sz = slider_sz - grab_sz;
2807     const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
2808     const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
2809 
2810     float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2811     float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
2812     if (is_logarithmic)
2813     {
2814         // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
2815         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2816         logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2817         zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
2818     }
2819 
2820     // Process interacting with the slider
2821     bool value_changed = false;
2822     if (g.ActiveId == id)
2823     {
2824         bool set_new_value = false;
2825         float clicked_t = 0.0f;
2826         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
2827         {
2828             if (!g.IO.MouseDown[0])
2829             {
2830                 ClearActiveID();
2831             }
2832             else
2833             {
2834                 const float mouse_abs_pos = g.IO.MousePos[axis];
2835                 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
2836                 if (axis == ImGuiAxis_Y)
2837                     clicked_t = 1.0f - clicked_t;
2838                 set_new_value = true;
2839             }
2840         }
2841         else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2842         {
2843             if (g.ActiveIdIsJustActivated)
2844             {
2845                 g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
2846                 g.SliderCurrentAccumDirty = false;
2847             }
2848 
2849             const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
2850             float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y;
2851             if (input_delta != 0.0f)
2852             {
2853                 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2854                 if (decimal_precision > 0)
2855                 {
2856                     input_delta /= 100.0f;    // Gamepad/keyboard tweak speeds in % of slider bounds
2857                     if (IsNavInputDown(ImGuiNavInput_TweakSlow))
2858                         input_delta /= 10.0f;
2859                 }
2860                 else
2861                 {
2862                     if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
2863                         input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
2864                     else
2865                         input_delta /= 100.0f;
2866                 }
2867                 if (IsNavInputDown(ImGuiNavInput_TweakFast))
2868                     input_delta *= 10.0f;
2869 
2870                 g.SliderCurrentAccum += input_delta;
2871                 g.SliderCurrentAccumDirty = true;
2872             }
2873 
2874             float delta = g.SliderCurrentAccum;
2875             if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2876             {
2877                 ClearActiveID();
2878             }
2879             else if (g.SliderCurrentAccumDirty)
2880             {
2881                 clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2882 
2883                 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
2884                 {
2885                     set_new_value = false;
2886                     g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
2887                 }
2888                 else
2889                 {
2890                     set_new_value = true;
2891                     float old_clicked_t = clicked_t;
2892                     clicked_t = ImSaturate(clicked_t + delta);
2893 
2894                     // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
2895                     TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2896                     if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
2897                         v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
2898                     float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2899 
2900                     if (delta > 0)
2901                         g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
2902                     else
2903                         g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
2904                 }
2905 
2906                 g.SliderCurrentAccumDirty = false;
2907             }
2908         }
2909 
2910         if (set_new_value)
2911         {
2912             TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2913 
2914             // Round to user desired precision based on format string
2915             if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
2916                 v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
2917 
2918             // Apply result
2919             if (*v != v_new)
2920             {
2921                 *v = v_new;
2922                 value_changed = true;
2923             }
2924         }
2925     }
2926 
2927     if (slider_sz < 1.0f)
2928     {
2929         *out_grab_bb = ImRect(bb.Min, bb.Min);
2930     }
2931     else
2932     {
2933         // Output grab position so it can be displayed by the caller
2934         float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2935         if (axis == ImGuiAxis_Y)
2936             grab_t = 1.0f - grab_t;
2937         const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
2938         if (axis == ImGuiAxis_X)
2939             *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);
2940         else
2941             *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);
2942     }
2943 
2944     return value_changed;
2945 }
2946 
2947 // For 32-bit and larger types, slider bounds are limited to half the natural type range.
2948 // 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.
2949 // 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 * p_v,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags,ImRect * out_grab_bb)2950 bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2951 {
2952     // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2953     IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag!  Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
2954 
2955     ImGuiContext& g = *GImGui;
2956     if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2957         return false;
2958 
2959     switch (data_type)
2960     {
2961     case ImGuiDataType_S8:  { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min,  *(const ImS8*)p_max,  format, flags, out_grab_bb); if (r) *(ImS8*)p_v  = (ImS8)v32;  return r; }
2962     case ImGuiDataType_U8:  { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min,  *(const ImU8*)p_max,  format, flags, out_grab_bb); if (r) *(ImU8*)p_v  = (ImU8)v32;  return r; }
2963     case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
2964     case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
2965     case ImGuiDataType_S32:
2966         IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
2967         return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)p_v,  *(const ImS32*)p_min,  *(const ImS32*)p_max,  format, flags, out_grab_bb);
2968     case ImGuiDataType_U32:
2969         IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
2970         return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)p_v,  *(const ImU32*)p_min,  *(const ImU32*)p_max,  format, flags, out_grab_bb);
2971     case ImGuiDataType_S64:
2972         IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
2973         return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)p_v,  *(const ImS64*)p_min,  *(const ImS64*)p_max,  format, flags, out_grab_bb);
2974     case ImGuiDataType_U64:
2975         IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
2976         return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)p_v,  *(const ImU64*)p_min,  *(const ImU64*)p_max,  format, flags, out_grab_bb);
2977     case ImGuiDataType_Float:
2978         IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
2979         return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)p_v,  *(const float*)p_min,  *(const float*)p_max,  format, flags, out_grab_bb);
2980     case ImGuiDataType_Double:
2981         IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
2982         return SliderBehaviorT<double, double, double>(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb);
2983     case ImGuiDataType_COUNT: break;
2984     }
2985     IM_ASSERT(0);
2986     return false;
2987 }
2988 
2989 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
2990 // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
SliderScalar(const char * label,ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2991 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2992 {
2993     ImGuiWindow* window = GetCurrentWindow();
2994     if (window->SkipItems)
2995         return false;
2996 
2997     ImGuiContext& g = *GImGui;
2998     const ImGuiStyle& style = g.Style;
2999     const ImGuiID id = window->GetID(label);
3000     const float w = CalcItemWidth();
3001 
3002     const ImVec2 label_size = CalcTextSize(label, NULL, true);
3003     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
3004     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));
3005 
3006     const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
3007     ItemSize(total_bb, style.FramePadding.y);
3008     if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0))
3009         return false;
3010 
3011     // Default format string when passing NULL
3012     if (format == NULL)
3013         format = DataTypeGetInfo(data_type)->PrintFmt;
3014     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
3015         format = PatchFormatStringFloatToInt(format);
3016 
3017     // Tabbing or CTRL-clicking on Slider turns it into an input box
3018     const bool hovered = ItemHoverable(frame_bb, id);
3019     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
3020     if (!temp_input_is_active)
3021     {
3022         const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0;
3023         const bool clicked = (hovered && g.IO.MouseClicked[0]);
3024         if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id)
3025         {
3026             SetActiveID(id, window);
3027             SetFocusID(id, window);
3028             FocusWindow(window);
3029             g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
3030             if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id))
3031                 temp_input_is_active = true;
3032         }
3033     }
3034 
3035     if (temp_input_is_active)
3036     {
3037         // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
3038         const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
3039         return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
3040     }
3041 
3042     // Draw frame
3043     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3044     RenderNavHighlight(frame_bb, id);
3045     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3046 
3047     // Slider behavior
3048     ImRect grab_bb;
3049     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
3050     if (value_changed)
3051         MarkItemEdited(id);
3052 
3053     // Render grab
3054     if (grab_bb.Max.x > grab_bb.Min.x)
3055         window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3056 
3057     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3058     char value_buf[64];
3059     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3060     if (g.LogEnabled)
3061         LogSetNextTextDecoration("{", "}");
3062     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
3063 
3064     if (label_size.x > 0.0f)
3065         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3066 
3067     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
3068     return value_changed;
3069 }
3070 
3071 // 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,ImGuiSliderFlags flags)3072 bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags)
3073 {
3074     ImGuiWindow* window = GetCurrentWindow();
3075     if (window->SkipItems)
3076         return false;
3077 
3078     ImGuiContext& g = *GImGui;
3079     bool value_changed = false;
3080     BeginGroup();
3081     PushID(label);
3082     PushMultiItemsWidths(components, CalcItemWidth());
3083     size_t type_size = GDataTypeInfo[data_type].Size;
3084     for (int i = 0; i < components; i++)
3085     {
3086         PushID(i);
3087         if (i > 0)
3088             SameLine(0, g.Style.ItemInnerSpacing.x);
3089         value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
3090         PopID();
3091         PopItemWidth();
3092         v = (void*)((char*)v + type_size);
3093     }
3094     PopID();
3095 
3096     const char* label_end = FindRenderedTextEnd(label);
3097     if (label != label_end)
3098     {
3099         SameLine(0, g.Style.ItemInnerSpacing.x);
3100         TextEx(label, label_end);
3101     }
3102 
3103     EndGroup();
3104     return value_changed;
3105 }
3106 
SliderFloat(const char * label,float * v,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3107 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3108 {
3109     return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3110 }
3111 
SliderFloat2(const char * label,float v[2],float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3112 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3113 {
3114     return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
3115 }
3116 
SliderFloat3(const char * label,float v[3],float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3117 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3118 {
3119     return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
3120 }
3121 
SliderFloat4(const char * label,float v[4],float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3122 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3123 {
3124     return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
3125 }
3126 
SliderAngle(const char * label,float * v_rad,float v_degrees_min,float v_degrees_max,const char * format,ImGuiSliderFlags flags)3127 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
3128 {
3129     if (format == NULL)
3130         format = "%.0f deg";
3131     float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
3132     bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
3133     *v_rad = v_deg * (2 * IM_PI) / 360.0f;
3134     return value_changed;
3135 }
3136 
SliderInt(const char * label,int * v,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3137 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3138 {
3139     return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3140 }
3141 
SliderInt2(const char * label,int v[2],int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3142 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3143 {
3144     return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
3145 }
3146 
SliderInt3(const char * label,int v[3],int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3147 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3148 {
3149     return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
3150 }
3151 
SliderInt4(const char * label,int v[4],int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3152 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3153 {
3154     return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
3155 }
3156 
VSliderScalar(const char * label,const ImVec2 & size,ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)3157 bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
3158 {
3159     ImGuiWindow* window = GetCurrentWindow();
3160     if (window->SkipItems)
3161         return false;
3162 
3163     ImGuiContext& g = *GImGui;
3164     const ImGuiStyle& style = g.Style;
3165     const ImGuiID id = window->GetID(label);
3166 
3167     const ImVec2 label_size = CalcTextSize(label, NULL, true);
3168     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3169     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));
3170 
3171     ItemSize(bb, style.FramePadding.y);
3172     if (!ItemAdd(frame_bb, id))
3173         return false;
3174 
3175     // Default format string when passing NULL
3176     if (format == NULL)
3177         format = DataTypeGetInfo(data_type)->PrintFmt;
3178     else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
3179         format = PatchFormatStringFloatToInt(format);
3180 
3181     const bool hovered = ItemHoverable(frame_bb, id);
3182     if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
3183     {
3184         SetActiveID(id, window);
3185         SetFocusID(id, window);
3186         FocusWindow(window);
3187         g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
3188     }
3189 
3190     // Draw frame
3191     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3192     RenderNavHighlight(frame_bb, id);
3193     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3194 
3195     // Slider behavior
3196     ImRect grab_bb;
3197     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
3198     if (value_changed)
3199         MarkItemEdited(id);
3200 
3201     // Render grab
3202     if (grab_bb.Max.y > grab_bb.Min.y)
3203         window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3204 
3205     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3206     // For the vertical slider we allow centered text to overlap the frame padding
3207     char value_buf[64];
3208     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3209     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));
3210     if (label_size.x > 0.0f)
3211         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3212 
3213     return value_changed;
3214 }
3215 
VSliderFloat(const char * label,const ImVec2 & size,float * v,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3216 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3217 {
3218     return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3219 }
3220 
VSliderInt(const char * label,const ImVec2 & size,int * v,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3221 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3222 {
3223     return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3224 }
3225 
3226 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
3227 
3228 // Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
SliderScalar(const char * label,ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max,const char * format,float power)3229 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power)
3230 {
3231     ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
3232     if (power != 1.0f)
3233     {
3234         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
3235         slider_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
3236     }
3237     return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags);
3238 }
3239 
SliderScalarN(const char * label,ImGuiDataType data_type,void * v,int components,const void * v_min,const void * v_max,const char * format,float power)3240 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)
3241 {
3242     ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
3243     if (power != 1.0f)
3244     {
3245         IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
3246         slider_flags |= ImGuiSliderFlags_Logarithmic;   // Fallback for non-asserting paths
3247     }
3248     return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags);
3249 }
3250 
3251 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
3252 
3253 //-------------------------------------------------------------------------
3254 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
3255 //-------------------------------------------------------------------------
3256 // - ImParseFormatFindStart() [Internal]
3257 // - ImParseFormatFindEnd() [Internal]
3258 // - ImParseFormatTrimDecorations() [Internal]
3259 // - ImParseFormatPrecision() [Internal]
3260 // - TempInputTextScalar() [Internal]
3261 // - InputScalar()
3262 // - InputScalarN()
3263 // - InputFloat()
3264 // - InputFloat2()
3265 // - InputFloat3()
3266 // - InputFloat4()
3267 // - InputInt()
3268 // - InputInt2()
3269 // - InputInt3()
3270 // - InputInt4()
3271 // - InputDouble()
3272 //-------------------------------------------------------------------------
3273 
3274 // We don't use strchr() because our strings are usually very short and often start with '%'
ImParseFormatFindStart(const char * fmt)3275 const char* ImParseFormatFindStart(const char* fmt)
3276 {
3277     while (char c = fmt[0])
3278     {
3279         if (c == '%' && fmt[1] != '%')
3280             return fmt;
3281         else if (c == '%')
3282             fmt++;
3283         fmt++;
3284     }
3285     return fmt;
3286 }
3287 
ImParseFormatFindEnd(const char * fmt)3288 const char* ImParseFormatFindEnd(const char* fmt)
3289 {
3290     // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
3291     if (fmt[0] != '%')
3292         return fmt;
3293     const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
3294     const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
3295     for (char c; (c = *fmt) != 0; fmt++)
3296     {
3297         if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
3298             return fmt + 1;
3299         if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
3300             return fmt + 1;
3301     }
3302     return fmt;
3303 }
3304 
3305 // Extract the format out of a format string with leading or trailing decorations
3306 //  fmt = "blah blah"  -> return fmt
3307 //  fmt = "%.3f"       -> return fmt
3308 //  fmt = "hello %.3f" -> return fmt + 6
3309 //  fmt = "%.3f hello" -> return buf written with "%.3f"
ImParseFormatTrimDecorations(const char * fmt,char * buf,size_t buf_size)3310 const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
3311 {
3312     const char* fmt_start = ImParseFormatFindStart(fmt);
3313     if (fmt_start[0] != '%')
3314         return fmt;
3315     const char* fmt_end = ImParseFormatFindEnd(fmt_start);
3316     if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
3317         return fmt_start;
3318     ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
3319     return buf;
3320 }
3321 
3322 // Parse display precision back from the display format string
3323 // 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)3324 int ImParseFormatPrecision(const char* fmt, int default_precision)
3325 {
3326     fmt = ImParseFormatFindStart(fmt);
3327     if (fmt[0] != '%')
3328         return default_precision;
3329     fmt++;
3330     while (*fmt >= '0' && *fmt <= '9')
3331         fmt++;
3332     int precision = INT_MAX;
3333     if (*fmt == '.')
3334     {
3335         fmt = ImAtoi<int>(fmt + 1, &precision);
3336         if (precision < 0 || precision > 99)
3337             precision = default_precision;
3338     }
3339     if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
3340         precision = -1;
3341     if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
3342         precision = -1;
3343     return (precision == INT_MAX) ? default_precision : precision;
3344 }
3345 
3346 // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
3347 // FIXME: Facilitate using this in variety of other situations.
TempInputText(const ImRect & bb,ImGuiID id,const char * label,char * buf,int buf_size,ImGuiInputTextFlags flags)3348 bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
3349 {
3350     // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
3351     // We clear ActiveID on the first frame to allow the InputText() taking it back.
3352     ImGuiContext& g = *GImGui;
3353     const bool init = (g.TempInputId != id);
3354     if (init)
3355         ClearActiveID();
3356 
3357     g.CurrentWindow->DC.CursorPos = bb.Min;
3358     bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
3359     if (init)
3360     {
3361         // First frame we started displaying the InputText widget, we expect it to take the active id.
3362         IM_ASSERT(g.ActiveId == id);
3363         g.TempInputId = g.ActiveId;
3364     }
3365     return value_changed;
3366 }
3367 
3368 // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
3369 // This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
3370 // However this may not be ideal for all uses, as some user code may break on out of bound values.
TempInputScalar(const ImRect & bb,ImGuiID id,const char * label,ImGuiDataType data_type,void * p_data,const char * format,const void * p_clamp_min,const void * p_clamp_max)3371 bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)
3372 {
3373     ImGuiContext& g = *GImGui;
3374 
3375     char fmt_buf[32];
3376     char data_buf[32];
3377     format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
3378     DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
3379     ImStrTrimBlanks(data_buf);
3380 
3381     ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
3382     flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
3383     bool value_changed = false;
3384     if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
3385     {
3386         // Backup old value
3387         size_t data_type_size = DataTypeGetInfo(data_type)->Size;
3388         ImGuiDataTypeTempStorage data_backup;
3389         memcpy(&data_backup, p_data, data_type_size);
3390 
3391         // Apply new value (or operations) then clamp
3392         DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL);
3393         if (p_clamp_min || p_clamp_max)
3394         {
3395             if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
3396                 ImSwap(p_clamp_min, p_clamp_max);
3397             DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
3398         }
3399 
3400         // Only mark as edited if new value is different
3401         value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
3402         if (value_changed)
3403             MarkItemEdited(id);
3404     }
3405     return value_changed;
3406 }
3407 
3408 // Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional.
3409 // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
InputScalar(const char * label,ImGuiDataType data_type,void * p_data,const void * p_step,const void * p_step_fast,const char * format,ImGuiInputTextFlags flags)3410 bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
3411 {
3412     ImGuiWindow* window = GetCurrentWindow();
3413     if (window->SkipItems)
3414         return false;
3415 
3416     ImGuiContext& g = *GImGui;
3417     ImGuiStyle& style = g.Style;
3418 
3419     if (format == NULL)
3420         format = DataTypeGetInfo(data_type)->PrintFmt;
3421 
3422     char buf[64];
3423     DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
3424 
3425     bool value_changed = false;
3426     if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
3427         flags |= ImGuiInputTextFlags_CharsDecimal;
3428     flags |= ImGuiInputTextFlags_AutoSelectAll;
3429     flags |= ImGuiInputTextFlags_NoMarkEdited;  // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
3430 
3431     if (p_step != NULL)
3432     {
3433         const float button_size = GetFrameHeight();
3434 
3435         BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
3436         PushID(label);
3437         SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
3438         if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
3439             value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
3440 
3441         // Step buttons
3442         const ImVec2 backup_frame_padding = style.FramePadding;
3443         style.FramePadding.x = style.FramePadding.y;
3444         ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
3445         if (flags & ImGuiInputTextFlags_ReadOnly)
3446             BeginDisabled(true);
3447         SameLine(0, style.ItemInnerSpacing.x);
3448         if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
3449         {
3450             DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3451             value_changed = true;
3452         }
3453         SameLine(0, style.ItemInnerSpacing.x);
3454         if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
3455         {
3456             DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3457             value_changed = true;
3458         }
3459         if (flags & ImGuiInputTextFlags_ReadOnly)
3460             EndDisabled();
3461 
3462         const char* label_end = FindRenderedTextEnd(label);
3463         if (label != label_end)
3464         {
3465             SameLine(0, style.ItemInnerSpacing.x);
3466             TextEx(label, label_end);
3467         }
3468         style.FramePadding = backup_frame_padding;
3469 
3470         PopID();
3471         EndGroup();
3472     }
3473     else
3474     {
3475         if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
3476             value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
3477     }
3478     if (value_changed)
3479         MarkItemEdited(g.LastItemData.ID);
3480 
3481     return value_changed;
3482 }
3483 
InputScalarN(const char * label,ImGuiDataType data_type,void * p_data,int components,const void * p_step,const void * p_step_fast,const char * format,ImGuiInputTextFlags flags)3484 bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
3485 {
3486     ImGuiWindow* window = GetCurrentWindow();
3487     if (window->SkipItems)
3488         return false;
3489 
3490     ImGuiContext& g = *GImGui;
3491     bool value_changed = false;
3492     BeginGroup();
3493     PushID(label);
3494     PushMultiItemsWidths(components, CalcItemWidth());
3495     size_t type_size = GDataTypeInfo[data_type].Size;
3496     for (int i = 0; i < components; i++)
3497     {
3498         PushID(i);
3499         if (i > 0)
3500             SameLine(0, g.Style.ItemInnerSpacing.x);
3501         value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
3502         PopID();
3503         PopItemWidth();
3504         p_data = (void*)((char*)p_data + type_size);
3505     }
3506     PopID();
3507 
3508     const char* label_end = FindRenderedTextEnd(label);
3509     if (label != label_end)
3510     {
3511         SameLine(0.0f, g.Style.ItemInnerSpacing.x);
3512         TextEx(label, label_end);
3513     }
3514 
3515     EndGroup();
3516     return value_changed;
3517 }
3518 
InputFloat(const char * label,float * v,float step,float step_fast,const char * format,ImGuiInputTextFlags flags)3519 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
3520 {
3521     flags |= ImGuiInputTextFlags_CharsScientific;
3522     return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
3523 }
3524 
InputFloat2(const char * label,float v[2],const char * format,ImGuiInputTextFlags flags)3525 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
3526 {
3527     return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
3528 }
3529 
InputFloat3(const char * label,float v[3],const char * format,ImGuiInputTextFlags flags)3530 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
3531 {
3532     return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
3533 }
3534 
InputFloat4(const char * label,float v[4],const char * format,ImGuiInputTextFlags flags)3535 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
3536 {
3537     return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
3538 }
3539 
InputInt(const char * label,int * v,int step,int step_fast,ImGuiInputTextFlags flags)3540 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
3541 {
3542     // 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.
3543     const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
3544     return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
3545 }
3546 
InputInt2(const char * label,int v[2],ImGuiInputTextFlags flags)3547 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
3548 {
3549     return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
3550 }
3551 
InputInt3(const char * label,int v[3],ImGuiInputTextFlags flags)3552 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
3553 {
3554     return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
3555 }
3556 
InputInt4(const char * label,int v[4],ImGuiInputTextFlags flags)3557 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
3558 {
3559     return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
3560 }
3561 
InputDouble(const char * label,double * v,double step,double step_fast,const char * format,ImGuiInputTextFlags flags)3562 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
3563 {
3564     flags |= ImGuiInputTextFlags_CharsScientific;
3565     return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
3566 }
3567 
3568 //-------------------------------------------------------------------------
3569 // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
3570 //-------------------------------------------------------------------------
3571 // - InputText()
3572 // - InputTextWithHint()
3573 // - InputTextMultiline()
3574 // - InputTextEx() [Internal]
3575 //-------------------------------------------------------------------------
3576 
InputText(const char * label,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3577 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3578 {
3579     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
3580     return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3581 }
3582 
InputTextMultiline(const char * label,char * buf,size_t buf_size,const ImVec2 & size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3583 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3584 {
3585     return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
3586 }
3587 
InputTextWithHint(const char * label,const char * hint,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3588 bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3589 {
3590     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
3591     return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3592 }
3593 
InputTextCalcTextLenAndLineCount(const char * text_begin,const char ** out_text_end)3594 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
3595 {
3596     int line_count = 0;
3597     const char* s = text_begin;
3598     while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
3599         if (c == '\n')
3600             line_count++;
3601     s--;
3602     if (s[0] != '\n' && s[0] != '\r')
3603         line_count++;
3604     *out_text_end = s;
3605     return line_count;
3606 }
3607 
InputTextCalcTextSizeW(const ImWchar * text_begin,const ImWchar * text_end,const ImWchar ** remaining,ImVec2 * out_offset,bool stop_on_new_line)3608 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
3609 {
3610     ImGuiContext& g = *GImGui;
3611     ImFont* font = g.Font;
3612     const float line_height = g.FontSize;
3613     const float scale = line_height / font->FontSize;
3614 
3615     ImVec2 text_size = ImVec2(0, 0);
3616     float line_width = 0.0f;
3617 
3618     const ImWchar* s = text_begin;
3619     while (s < text_end)
3620     {
3621         unsigned int c = (unsigned int)(*s++);
3622         if (c == '\n')
3623         {
3624             text_size.x = ImMax(text_size.x, line_width);
3625             text_size.y += line_height;
3626             line_width = 0.0f;
3627             if (stop_on_new_line)
3628                 break;
3629             continue;
3630         }
3631         if (c == '\r')
3632             continue;
3633 
3634         const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
3635         line_width += char_width;
3636     }
3637 
3638     if (text_size.x < line_width)
3639         text_size.x = line_width;
3640 
3641     if (out_offset)
3642         *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
3643 
3644     if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
3645         text_size.y += line_height;
3646 
3647     if (remaining)
3648         *remaining = s;
3649 
3650     return text_size;
3651 }
3652 
3653 // 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)
3654 namespace ImStb
3655 {
3656 
STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState * obj)3657 static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->CurLenW; }
STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState * obj,int idx)3658 static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { return obj->TextW[idx]; }
STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState * obj,int line_start_idx,int char_idx)3659 static float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* 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; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); }
STB_TEXTEDIT_KEYTOTEXT(int key)3660 static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x200000 ? 0 : key; }
3661 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
STB_TEXTEDIT_LAYOUTROW(StbTexteditRow * r,ImGuiInputTextState * obj,int line_start_idx)3662 static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
3663 {
3664     const ImWchar* text = obj->TextW.Data;
3665     const ImWchar* text_remaining = NULL;
3666     const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
3667     r->x0 = 0.0f;
3668     r->x1 = size.x;
3669     r->baseline_y_delta = size.y;
3670     r->ymin = 0.0f;
3671     r->ymax = size.y;
3672     r->num_chars = (int)(text_remaining - (text + line_start_idx));
3673 }
3674 
3675 // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
is_separator(unsigned int c)3676 static bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
is_word_boundary_from_right(ImGuiInputTextState * obj,int idx)3677 static int  is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)      { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; }
STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState * obj,int idx)3678 static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)   { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
3679 #ifdef __APPLE__    // FIXME: Move setting to IO structure
is_word_boundary_from_left(ImGuiInputTextState * obj,int idx)3680 static int  is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)       { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; }
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState * obj,int idx)3681 static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
3682 #else
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState * obj,int idx)3683 static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
3684 #endif
3685 #define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
3686 #define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
3687 
STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState * obj,int pos,int n)3688 static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
3689 {
3690     ImWchar* dst = obj->TextW.Data + pos;
3691 
3692     // We maintain our buffer length in both UTF-8 and wchar formats
3693     obj->Edited = true;
3694     obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
3695     obj->CurLenW -= n;
3696 
3697     // Offset remaining text (FIXME-OPT: Use memmove)
3698     const ImWchar* src = obj->TextW.Data + pos + n;
3699     while (ImWchar c = *src++)
3700         *dst++ = c;
3701     *dst = '\0';
3702 }
3703 
STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState * obj,int pos,const ImWchar * new_text,int new_text_len)3704 static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
3705 {
3706     const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3707     const int text_len = obj->CurLenW;
3708     IM_ASSERT(pos <= text_len);
3709 
3710     const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
3711     if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
3712         return false;
3713 
3714     // Grow internal buffer if needed
3715     if (new_text_len + text_len + 1 > obj->TextW.Size)
3716     {
3717         if (!is_resizable)
3718             return false;
3719         IM_ASSERT(text_len < obj->TextW.Size);
3720         obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
3721     }
3722 
3723     ImWchar* text = obj->TextW.Data;
3724     if (pos != text_len)
3725         memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
3726     memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
3727 
3728     obj->Edited = true;
3729     obj->CurLenW += new_text_len;
3730     obj->CurLenA += new_text_len_utf8;
3731     obj->TextW[obj->CurLenW] = '\0';
3732 
3733     return true;
3734 }
3735 
3736 // 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)
3737 #define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
3738 #define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
3739 #define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
3740 #define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
3741 #define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
3742 #define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
3743 #define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
3744 #define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
3745 #define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
3746 #define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
3747 #define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
3748 #define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
3749 #define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
3750 #define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
3751 #define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
3752 #define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
3753 #define STB_TEXTEDIT_K_SHIFT        0x400000
3754 
3755 #define STB_TEXTEDIT_IMPLEMENTATION
3756 #include "imstb_textedit.h"
3757 
3758 // stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
3759 // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
stb_textedit_replace(ImGuiInputTextState * str,STB_TexteditState * state,const STB_TEXTEDIT_CHARTYPE * text,int text_len)3760 static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len)
3761 {
3762     stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
3763     ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
3764     if (text_len <= 0)
3765         return;
3766     if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
3767     {
3768         state->cursor = text_len;
3769         state->has_preferred_x = 0;
3770         return;
3771     }
3772     IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
3773 }
3774 
3775 } // namespace ImStb
3776 
OnKeyPressed(int key)3777 void ImGuiInputTextState::OnKeyPressed(int key)
3778 {
3779     stb_textedit_key(this, &Stb, key);
3780     CursorFollow = true;
3781     CursorAnimReset();
3782 }
3783 
ImGuiInputTextCallbackData()3784 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
3785 {
3786     memset(this, 0, sizeof(*this));
3787 }
3788 
3789 // Public API to manipulate UTF-8 text
3790 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
3791 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
DeleteChars(int pos,int bytes_count)3792 void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
3793 {
3794     IM_ASSERT(pos + bytes_count <= BufTextLen);
3795     char* dst = Buf + pos;
3796     const char* src = Buf + pos + bytes_count;
3797     while (char c = *src++)
3798         *dst++ = c;
3799     *dst = '\0';
3800 
3801     if (CursorPos >= pos + bytes_count)
3802         CursorPos -= bytes_count;
3803     else if (CursorPos >= pos)
3804         CursorPos = pos;
3805     SelectionStart = SelectionEnd = CursorPos;
3806     BufDirty = true;
3807     BufTextLen -= bytes_count;
3808 }
3809 
InsertChars(int pos,const char * new_text,const char * new_text_end)3810 void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
3811 {
3812     const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3813     const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
3814     if (new_text_len + BufTextLen >= BufSize)
3815     {
3816         if (!is_resizable)
3817             return;
3818 
3819         // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
3820         ImGuiContext& g = *GImGui;
3821         ImGuiInputTextState* edit_state = &g.InputTextState;
3822         IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
3823         IM_ASSERT(Buf == edit_state->TextA.Data);
3824         int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
3825         edit_state->TextA.reserve(new_buf_size + 1);
3826         Buf = edit_state->TextA.Data;
3827         BufSize = edit_state->BufCapacityA = new_buf_size;
3828     }
3829 
3830     if (BufTextLen != pos)
3831         memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
3832     memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
3833     Buf[BufTextLen + new_text_len] = '\0';
3834 
3835     if (CursorPos >= pos)
3836         CursorPos += new_text_len;
3837     SelectionStart = SelectionEnd = CursorPos;
3838     BufDirty = true;
3839     BufTextLen += new_text_len;
3840 }
3841 
3842 // Return false to discard a character.
InputTextFilterCharacter(unsigned int * p_char,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data,ImGuiInputSource input_source)3843 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source)
3844 {
3845     IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard);
3846     unsigned int c = *p_char;
3847 
3848     // Filter non-printable (NB: isprint is unreliable! see #2467)
3849     bool apply_named_filters = true;
3850     if (c < 0x20)
3851     {
3852         bool pass = false;
3853         pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
3854         pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
3855         if (!pass)
3856             return false;
3857         apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
3858     }
3859 
3860     if (input_source != ImGuiInputSource_Clipboard)
3861     {
3862         // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
3863         if (c == 127)
3864             return false;
3865 
3866         // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
3867         if (c >= 0xE000 && c <= 0xF8FF)
3868             return false;
3869     }
3870 
3871     // Filter Unicode ranges we are not handling in this build
3872     if (c > IM_UNICODE_CODEPOINT_MAX)
3873         return false;
3874 
3875     // Generic named filters
3876     if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)))
3877     {
3878         // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf.
3879         // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
3880         // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
3881         // Change the default decimal_point with:
3882         //   ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
3883         ImGuiContext& g = *GImGui;
3884         const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint;
3885 
3886         // Allow 0-9 . - + * /
3887         if (flags & ImGuiInputTextFlags_CharsDecimal)
3888             if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
3889                 return false;
3890 
3891         // Allow 0-9 . - + * / e E
3892         if (flags & ImGuiInputTextFlags_CharsScientific)
3893             if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
3894                 return false;
3895 
3896         // Allow 0-9 a-F A-F
3897         if (flags & ImGuiInputTextFlags_CharsHexadecimal)
3898             if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
3899                 return false;
3900 
3901         // Turn a-z into A-Z
3902         if (flags & ImGuiInputTextFlags_CharsUppercase)
3903             if (c >= 'a' && c <= 'z')
3904                 *p_char = (c += (unsigned int)('A' - 'a'));
3905 
3906         if (flags & ImGuiInputTextFlags_CharsNoBlank)
3907             if (ImCharIsBlankW(c))
3908                 return false;
3909     }
3910 
3911     // Custom callback filter
3912     if (flags & ImGuiInputTextFlags_CallbackCharFilter)
3913     {
3914         ImGuiInputTextCallbackData callback_data;
3915         memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
3916         callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
3917         callback_data.EventChar = (ImWchar)c;
3918         callback_data.Flags = flags;
3919         callback_data.UserData = user_data;
3920         if (callback(&callback_data) != 0)
3921             return false;
3922         *p_char = callback_data.EventChar;
3923         if (!callback_data.EventChar)
3924             return false;
3925     }
3926 
3927     return true;
3928 }
3929 
3930 // Edit a string of text
3931 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
3932 //   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
3933 //   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
3934 // - 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.
3935 // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
3936 // (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
3937 //  doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
InputTextEx(const char * label,const char * hint,char * buf,int buf_size,const ImVec2 & size_arg,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * callback_user_data)3938 bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
3939 {
3940     ImGuiWindow* window = GetCurrentWindow();
3941     if (window->SkipItems)
3942         return false;
3943 
3944     IM_ASSERT(buf != NULL && buf_size >= 0);
3945     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
3946     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
3947 
3948     ImGuiContext& g = *GImGui;
3949     ImGuiIO& io = g.IO;
3950     const ImGuiStyle& style = g.Style;
3951 
3952     const bool RENDER_SELECTION_WHEN_INACTIVE = false;
3953     const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
3954     const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
3955     const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
3956     const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
3957     const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
3958     if (is_resizable)
3959         IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
3960 
3961     if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
3962         BeginGroup();
3963     const ImGuiID id = window->GetID(label);
3964     const ImVec2 label_size = CalcTextSize(label, NULL, true);
3965     const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line
3966     const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
3967 
3968     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
3969     const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
3970 
3971     ImGuiWindow* draw_window = window;
3972     ImVec2 inner_size = frame_size;
3973     ImGuiItemStatusFlags item_status_flags = 0;
3974     if (is_multiline)
3975     {
3976         ImVec2 backup_pos = window->DC.CursorPos;
3977         ItemSize(total_bb, style.FramePadding.y);
3978         if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable))
3979         {
3980             EndGroup();
3981             return false;
3982         }
3983         item_status_flags = g.LastItemData.StatusFlags;
3984         window->DC.CursorPos = backup_pos;
3985 
3986         // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
3987         PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
3988         PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
3989         PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
3990         bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove);
3991         PopStyleVar(2);
3992         PopStyleColor();
3993         if (!child_visible)
3994         {
3995             EndChild();
3996             EndGroup();
3997             return false;
3998         }
3999         draw_window = g.CurrentWindow; // Child window
4000         draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
4001         draw_window->DC.CursorPos += style.FramePadding;
4002         inner_size.x -= draw_window->ScrollbarSizes.x;
4003     }
4004     else
4005     {
4006         // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
4007         ItemSize(total_bb, style.FramePadding.y);
4008         if (!(flags & ImGuiInputTextFlags_MergedItem))
4009             if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable))
4010                 return false;
4011         item_status_flags = g.LastItemData.StatusFlags;
4012     }
4013     const bool hovered = ItemHoverable(frame_bb, id);
4014     if (hovered)
4015         g.MouseCursor = ImGuiMouseCursor_TextInput;
4016 
4017     // We are only allowed to access the state if we are already the active widget.
4018     ImGuiInputTextState* state = GetInputTextState(id);
4019 
4020     const bool focus_requested_by_code = (item_status_flags & ImGuiItemStatusFlags_FocusedByCode) != 0;
4021     const bool focus_requested_by_tabbing = (item_status_flags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
4022 
4023     const bool user_clicked = hovered && io.MouseClicked[0];
4024     const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard));
4025     const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4026     const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4027 
4028     bool clear_active_id = false;
4029     bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
4030 
4031     float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
4032 
4033     const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline);
4034     const bool init_make_active = (user_clicked || user_scroll_finish || user_nav_input_start || focus_requested_by_code || focus_requested_by_tabbing);
4035     const bool init_state = (init_make_active || user_scroll_active);
4036     if ((init_state && g.ActiveId != id) || init_changed_specs)
4037     {
4038         // Access state even if we don't own it yet.
4039         state = &g.InputTextState;
4040         state->CursorAnimReset();
4041 
4042         // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
4043         // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
4044         const int buf_len = (int)strlen(buf);
4045         state->InitialTextA.resize(buf_len + 1);    // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
4046         memcpy(state->InitialTextA.Data, buf, buf_len + 1);
4047 
4048         // Start edition
4049         const char* buf_end = NULL;
4050         state->TextW.resize(buf_size + 1);          // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
4051         state->TextA.resize(0);
4052         state->TextAIsValid = false;                // TextA is not valid yet (we will display buf until then)
4053         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
4054         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.
4055 
4056         // Preserve cursor position and undo/redo stack if we come back to same widget
4057         // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
4058         const bool recycle_state = (state->ID == id && !init_changed_specs);
4059         if (recycle_state)
4060         {
4061             // Recycle existing cursor/selection/undo stack but clamp position
4062             // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
4063             state->CursorClamp();
4064         }
4065         else
4066         {
4067             state->ID = id;
4068             state->ScrollX = 0.0f;
4069             stb_textedit_initialize_state(&state->Stb, !is_multiline);
4070             if (!is_multiline && focus_requested_by_code)
4071                 select_all = true;
4072         }
4073         if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
4074             state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863)
4075         if (!is_multiline && (focus_requested_by_tabbing || (user_clicked && io.KeyCtrl)))
4076             select_all = true;
4077     }
4078 
4079     if (g.ActiveId != id && init_make_active)
4080     {
4081         IM_ASSERT(state && state->ID == id);
4082         SetActiveID(id, window);
4083         SetFocusID(id, window);
4084         FocusWindow(window);
4085 
4086         // Declare our inputs
4087         IM_ASSERT(ImGuiNavInput_COUNT < 32);
4088         g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
4089         if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
4090             g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
4091         g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
4092         g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End);
4093         if (is_multiline)
4094             g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown);
4095         if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput))  // Disable keyboard tabbing out as we will use the \t character.
4096             g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab);
4097     }
4098 
4099     // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
4100     if (g.ActiveId == id && state == NULL)
4101         ClearActiveID();
4102 
4103     // Release focus when we click outside
4104     if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
4105         clear_active_id = true;
4106 
4107     // Lock the decision of whether we are going to take the path displaying the cursor or selection
4108     const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
4109     bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4110     bool value_changed = false;
4111     bool enter_pressed = false;
4112 
4113     // When read-only we always use the live data passed to the function
4114     // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
4115     if (is_readonly && state != NULL && (render_cursor || render_selection))
4116     {
4117         const char* buf_end = NULL;
4118         state->TextW.resize(buf_size + 1);
4119         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
4120         state->CurLenA = (int)(buf_end - buf);
4121         state->CursorClamp();
4122         render_selection &= state->HasSelection();
4123     }
4124 
4125     // Select the buffer to render.
4126     const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
4127     const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
4128 
4129     // Password pushes a temporary font with only a fallback glyph
4130     if (is_password && !is_displaying_hint)
4131     {
4132         const ImFontGlyph* glyph = g.Font->FindGlyph('*');
4133         ImFont* password_font = &g.InputTextPasswordFont;
4134         password_font->FontSize = g.Font->FontSize;
4135         password_font->Scale = g.Font->Scale;
4136         password_font->Ascent = g.Font->Ascent;
4137         password_font->Descent = g.Font->Descent;
4138         password_font->ContainerAtlas = g.Font->ContainerAtlas;
4139         password_font->FallbackGlyph = glyph;
4140         password_font->FallbackAdvanceX = glyph->AdvanceX;
4141         IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
4142         PushFont(password_font);
4143     }
4144 
4145     // Process mouse inputs and character inputs
4146     int backup_current_text_length = 0;
4147     if (g.ActiveId == id)
4148     {
4149         IM_ASSERT(state != NULL);
4150         backup_current_text_length = state->CurLenA;
4151         state->Edited = false;
4152         state->BufCapacityA = buf_size;
4153         state->Flags = flags;
4154         state->UserCallback = callback;
4155         state->UserCallbackData = callback_user_data;
4156 
4157         // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
4158         // Down the line we should have a cleaner library-wide concept of Selected vs Active.
4159         g.ActiveIdAllowOverlap = !io.MouseDown[0];
4160         g.WantTextInputNextFrame = 1;
4161 
4162         // Edit in progress
4163         const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX;
4164         const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
4165 
4166         const bool is_osx = io.ConfigMacOSXBehaviors;
4167         if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
4168         {
4169             state->SelectAll();
4170             state->SelectedAllMouseLock = true;
4171         }
4172         else if (hovered && is_osx && io.MouseDoubleClicked[0])
4173         {
4174             // Double-click select a word only, OS X style (by simulating keystrokes)
4175             state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
4176             state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
4177         }
4178         else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
4179         {
4180             if (hovered)
4181             {
4182                 stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
4183                 state->CursorAnimReset();
4184             }
4185         }
4186         else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
4187         {
4188             stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
4189             state->CursorAnimReset();
4190             state->CursorFollow = true;
4191         }
4192         if (state->SelectedAllMouseLock && !io.MouseDown[0])
4193             state->SelectedAllMouseLock = false;
4194 
4195         // It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys.
4196         // Win32 and GLFW naturally do it but not SDL.
4197         const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
4198         if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
4199             if (!io.InputQueueCharacters.contains('\t'))
4200             {
4201                 unsigned int c = '\t'; // Insert TAB
4202                 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4203                     state->OnKeyPressed((int)c);
4204             }
4205 
4206         // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
4207         // 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.
4208         if (io.InputQueueCharacters.Size > 0)
4209         {
4210             if (!ignore_char_inputs && !is_readonly && !user_nav_input_start)
4211                 for (int n = 0; n < io.InputQueueCharacters.Size; n++)
4212                 {
4213                     // Insert character if they pass filtering
4214                     unsigned int c = (unsigned int)io.InputQueueCharacters[n];
4215                     if (c == '\t' && io.KeyShift)
4216                         continue;
4217                     if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4218                         state->OnKeyPressed((int)c);
4219                 }
4220 
4221             // Consume characters
4222             io.InputQueueCharacters.resize(0);
4223         }
4224     }
4225 
4226     // Process other shortcuts/key-presses
4227     bool cancel_edit = false;
4228     if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
4229     {
4230         IM_ASSERT(state != NULL);
4231         IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here.
4232 
4233         const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
4234         state->Stb.row_count_per_page = row_count_per_page;
4235 
4236         const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
4237         const bool is_osx = io.ConfigMacOSXBehaviors;
4238         const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift));
4239         const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
4240         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
4241         const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
4242         const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift);
4243         const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
4244 
4245         const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
4246         const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
4247         const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly;
4248         const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable);
4249         const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable;
4250 
4251         if (IsKeyPressedMap(ImGuiKey_LeftArrow))                        { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
4252         else if (IsKeyPressedMap(ImGuiKey_RightArrow))                  { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
4253         else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
4254         else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
4255         else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
4256         else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
4257         else if (IsKeyPressedMap(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
4258         else if (IsKeyPressedMap(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
4259         else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly)      { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
4260         else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly)
4261         {
4262             if (!state->HasSelection())
4263             {
4264                 if (is_wordmove_key_down)
4265                     state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
4266                 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl)
4267                     state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
4268             }
4269             state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
4270         }
4271         else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter))
4272         {
4273             bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
4274             if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
4275             {
4276                 enter_pressed = clear_active_id = true;
4277             }
4278             else if (!is_readonly)
4279             {
4280                 unsigned int c = '\n'; // Insert new line
4281                 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4282                     state->OnKeyPressed((int)c);
4283             }
4284         }
4285         else if (IsKeyPressedMap(ImGuiKey_Escape))
4286         {
4287             clear_active_id = cancel_edit = true;
4288         }
4289         else if (is_undo || is_redo)
4290         {
4291             state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
4292             state->ClearSelection();
4293         }
4294         else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
4295         {
4296             state->SelectAll();
4297             state->CursorFollow = true;
4298         }
4299         else if (is_cut || is_copy)
4300         {
4301             // Cut, Copy
4302             if (io.SetClipboardTextFn)
4303             {
4304                 const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
4305                 const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
4306                 const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
4307                 char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
4308                 ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
4309                 SetClipboardText(clipboard_data);
4310                 MemFree(clipboard_data);
4311             }
4312             if (is_cut)
4313             {
4314                 if (!state->HasSelection())
4315                     state->SelectAll();
4316                 state->CursorFollow = true;
4317                 stb_textedit_cut(state, &state->Stb);
4318             }
4319         }
4320         else if (is_paste)
4321         {
4322             if (const char* clipboard = GetClipboardText())
4323             {
4324                 // Filter pasted buffer
4325                 const int clipboard_len = (int)strlen(clipboard);
4326                 ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar));
4327                 int clipboard_filtered_len = 0;
4328                 for (const char* s = clipboard; *s; )
4329                 {
4330                     unsigned int c;
4331                     s += ImTextCharFromUtf8(&c, s, NULL);
4332                     if (c == 0)
4333                         break;
4334                     if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
4335                         continue;
4336                     clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
4337                 }
4338                 clipboard_filtered[clipboard_filtered_len] = 0;
4339                 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
4340                 {
4341                     stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
4342                     state->CursorFollow = true;
4343                 }
4344                 MemFree(clipboard_filtered);
4345             }
4346         }
4347 
4348         // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
4349         render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4350     }
4351 
4352     // Process callbacks and apply result back to user's buffer.
4353     if (g.ActiveId == id)
4354     {
4355         IM_ASSERT(state != NULL);
4356         const char* apply_new_text = NULL;
4357         int apply_new_text_length = 0;
4358         if (cancel_edit)
4359         {
4360             // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
4361             if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0)
4362             {
4363                 // Push records into the undo stack so we can CTRL+Z the revert operation itself
4364                 apply_new_text = state->InitialTextA.Data;
4365                 apply_new_text_length = state->InitialTextA.Size - 1;
4366                 ImVector<ImWchar> w_text;
4367                 if (apply_new_text_length > 0)
4368                 {
4369                     w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1);
4370                     ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length);
4371                 }
4372                 stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0);
4373             }
4374         }
4375 
4376         // 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.
4377         // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
4378         // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
4379         bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
4380         if (apply_edit_back_to_user_buffer)
4381         {
4382             // Apply new value immediately - copy modified buffer back
4383             // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
4384             // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
4385             // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
4386             if (!is_readonly)
4387             {
4388                 state->TextAIsValid = true;
4389                 state->TextA.resize(state->TextW.Size * 4 + 1);
4390                 ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
4391             }
4392 
4393             // User callback
4394             if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
4395             {
4396                 IM_ASSERT(callback != NULL);
4397 
4398                 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
4399                 ImGuiInputTextFlags event_flag = 0;
4400                 ImGuiKey event_key = ImGuiKey_COUNT;
4401                 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
4402                 {
4403                     event_flag = ImGuiInputTextFlags_CallbackCompletion;
4404                     event_key = ImGuiKey_Tab;
4405                 }
4406                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
4407                 {
4408                     event_flag = ImGuiInputTextFlags_CallbackHistory;
4409                     event_key = ImGuiKey_UpArrow;
4410                 }
4411                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
4412                 {
4413                     event_flag = ImGuiInputTextFlags_CallbackHistory;
4414                     event_key = ImGuiKey_DownArrow;
4415                 }
4416                 else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
4417                 {
4418                     event_flag = ImGuiInputTextFlags_CallbackEdit;
4419                 }
4420                 else if (flags & ImGuiInputTextFlags_CallbackAlways)
4421                 {
4422                     event_flag = ImGuiInputTextFlags_CallbackAlways;
4423                 }
4424 
4425                 if (event_flag)
4426                 {
4427                     ImGuiInputTextCallbackData callback_data;
4428                     memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
4429                     callback_data.EventFlag = event_flag;
4430                     callback_data.Flags = flags;
4431                     callback_data.UserData = callback_user_data;
4432 
4433                     callback_data.EventKey = event_key;
4434                     callback_data.Buf = state->TextA.Data;
4435                     callback_data.BufTextLen = state->CurLenA;
4436                     callback_data.BufSize = state->BufCapacityA;
4437                     callback_data.BufDirty = false;
4438 
4439                     // 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)
4440                     ImWchar* text = state->TextW.Data;
4441                     const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
4442                     const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
4443                     const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
4444 
4445                     // Call user code
4446                     callback(&callback_data);
4447 
4448                     // Read back what user may have modified
4449                     IM_ASSERT(callback_data.Buf == state->TextA.Data);  // Invalid to modify those fields
4450                     IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
4451                     IM_ASSERT(callback_data.Flags == flags);
4452                     const bool buf_dirty = callback_data.BufDirty;
4453                     if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty)            { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; }
4454                     if (callback_data.SelectionStart != utf8_selection_start || buf_dirty)  { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
4455                     if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty)      { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
4456                     if (buf_dirty)
4457                     {
4458                         IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
4459                         if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
4460                             state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
4461                         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
4462                         state->CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
4463                         state->CursorAnimReset();
4464                     }
4465                 }
4466             }
4467 
4468             // Will copy result string if modified
4469             if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
4470             {
4471                 apply_new_text = state->TextA.Data;
4472                 apply_new_text_length = state->CurLenA;
4473             }
4474         }
4475 
4476         // Copy result to user buffer
4477         if (apply_new_text)
4478         {
4479             // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
4480             // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
4481             // without any storage on user's side.
4482             IM_ASSERT(apply_new_text_length >= 0);
4483             if (is_resizable)
4484             {
4485                 ImGuiInputTextCallbackData callback_data;
4486                 callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
4487                 callback_data.Flags = flags;
4488                 callback_data.Buf = buf;
4489                 callback_data.BufTextLen = apply_new_text_length;
4490                 callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
4491                 callback_data.UserData = callback_user_data;
4492                 callback(&callback_data);
4493                 buf = callback_data.Buf;
4494                 buf_size = callback_data.BufSize;
4495                 apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
4496                 IM_ASSERT(apply_new_text_length <= buf_size);
4497             }
4498             //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
4499 
4500             // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
4501             ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
4502             value_changed = true;
4503         }
4504 
4505         // Clear temporary user storage
4506         state->Flags = ImGuiInputTextFlags_None;
4507         state->UserCallback = NULL;
4508         state->UserCallbackData = NULL;
4509     }
4510 
4511     // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
4512     if (clear_active_id && g.ActiveId == id)
4513         ClearActiveID();
4514 
4515     // Render frame
4516     if (!is_multiline)
4517     {
4518         RenderNavHighlight(frame_bb, id);
4519         RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
4520     }
4521 
4522     const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
4523     ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
4524     ImVec2 text_size(0.0f, 0.0f);
4525 
4526     // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
4527     // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
4528     // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
4529     const int buf_display_max_length = 2 * 1024 * 1024;
4530     const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
4531     const char* buf_display_end = NULL; // We have specialized paths below for setting the length
4532     if (is_displaying_hint)
4533     {
4534         buf_display = hint;
4535         buf_display_end = hint + strlen(hint);
4536     }
4537 
4538     // Render text. We currently only render selection when the widget is active or while scrolling.
4539     // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
4540     if (render_cursor || render_selection)
4541     {
4542         IM_ASSERT(state != NULL);
4543         if (!is_displaying_hint)
4544             buf_display_end = buf_display + state->CurLenA;
4545 
4546         // Render text (with cursor and selection)
4547         // This is going to be messy. We need to:
4548         // - Display the text (this alone can be more easily clipped)
4549         // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
4550         // - Measure text height (for scrollbar)
4551         // 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)
4552         // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
4553         const ImWchar* text_begin = state->TextW.Data;
4554         ImVec2 cursor_offset, select_start_offset;
4555 
4556         {
4557             // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
4558             const ImWchar* searches_input_ptr[2] = { NULL, NULL };
4559             int searches_result_line_no[2] = { -1000, -1000 };
4560             int searches_remaining = 0;
4561             if (render_cursor)
4562             {
4563                 searches_input_ptr[0] = text_begin + state->Stb.cursor;
4564                 searches_result_line_no[0] = -1;
4565                 searches_remaining++;
4566             }
4567             if (render_selection)
4568             {
4569                 searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
4570                 searches_result_line_no[1] = -1;
4571                 searches_remaining++;
4572             }
4573 
4574             // Iterate all lines to find our line numbers
4575             // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
4576             searches_remaining += is_multiline ? 1 : 0;
4577             int line_count = 0;
4578             //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-bit
4579             for (const ImWchar* s = text_begin; *s != 0; s++)
4580                 if (*s == '\n')
4581                 {
4582                     line_count++;
4583                     if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
4584                     if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
4585                 }
4586             line_count++;
4587             if (searches_result_line_no[0] == -1)
4588                 searches_result_line_no[0] = line_count;
4589             if (searches_result_line_no[1] == -1)
4590                 searches_result_line_no[1] = line_count;
4591 
4592             // Calculate 2d position by finding the beginning of the line and measuring distance
4593             cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
4594             cursor_offset.y = searches_result_line_no[0] * g.FontSize;
4595             if (searches_result_line_no[1] >= 0)
4596             {
4597                 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
4598                 select_start_offset.y = searches_result_line_no[1] * g.FontSize;
4599             }
4600 
4601             // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
4602             if (is_multiline)
4603                 text_size = ImVec2(inner_size.x, line_count * g.FontSize);
4604         }
4605 
4606         // Scroll
4607         if (render_cursor && state->CursorFollow)
4608         {
4609             // Horizontal scroll in chunks of quarter width
4610             if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
4611             {
4612                 const float scroll_increment_x = inner_size.x * 0.25f;
4613                 const float visible_width = inner_size.x - style.FramePadding.x;
4614                 if (cursor_offset.x < state->ScrollX)
4615                     state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
4616                 else if (cursor_offset.x - visible_width >= state->ScrollX)
4617                     state->ScrollX = IM_FLOOR(cursor_offset.x - visible_width + scroll_increment_x);
4618             }
4619             else
4620             {
4621                 state->ScrollX = 0.0f;
4622             }
4623 
4624             // Vertical scroll
4625             if (is_multiline)
4626             {
4627                 // Test if cursor is vertically visible
4628                 if (cursor_offset.y - g.FontSize < scroll_y)
4629                     scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
4630                 else if (cursor_offset.y - inner_size.y >= scroll_y)
4631                     scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
4632                 const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
4633                 scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
4634                 draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
4635                 draw_window->Scroll.y = scroll_y;
4636             }
4637 
4638             state->CursorFollow = false;
4639         }
4640 
4641         // Draw selection
4642         const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
4643         if (render_selection)
4644         {
4645             const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
4646             const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
4647 
4648             ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
4649             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.
4650             float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
4651             ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
4652             for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
4653             {
4654                 if (rect_pos.y > clip_rect.w + g.FontSize)
4655                     break;
4656                 if (rect_pos.y < clip_rect.y)
4657                 {
4658                     //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p);  // FIXME-OPT: Could use this when wchar_t are 16-bit
4659                     //p = p ? p + 1 : text_selected_end;
4660                     while (p < text_selected_end)
4661                         if (*p++ == '\n')
4662                             break;
4663                 }
4664                 else
4665                 {
4666                     ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
4667                     if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
4668                     ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
4669                     rect.ClipWith(clip_rect);
4670                     if (rect.Overlaps(clip_rect))
4671                         draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
4672                 }
4673                 rect_pos.x = draw_pos.x - draw_scroll.x;
4674                 rect_pos.y += g.FontSize;
4675             }
4676         }
4677 
4678         // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
4679         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
4680         {
4681             ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
4682             draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
4683         }
4684 
4685         // Draw blinking cursor
4686         if (render_cursor)
4687         {
4688             state->CursorAnim += io.DeltaTime;
4689             bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
4690             ImVec2 cursor_screen_pos = ImFloor(draw_pos + cursor_offset - draw_scroll);
4691             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);
4692             if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
4693                 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
4694 
4695             // 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.)
4696             if (!is_readonly)
4697                 g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
4698         }
4699     }
4700     else
4701     {
4702         // Render text only (no selection, no cursor)
4703         if (is_multiline)
4704             text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
4705         else if (!is_displaying_hint && g.ActiveId == id)
4706             buf_display_end = buf_display + state->CurLenA;
4707         else if (!is_displaying_hint)
4708             buf_display_end = buf_display + strlen(buf_display);
4709 
4710         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
4711         {
4712             ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
4713             draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
4714         }
4715     }
4716 
4717     if (is_password && !is_displaying_hint)
4718         PopFont();
4719 
4720     if (is_multiline)
4721     {
4722         Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
4723         EndChild();
4724         EndGroup();
4725     }
4726 
4727     // Log as text
4728     if (g.LogEnabled && (!is_password || is_displaying_hint))
4729     {
4730         LogSetNextTextDecoration("{", "}");
4731         LogRenderedText(&draw_pos, buf_display, buf_display_end);
4732     }
4733 
4734     if (label_size.x > 0)
4735         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
4736 
4737     if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited))
4738         MarkItemEdited(id);
4739 
4740     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
4741     if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
4742         return enter_pressed;
4743     else
4744         return value_changed;
4745 }
4746 
4747 //-------------------------------------------------------------------------
4748 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
4749 //-------------------------------------------------------------------------
4750 // - ColorEdit3()
4751 // - ColorEdit4()
4752 // - ColorPicker3()
4753 // - RenderColorRectWithAlphaCheckerboard() [Internal]
4754 // - ColorPicker4()
4755 // - ColorButton()
4756 // - SetColorEditOptions()
4757 // - ColorTooltip() [Internal]
4758 // - ColorEditOptionsPopup() [Internal]
4759 // - ColorPickerOptionsPopup() [Internal]
4760 //-------------------------------------------------------------------------
4761 
ColorEdit3(const char * label,float col[3],ImGuiColorEditFlags flags)4762 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
4763 {
4764     return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
4765 }
4766 
4767 // Edit colors components (each component in 0.0f..1.0f range).
4768 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4769 // With typical options: Left-click on color 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)4770 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
4771 {
4772     ImGuiWindow* window = GetCurrentWindow();
4773     if (window->SkipItems)
4774         return false;
4775 
4776     ImGuiContext& g = *GImGui;
4777     const ImGuiStyle& style = g.Style;
4778     const float square_sz = GetFrameHeight();
4779     const float w_full = CalcItemWidth();
4780     const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
4781     const float w_inputs = w_full - w_button;
4782     const char* label_display_end = FindRenderedTextEnd(label);
4783     g.NextItemData.ClearFlags();
4784 
4785     BeginGroup();
4786     PushID(label);
4787 
4788     // If we're not showing any slider there's no point in doing any HSV conversions
4789     const ImGuiColorEditFlags flags_untouched = flags;
4790     if (flags & ImGuiColorEditFlags_NoInputs)
4791         flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
4792 
4793     // Context menu: display and modify options (before defaults are applied)
4794     if (!(flags & ImGuiColorEditFlags_NoOptions))
4795         ColorEditOptionsPopup(col, flags);
4796 
4797     // Read stored options
4798     if (!(flags & ImGuiColorEditFlags_DisplayMask_))
4799         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
4800     if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
4801         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
4802     if (!(flags & ImGuiColorEditFlags_PickerMask_))
4803         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
4804     if (!(flags & ImGuiColorEditFlags_InputMask_))
4805         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
4806     flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
4807     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
4808     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected
4809 
4810     const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
4811     const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
4812     const int components = alpha ? 4 : 3;
4813 
4814     // Convert to the formats we need
4815     float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
4816     if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
4817         ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
4818     else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
4819     {
4820         // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
4821         ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
4822         if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
4823         {
4824             if (f[1] == 0)
4825                 f[0] = g.ColorEditLastHue;
4826             if (f[2] == 0)
4827                 f[1] = g.ColorEditLastSat;
4828         }
4829     }
4830     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]) };
4831 
4832     bool value_changed = false;
4833     bool value_changed_as_float = false;
4834 
4835     const ImVec2 pos = window->DC.CursorPos;
4836     const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
4837     window->DC.CursorPos.x = pos.x + inputs_offset_x;
4838 
4839     if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
4840     {
4841         // RGB/HSV 0..255 Sliders
4842         const float w_item_one  = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
4843         const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
4844 
4845         const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
4846         static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
4847         static const char* fmt_table_int[3][4] =
4848         {
4849             {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
4850             { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
4851             { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
4852         };
4853         static const char* fmt_table_float[3][4] =
4854         {
4855             {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
4856             { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
4857             { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
4858         };
4859         const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
4860 
4861         for (int n = 0; n < components; n++)
4862         {
4863             if (n > 0)
4864                 SameLine(0, style.ItemInnerSpacing.x);
4865             SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last);
4866 
4867             // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
4868             if (flags & ImGuiColorEditFlags_Float)
4869             {
4870                 value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
4871                 value_changed_as_float |= value_changed;
4872             }
4873             else
4874             {
4875                 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
4876             }
4877             if (!(flags & ImGuiColorEditFlags_NoOptions))
4878                 OpenPopupOnItemClick("context");
4879         }
4880     }
4881     else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
4882     {
4883         // RGB Hexadecimal Input
4884         char buf[64];
4885         if (alpha)
4886             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));
4887         else
4888             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
4889         SetNextItemWidth(w_inputs);
4890         if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
4891         {
4892             value_changed = true;
4893             char* p = buf;
4894             while (*p == '#' || ImCharIsBlankA(*p))
4895                 p++;
4896             i[0] = i[1] = i[2] = 0;
4897             i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
4898             int r;
4899             if (alpha)
4900                 r = 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)
4901             else
4902                 r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
4903             IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
4904         }
4905         if (!(flags & ImGuiColorEditFlags_NoOptions))
4906             OpenPopupOnItemClick("context");
4907     }
4908 
4909     ImGuiWindow* picker_active_window = NULL;
4910     if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
4911     {
4912         const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
4913         window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
4914 
4915         const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
4916         if (ColorButton("##ColorButton", col_v4, flags))
4917         {
4918             if (!(flags & ImGuiColorEditFlags_NoPicker))
4919             {
4920                 // Store current color and open a picker
4921                 g.ColorPickerRef = col_v4;
4922                 OpenPopup("picker");
4923                 SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(-1, style.ItemSpacing.y));
4924             }
4925         }
4926         if (!(flags & ImGuiColorEditFlags_NoOptions))
4927             OpenPopupOnItemClick("context");
4928 
4929         if (BeginPopup("picker"))
4930         {
4931             picker_active_window = g.CurrentWindow;
4932             if (label != label_display_end)
4933             {
4934                 TextEx(label, label_display_end);
4935                 Spacing();
4936             }
4937             ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
4938             ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
4939             SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
4940             value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
4941             EndPopup();
4942         }
4943     }
4944 
4945     if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
4946     {
4947         const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x;
4948         window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y);
4949         TextEx(label, label_display_end);
4950     }
4951 
4952     // Convert back
4953     if (value_changed && picker_active_window == NULL)
4954     {
4955         if (!value_changed_as_float)
4956             for (int n = 0; n < 4; n++)
4957                 f[n] = i[n] / 255.0f;
4958         if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
4959         {
4960             g.ColorEditLastHue = f[0];
4961             g.ColorEditLastSat = f[1];
4962             ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
4963             memcpy(g.ColorEditLastColor, f, sizeof(float) * 3);
4964         }
4965         if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
4966             ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
4967 
4968         col[0] = f[0];
4969         col[1] = f[1];
4970         col[2] = f[2];
4971         if (alpha)
4972             col[3] = f[3];
4973     }
4974 
4975     PopID();
4976     EndGroup();
4977 
4978     // Drag and Drop Target
4979     // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
4980     if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
4981     {
4982         bool accepted_drag_drop = false;
4983         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
4984         {
4985             memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
4986             value_changed = accepted_drag_drop = true;
4987         }
4988         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
4989         {
4990             memcpy((float*)col, payload->Data, sizeof(float) * components);
4991             value_changed = accepted_drag_drop = true;
4992         }
4993 
4994         // Drag-drop payloads are always RGB
4995         if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
4996             ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
4997         EndDragDropTarget();
4998     }
4999 
5000     // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
5001     if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
5002         g.LastItemData.ID = g.ActiveId;
5003 
5004     if (value_changed)
5005         MarkItemEdited(g.LastItemData.ID);
5006 
5007     return value_changed;
5008 }
5009 
ColorPicker3(const char * label,float col[3],ImGuiColorEditFlags flags)5010 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
5011 {
5012     float col4[4] = { col[0], col[1], col[2], 1.0f };
5013     if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
5014         return false;
5015     col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
5016     return true;
5017 }
5018 
5019 // Helper for ColorPicker4()
RenderArrowsForVerticalBar(ImDrawList * draw_list,ImVec2 pos,ImVec2 half_sz,float bar_w,float alpha)5020 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
5021 {
5022     ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
5023     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(0,0,0,alpha8));
5024     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
5025     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(0,0,0,alpha8));
5026     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));
5027 }
5028 
5029 // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5030 // (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)
5031 // 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..)
5032 // FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)
ColorPicker4(const char * label,float col[4],ImGuiColorEditFlags flags,const float * ref_col)5033 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
5034 {
5035     ImGuiContext& g = *GImGui;
5036     ImGuiWindow* window = GetCurrentWindow();
5037     if (window->SkipItems)
5038         return false;
5039 
5040     ImDrawList* draw_list = window->DrawList;
5041     ImGuiStyle& style = g.Style;
5042     ImGuiIO& io = g.IO;
5043 
5044     const float width = CalcItemWidth();
5045     g.NextItemData.ClearFlags();
5046 
5047     PushID(label);
5048     BeginGroup();
5049 
5050     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5051         flags |= ImGuiColorEditFlags_NoSmallPreview;
5052 
5053     // Context menu: display and store options.
5054     if (!(flags & ImGuiColorEditFlags_NoOptions))
5055         ColorPickerOptionsPopup(col, flags);
5056 
5057     // Read stored options
5058     if (!(flags & ImGuiColorEditFlags_PickerMask_))
5059         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
5060     if (!(flags & ImGuiColorEditFlags_InputMask_))
5061         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
5062     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
5063     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected
5064     if (!(flags & ImGuiColorEditFlags_NoOptions))
5065         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
5066 
5067     // Setup
5068     int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
5069     bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
5070     ImVec2 picker_pos = window->DC.CursorPos;
5071     float square_sz = GetFrameHeight();
5072     float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
5073     float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
5074     float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
5075     float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
5076     float bars_triangles_half_sz = IM_FLOOR(bars_width * 0.20f);
5077 
5078     float backup_initial_col[4];
5079     memcpy(backup_initial_col, col, components * sizeof(float));
5080 
5081     float wheel_thickness = sv_picker_size * 0.08f;
5082     float wheel_r_outer = sv_picker_size * 0.50f;
5083     float wheel_r_inner = wheel_r_outer - wheel_thickness;
5084     ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
5085 
5086     // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
5087     float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
5088     ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
5089     ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
5090     ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
5091 
5092     float H = col[0], S = col[1], V = col[2];
5093     float R = col[0], G = col[1], B = col[2];
5094     if (flags & ImGuiColorEditFlags_InputRGB)
5095     {
5096         // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
5097         ColorConvertRGBtoHSV(R, G, B, H, S, V);
5098         if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
5099         {
5100             if (S == 0)
5101                 H = g.ColorEditLastHue;
5102             if (V == 0)
5103                 S = g.ColorEditLastSat;
5104         }
5105     }
5106     else if (flags & ImGuiColorEditFlags_InputHSV)
5107     {
5108         ColorConvertHSVtoRGB(H, S, V, R, G, B);
5109     }
5110 
5111     bool value_changed = false, value_changed_h = false, value_changed_sv = false;
5112 
5113     PushItemFlag(ImGuiItemFlags_NoNav, true);
5114     if (flags & ImGuiColorEditFlags_PickerHueWheel)
5115     {
5116         // Hue wheel + SV triangle logic
5117         InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
5118         if (IsItemActive())
5119         {
5120             ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
5121             ImVec2 current_off = g.IO.MousePos - wheel_center;
5122             float initial_dist2 = ImLengthSqr(initial_off);
5123             if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
5124             {
5125                 // Interactive with Hue wheel
5126                 H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
5127                 if (H < 0.0f)
5128                     H += 1.0f;
5129                 value_changed = value_changed_h = true;
5130             }
5131             float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
5132             float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
5133             if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
5134             {
5135                 // Interacting with SV triangle
5136                 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
5137                 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
5138                     current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
5139                 float uu, vv, ww;
5140                 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
5141                 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
5142                 S = ImClamp(uu / V, 0.0001f, 1.0f);
5143                 value_changed = value_changed_sv = true;
5144             }
5145         }
5146         if (!(flags & ImGuiColorEditFlags_NoOptions))
5147             OpenPopupOnItemClick("context");
5148     }
5149     else if (flags & ImGuiColorEditFlags_PickerHueBar)
5150     {
5151         // SV rectangle logic
5152         InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
5153         if (IsItemActive())
5154         {
5155             S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
5156             V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5157             value_changed = value_changed_sv = true;
5158         }
5159         if (!(flags & ImGuiColorEditFlags_NoOptions))
5160             OpenPopupOnItemClick("context");
5161 
5162         // Hue bar logic
5163         SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
5164         InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
5165         if (IsItemActive())
5166         {
5167             H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5168             value_changed = value_changed_h = true;
5169         }
5170     }
5171 
5172     // Alpha bar logic
5173     if (alpha_bar)
5174     {
5175         SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
5176         InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
5177         if (IsItemActive())
5178         {
5179             col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5180             value_changed = true;
5181         }
5182     }
5183     PopItemFlag(); // ImGuiItemFlags_NoNav
5184 
5185     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5186     {
5187         SameLine(0, style.ItemInnerSpacing.x);
5188         BeginGroup();
5189     }
5190 
5191     if (!(flags & ImGuiColorEditFlags_NoLabel))
5192     {
5193         const char* label_display_end = FindRenderedTextEnd(label);
5194         if (label != label_display_end)
5195         {
5196             if ((flags & ImGuiColorEditFlags_NoSidePreview))
5197                 SameLine(0, style.ItemInnerSpacing.x);
5198             TextEx(label, label_display_end);
5199         }
5200     }
5201 
5202     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5203     {
5204         PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
5205         ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5206         if ((flags & ImGuiColorEditFlags_NoLabel))
5207             Text("Current");
5208 
5209         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
5210         ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
5211         if (ref_col != NULL)
5212         {
5213             Text("Original");
5214             ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
5215             if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
5216             {
5217                 memcpy(col, ref_col, components * sizeof(float));
5218                 value_changed = true;
5219             }
5220         }
5221         PopItemFlag();
5222         EndGroup();
5223     }
5224 
5225     // Convert back color to RGB
5226     if (value_changed_h || value_changed_sv)
5227     {
5228         if (flags & ImGuiColorEditFlags_InputRGB)
5229         {
5230             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]);
5231             g.ColorEditLastHue = H;
5232             g.ColorEditLastSat = S;
5233             memcpy(g.ColorEditLastColor, col, sizeof(float) * 3);
5234         }
5235         else if (flags & ImGuiColorEditFlags_InputHSV)
5236         {
5237             col[0] = H;
5238             col[1] = S;
5239             col[2] = V;
5240         }
5241     }
5242 
5243     // R,G,B and H,S,V slider color editor
5244     bool value_changed_fix_hue_wrap = false;
5245     if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
5246     {
5247         PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
5248         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
5249         ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
5250         if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5251             if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
5252             {
5253                 // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
5254                 // 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)
5255                 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
5256                 value_changed = true;
5257             }
5258         if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5259             value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
5260         if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5261             value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
5262         PopItemWidth();
5263     }
5264 
5265     // Try to cancel hue wrap (after ColorEdit4 call), if any
5266     if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
5267     {
5268         float new_H, new_S, new_V;
5269         ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
5270         if (new_H <= 0 && H > 0)
5271         {
5272             if (new_V <= 0 && V != new_V)
5273                 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
5274             else if (new_S <= 0)
5275                 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
5276         }
5277     }
5278 
5279     if (value_changed)
5280     {
5281         if (flags & ImGuiColorEditFlags_InputRGB)
5282         {
5283             R = col[0];
5284             G = col[1];
5285             B = col[2];
5286             ColorConvertRGBtoHSV(R, G, B, H, S, V);
5287             if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) // Fix local Hue as display below will use it immediately.
5288             {
5289                 if (S == 0)
5290                     H = g.ColorEditLastHue;
5291                 if (V == 0)
5292                     S = g.ColorEditLastSat;
5293             }
5294         }
5295         else if (flags & ImGuiColorEditFlags_InputHSV)
5296         {
5297             H = col[0];
5298             S = col[1];
5299             V = col[2];
5300             ColorConvertHSVtoRGB(H, S, V, R, G, B);
5301         }
5302     }
5303 
5304     const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
5305     const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
5306     const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
5307     const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
5308     const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) };
5309 
5310     ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
5311     ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
5312     ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
5313 
5314     ImVec2 sv_cursor_pos;
5315 
5316     if (flags & ImGuiColorEditFlags_PickerHueWheel)
5317     {
5318         // Render Hue Wheel
5319         const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
5320         const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
5321         for (int n = 0; n < 6; n++)
5322         {
5323             const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
5324             const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
5325             const int vert_start_idx = draw_list->VtxBuffer.Size;
5326             draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
5327             draw_list->PathStroke(col_white, 0, wheel_thickness);
5328             const int vert_end_idx = draw_list->VtxBuffer.Size;
5329 
5330             // Paint colors over existing vertices
5331             ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
5332             ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
5333             ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
5334         }
5335 
5336         // Render Cursor + preview on Hue Wheel
5337         float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
5338         float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
5339         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);
5340         float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
5341         int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
5342         draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
5343         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
5344         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
5345 
5346         // Render SV triangle (rotated according to hue)
5347         ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
5348         ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
5349         ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
5350         ImVec2 uv_white = GetFontTexUvWhitePixel();
5351         draw_list->PrimReserve(6, 6);
5352         draw_list->PrimVtx(tra, uv_white, hue_color32);
5353         draw_list->PrimVtx(trb, uv_white, hue_color32);
5354         draw_list->PrimVtx(trc, uv_white, col_white);
5355         draw_list->PrimVtx(tra, uv_white, 0);
5356         draw_list->PrimVtx(trb, uv_white, col_black);
5357         draw_list->PrimVtx(trc, uv_white, 0);
5358         draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
5359         sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
5360     }
5361     else if (flags & ImGuiColorEditFlags_PickerHueBar)
5362     {
5363         // Render SV Square
5364         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
5365         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
5366         RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
5367         sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S)     * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
5368         sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
5369 
5370         // Render Hue Bar
5371         for (int i = 0; i < 6; ++i)
5372             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)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]);
5373         float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
5374         RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
5375         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, style.Alpha);
5376     }
5377 
5378     // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
5379     float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
5380     draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, 12);
5381     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, 12);
5382     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12);
5383 
5384     // Render alpha bar
5385     if (alpha_bar)
5386     {
5387         float alpha = ImSaturate(col[3]);
5388         ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
5389         RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
5390         draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK);
5391         float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
5392         RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
5393         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, style.Alpha);
5394     }
5395 
5396     EndGroup();
5397 
5398     if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
5399         value_changed = false;
5400     if (value_changed)
5401         MarkItemEdited(g.LastItemData.ID);
5402 
5403     PopID();
5404 
5405     return value_changed;
5406 }
5407 
5408 // A little color square. Return true when clicked.
5409 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
5410 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
5411 // Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
ColorButton(const char * desc_id,const ImVec4 & col,ImGuiColorEditFlags flags,ImVec2 size)5412 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
5413 {
5414     ImGuiWindow* window = GetCurrentWindow();
5415     if (window->SkipItems)
5416         return false;
5417 
5418     ImGuiContext& g = *GImGui;
5419     const ImGuiID id = window->GetID(desc_id);
5420     float default_size = GetFrameHeight();
5421     if (size.x == 0.0f)
5422         size.x = default_size;
5423     if (size.y == 0.0f)
5424         size.y = default_size;
5425     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
5426     ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
5427     if (!ItemAdd(bb, id))
5428         return false;
5429 
5430     bool hovered, held;
5431     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
5432 
5433     if (flags & ImGuiColorEditFlags_NoAlpha)
5434         flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
5435 
5436     ImVec4 col_rgb = col;
5437     if (flags & ImGuiColorEditFlags_InputHSV)
5438         ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
5439 
5440     ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
5441     float grid_step = ImMin(size.x, size.y) / 2.99f;
5442     float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
5443     ImRect bb_inner = bb;
5444     float off = 0.0f;
5445     if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
5446     {
5447         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.
5448         bb_inner.Expand(off);
5449     }
5450     if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
5451     {
5452         float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
5453         RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight);
5454         window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
5455     }
5456     else
5457     {
5458         // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
5459         ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
5460         if (col_source.w < 1.0f)
5461             RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
5462         else
5463             window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
5464     }
5465     RenderNavHighlight(bb, id);
5466     if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
5467     {
5468         if (g.Style.FrameBorderSize > 0.0f)
5469             RenderFrameBorder(bb.Min, bb.Max, rounding);
5470         else
5471             window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
5472     }
5473 
5474     // Drag and Drop Source
5475     // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
5476     if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
5477     {
5478         if (flags & ImGuiColorEditFlags_NoAlpha)
5479             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
5480         else
5481             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
5482         ColorButton(desc_id, col, flags);
5483         SameLine();
5484         TextEx("Color");
5485         EndDragDropSource();
5486     }
5487 
5488     // Tooltip
5489     if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
5490         ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
5491 
5492     return pressed;
5493 }
5494 
5495 // Initialize/override default color options
SetColorEditOptions(ImGuiColorEditFlags flags)5496 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
5497 {
5498     ImGuiContext& g = *GImGui;
5499     if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5500         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
5501     if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
5502         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
5503     if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
5504         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
5505     if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
5506         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
5507     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected
5508     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected
5509     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected
5510     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected
5511     g.ColorEditOptions = flags;
5512 }
5513 
5514 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
ColorTooltip(const char * text,const float * col,ImGuiColorEditFlags flags)5515 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
5516 {
5517     ImGuiContext& g = *GImGui;
5518 
5519     BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip);
5520     const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
5521     if (text_end > text)
5522     {
5523         TextEx(text, text_end);
5524         Separator();
5525     }
5526 
5527     ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
5528     ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5529     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]);
5530     ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
5531     SameLine();
5532     if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
5533     {
5534         if (flags & ImGuiColorEditFlags_NoAlpha)
5535             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]);
5536         else
5537             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]);
5538     }
5539     else if (flags & ImGuiColorEditFlags_InputHSV)
5540     {
5541         if (flags & ImGuiColorEditFlags_NoAlpha)
5542             Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
5543         else
5544             Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
5545     }
5546     EndTooltip();
5547 }
5548 
ColorEditOptionsPopup(const float * col,ImGuiColorEditFlags flags)5549 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
5550 {
5551     bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
5552     bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
5553     if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
5554         return;
5555     ImGuiContext& g = *GImGui;
5556     ImGuiColorEditFlags opts = g.ColorEditOptions;
5557     if (allow_opt_inputs)
5558     {
5559         if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
5560         if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
5561         if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
5562     }
5563     if (allow_opt_datatype)
5564     {
5565         if (allow_opt_inputs) Separator();
5566         if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
5567         if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
5568     }
5569 
5570     if (allow_opt_inputs || allow_opt_datatype)
5571         Separator();
5572     if (Button("Copy as..", ImVec2(-1, 0)))
5573         OpenPopup("Copy");
5574     if (BeginPopup("Copy"))
5575     {
5576         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]);
5577         char buf[64];
5578         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5579         if (Selectable(buf))
5580             SetClipboardText(buf);
5581         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
5582         if (Selectable(buf))
5583             SetClipboardText(buf);
5584         ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
5585         if (Selectable(buf))
5586             SetClipboardText(buf);
5587         if (!(flags & ImGuiColorEditFlags_NoAlpha))
5588         {
5589             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
5590             if (Selectable(buf))
5591                 SetClipboardText(buf);
5592         }
5593         EndPopup();
5594     }
5595 
5596     g.ColorEditOptions = opts;
5597     EndPopup();
5598 }
5599 
ColorPickerOptionsPopup(const float * ref_col,ImGuiColorEditFlags flags)5600 void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
5601 {
5602     bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
5603     bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
5604     if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
5605         return;
5606     ImGuiContext& g = *GImGui;
5607     if (allow_opt_picker)
5608     {
5609         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
5610         PushItemWidth(picker_size.x);
5611         for (int picker_type = 0; picker_type < 2; picker_type++)
5612         {
5613             // Draw small/thumbnail version of each picker type (over an invisible button for selection)
5614             if (picker_type > 0) Separator();
5615             PushID(picker_type);
5616             ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
5617             if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
5618             if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
5619             ImVec2 backup_pos = GetCursorScreenPos();
5620             if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
5621                 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
5622             SetCursorScreenPos(backup_pos);
5623             ImVec4 previewing_ref_col;
5624             memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
5625             ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
5626             PopID();
5627         }
5628         PopItemWidth();
5629     }
5630     if (allow_opt_alpha_bar)
5631     {
5632         if (allow_opt_picker) Separator();
5633         CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
5634     }
5635     EndPopup();
5636 }
5637 
5638 //-------------------------------------------------------------------------
5639 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
5640 //-------------------------------------------------------------------------
5641 // - TreeNode()
5642 // - TreeNodeV()
5643 // - TreeNodeEx()
5644 // - TreeNodeExV()
5645 // - TreeNodeBehavior() [Internal]
5646 // - TreePush()
5647 // - TreePop()
5648 // - GetTreeNodeToLabelSpacing()
5649 // - SetNextItemOpen()
5650 // - CollapsingHeader()
5651 //-------------------------------------------------------------------------
5652 
TreeNode(const char * str_id,const char * fmt,...)5653 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
5654 {
5655     va_list args;
5656     va_start(args, fmt);
5657     bool is_open = TreeNodeExV(str_id, 0, fmt, args);
5658     va_end(args);
5659     return is_open;
5660 }
5661 
TreeNode(const void * ptr_id,const char * fmt,...)5662 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
5663 {
5664     va_list args;
5665     va_start(args, fmt);
5666     bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
5667     va_end(args);
5668     return is_open;
5669 }
5670 
TreeNode(const char * label)5671 bool ImGui::TreeNode(const char* label)
5672 {
5673     ImGuiWindow* window = GetCurrentWindow();
5674     if (window->SkipItems)
5675         return false;
5676     return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
5677 }
5678 
TreeNodeV(const char * str_id,const char * fmt,va_list args)5679 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
5680 {
5681     return TreeNodeExV(str_id, 0, fmt, args);
5682 }
5683 
TreeNodeV(const void * ptr_id,const char * fmt,va_list args)5684 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
5685 {
5686     return TreeNodeExV(ptr_id, 0, fmt, args);
5687 }
5688 
TreeNodeEx(const char * label,ImGuiTreeNodeFlags flags)5689 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
5690 {
5691     ImGuiWindow* window = GetCurrentWindow();
5692     if (window->SkipItems)
5693         return false;
5694 
5695     return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
5696 }
5697 
TreeNodeEx(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,...)5698 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
5699 {
5700     va_list args;
5701     va_start(args, fmt);
5702     bool is_open = TreeNodeExV(str_id, flags, fmt, args);
5703     va_end(args);
5704     return is_open;
5705 }
5706 
TreeNodeEx(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,...)5707 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
5708 {
5709     va_list args;
5710     va_start(args, fmt);
5711     bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
5712     va_end(args);
5713     return is_open;
5714 }
5715 
TreeNodeExV(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)5716 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
5717 {
5718     ImGuiWindow* window = GetCurrentWindow();
5719     if (window->SkipItems)
5720         return false;
5721 
5722     ImGuiContext& g = *GImGui;
5723     const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5724     return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
5725 }
5726 
TreeNodeExV(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)5727 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
5728 {
5729     ImGuiWindow* window = GetCurrentWindow();
5730     if (window->SkipItems)
5731         return false;
5732 
5733     ImGuiContext& g = *GImGui;
5734     const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5735     return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
5736 }
5737 
TreeNodeBehaviorIsOpen(ImGuiID id,ImGuiTreeNodeFlags flags)5738 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
5739 {
5740     if (flags & ImGuiTreeNodeFlags_Leaf)
5741         return true;
5742 
5743     // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function)
5744     ImGuiContext& g = *GImGui;
5745     ImGuiWindow* window = g.CurrentWindow;
5746     ImGuiStorage* storage = window->DC.StateStorage;
5747 
5748     bool is_open;
5749     if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen)
5750     {
5751         if (g.NextItemData.OpenCond & ImGuiCond_Always)
5752         {
5753             is_open = g.NextItemData.OpenVal;
5754             storage->SetInt(id, is_open);
5755         }
5756         else
5757         {
5758             // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
5759             const int stored_value = storage->GetInt(id, -1);
5760             if (stored_value == -1)
5761             {
5762                 is_open = g.NextItemData.OpenVal;
5763                 storage->SetInt(id, is_open);
5764             }
5765             else
5766             {
5767                 is_open = stored_value != 0;
5768             }
5769         }
5770     }
5771     else
5772     {
5773         is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
5774     }
5775 
5776     // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
5777     // NB- If we are above max depth we still allow manually opened nodes to be logged.
5778     if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
5779         is_open = true;
5780 
5781     return is_open;
5782 }
5783 
TreeNodeBehavior(ImGuiID id,ImGuiTreeNodeFlags flags,const char * label,const char * label_end)5784 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
5785 {
5786     ImGuiWindow* window = GetCurrentWindow();
5787     if (window->SkipItems)
5788         return false;
5789 
5790     ImGuiContext& g = *GImGui;
5791     const ImGuiStyle& style = g.Style;
5792     const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
5793     const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
5794 
5795     if (!label_end)
5796         label_end = FindRenderedTextEnd(label);
5797     const ImVec2 label_size = CalcTextSize(label, label_end, false);
5798 
5799     // We vertically grow up to current line height up the typical widget height.
5800     const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
5801     ImRect frame_bb;
5802     frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
5803     frame_bb.Min.y = window->DC.CursorPos.y;
5804     frame_bb.Max.x = window->WorkRect.Max.x;
5805     frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
5806     if (display_frame)
5807     {
5808         // Framed header expand a little outside the default padding, to the edge of InnerClipRect
5809         // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f)
5810         frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f);
5811         frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f);
5812     }
5813 
5814     const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);           // Collapser arrow width + Spacing
5815     const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);                    // Latch before ItemSize changes it
5816     const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f);  // Include collapser
5817     ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
5818     ItemSize(ImVec2(text_width, frame_height), padding.y);
5819 
5820     // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
5821     ImRect interact_bb = frame_bb;
5822     if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0)
5823         interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f;
5824 
5825     // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
5826     // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
5827     // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
5828     const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
5829     bool is_open = TreeNodeBehaviorIsOpen(id, flags);
5830     if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5831         window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
5832 
5833     bool item_add = ItemAdd(interact_bb, id);
5834     g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
5835     g.LastItemData.DisplayRect = frame_bb;
5836 
5837     if (!item_add)
5838     {
5839         if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5840             TreePushOverrideID(id);
5841         IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
5842         return is_open;
5843     }
5844 
5845     ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
5846     if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
5847         button_flags |= ImGuiButtonFlags_AllowItemOverlap;
5848     if (!is_leaf)
5849         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
5850 
5851     // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
5852     // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
5853     // When clicking on the rest of the tree node we always disallow keyboard modifiers.
5854     const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
5855     const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
5856     const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
5857     if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
5858         button_flags |= ImGuiButtonFlags_NoKeyModifiers;
5859 
5860     // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
5861     // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
5862     // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
5863     // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
5864     // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
5865     // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
5866     // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
5867     // It is rather standard that arrow click react on Down rather than Up.
5868     // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work.
5869     if (is_mouse_x_over_arrow)
5870         button_flags |= ImGuiButtonFlags_PressedOnClick;
5871     else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
5872         button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
5873     else
5874         button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
5875 
5876     bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
5877     const bool was_selected = selected;
5878 
5879     bool hovered, held;
5880     bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
5881     bool toggled = false;
5882     if (!is_leaf)
5883     {
5884         if (pressed && g.DragDropHoldJustPressedId != id)
5885         {
5886             if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
5887                 toggled = true;
5888             if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
5889                 toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
5890             if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0])
5891                 toggled = true;
5892         }
5893         else if (pressed && g.DragDropHoldJustPressedId == id)
5894         {
5895             IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
5896             if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
5897                 toggled = true;
5898         }
5899 
5900         if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
5901         {
5902             toggled = true;
5903             NavMoveRequestCancel();
5904         }
5905         if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
5906         {
5907             toggled = true;
5908             NavMoveRequestCancel();
5909         }
5910 
5911         if (toggled)
5912         {
5913             is_open = !is_open;
5914             window->DC.StateStorage->SetInt(id, is_open);
5915             g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
5916         }
5917     }
5918     if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
5919         SetItemAllowOverlap();
5920 
5921     // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
5922     if (selected != was_selected) //-V547
5923         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
5924 
5925     // Render
5926     const ImU32 text_col = GetColorU32(ImGuiCol_Text);
5927     ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
5928     if (display_frame)
5929     {
5930         // Framed type
5931         const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5932         RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
5933         RenderNavHighlight(frame_bb, id, nav_highlight_flags);
5934         if (flags & ImGuiTreeNodeFlags_Bullet)
5935             RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
5936         else if (!is_leaf)
5937             RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
5938         else // Leaf without bullet, left-adjusted text
5939             text_pos.x -= text_offset_x;
5940         if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
5941             frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
5942 
5943         if (g.LogEnabled)
5944             LogSetNextTextDecoration("###", "###");
5945         RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
5946     }
5947     else
5948     {
5949         // Unframed typed for tree nodes
5950         if (hovered || selected)
5951         {
5952             const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5953             RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
5954         }
5955         RenderNavHighlight(frame_bb, id, nav_highlight_flags);
5956         if (flags & ImGuiTreeNodeFlags_Bullet)
5957             RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
5958         else if (!is_leaf)
5959             RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
5960         if (g.LogEnabled)
5961             LogSetNextTextDecoration(">", NULL);
5962         RenderText(text_pos, label, label_end, false);
5963     }
5964 
5965     if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5966         TreePushOverrideID(id);
5967     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
5968     return is_open;
5969 }
5970 
TreePush(const char * str_id)5971 void ImGui::TreePush(const char* str_id)
5972 {
5973     ImGuiWindow* window = GetCurrentWindow();
5974     Indent();
5975     window->DC.TreeDepth++;
5976     PushID(str_id ? str_id : "#TreePush");
5977 }
5978 
TreePush(const void * ptr_id)5979 void ImGui::TreePush(const void* ptr_id)
5980 {
5981     ImGuiWindow* window = GetCurrentWindow();
5982     Indent();
5983     window->DC.TreeDepth++;
5984     PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
5985 }
5986 
TreePushOverrideID(ImGuiID id)5987 void ImGui::TreePushOverrideID(ImGuiID id)
5988 {
5989     ImGuiContext& g = *GImGui;
5990     ImGuiWindow* window = g.CurrentWindow;
5991     Indent();
5992     window->DC.TreeDepth++;
5993     window->IDStack.push_back(id);
5994 }
5995 
TreePop()5996 void ImGui::TreePop()
5997 {
5998     ImGuiContext& g = *GImGui;
5999     ImGuiWindow* window = g.CurrentWindow;
6000     Unindent();
6001 
6002     window->DC.TreeDepth--;
6003     ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
6004 
6005     // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
6006     if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6007         if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask))
6008         {
6009             SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect());
6010             NavMoveRequestCancel();
6011         }
6012     window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1;
6013 
6014     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.
6015     PopID();
6016 }
6017 
6018 // Horizontal distance preceding label when using TreeNode() or Bullet()
GetTreeNodeToLabelSpacing()6019 float ImGui::GetTreeNodeToLabelSpacing()
6020 {
6021     ImGuiContext& g = *GImGui;
6022     return g.FontSize + (g.Style.FramePadding.x * 2.0f);
6023 }
6024 
6025 // Set next TreeNode/CollapsingHeader open state.
SetNextItemOpen(bool is_open,ImGuiCond cond)6026 void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
6027 {
6028     ImGuiContext& g = *GImGui;
6029     if (g.CurrentWindow->SkipItems)
6030         return;
6031     g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen;
6032     g.NextItemData.OpenVal = is_open;
6033     g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always;
6034 }
6035 
6036 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
6037 // 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)6038 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
6039 {
6040     ImGuiWindow* window = GetCurrentWindow();
6041     if (window->SkipItems)
6042         return false;
6043 
6044     return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
6045 }
6046 
6047 // p_visible == NULL                        : regular collapsing header
6048 // p_visible != NULL && *p_visible == true  : show a small close button on the corner of the header, clicking the button will set *p_visible = false
6049 // p_visible != NULL && *p_visible == false : do not show the header at all
6050 // Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
CollapsingHeader(const char * label,bool * p_visible,ImGuiTreeNodeFlags flags)6051 bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
6052 {
6053     ImGuiWindow* window = GetCurrentWindow();
6054     if (window->SkipItems)
6055         return false;
6056 
6057     if (p_visible && !*p_visible)
6058         return false;
6059 
6060     ImGuiID id = window->GetID(label);
6061     flags |= ImGuiTreeNodeFlags_CollapsingHeader;
6062     if (p_visible)
6063         flags |= ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
6064     bool is_open = TreeNodeBehavior(id, flags, label);
6065     if (p_visible != NULL)
6066     {
6067         // Create a small overlapping close button
6068         // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
6069         // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
6070         ImGuiContext& g = *GImGui;
6071         ImGuiLastItemData last_item_backup = g.LastItemData;
6072         float button_size = g.FontSize;
6073         float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
6074         float button_y = g.LastItemData.Rect.Min.y;
6075         ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
6076         if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
6077             *p_visible = false;
6078         g.LastItemData = last_item_backup;
6079     }
6080 
6081     return is_open;
6082 }
6083 
6084 //-------------------------------------------------------------------------
6085 // [SECTION] Widgets: Selectable
6086 //-------------------------------------------------------------------------
6087 // - Selectable()
6088 //-------------------------------------------------------------------------
6089 
6090 // Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
6091 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
6092 // With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowItemOverlap are also frequently used flags.
6093 // FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
Selectable(const char * label,bool selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)6094 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6095 {
6096     ImGuiWindow* window = GetCurrentWindow();
6097     if (window->SkipItems)
6098         return false;
6099 
6100     ImGuiContext& g = *GImGui;
6101     const ImGuiStyle& style = g.Style;
6102 
6103     // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
6104     ImGuiID id = window->GetID(label);
6105     ImVec2 label_size = CalcTextSize(label, NULL, true);
6106     ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
6107     ImVec2 pos = window->DC.CursorPos;
6108     pos.y += window->DC.CurrLineTextBaseOffset;
6109     ItemSize(size, 0.0f);
6110 
6111     // Fill horizontal space
6112     // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
6113     const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
6114     const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
6115     const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
6116     if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
6117         size.x = ImMax(label_size.x, max_x - min_x);
6118 
6119     // Text stays at the submission position, but bounding box may be extended on both sides
6120     const ImVec2 text_min = pos;
6121     const ImVec2 text_max(min_x + size.x, pos.y + size.y);
6122 
6123     // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
6124     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
6125     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
6126     {
6127         const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
6128         const float spacing_y = style.ItemSpacing.y;
6129         const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
6130         const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
6131         bb.Min.x -= spacing_L;
6132         bb.Min.y -= spacing_U;
6133         bb.Max.x += (spacing_x - spacing_L);
6134         bb.Max.y += (spacing_y - spacing_U);
6135     }
6136     //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
6137 
6138     // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable..
6139     const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6140     const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6141     if (span_all_columns)
6142     {
6143         window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6144         window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6145     }
6146 
6147     bool item_add;
6148     const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
6149     if (disabled_item)
6150     {
6151         ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
6152         g.CurrentItemFlags |= ImGuiItemFlags_Disabled;
6153         item_add = ItemAdd(bb, id);
6154         g.CurrentItemFlags = backup_item_flags;
6155     }
6156     else
6157     {
6158         item_add = ItemAdd(bb, id);
6159     }
6160 
6161     if (span_all_columns)
6162     {
6163         window->ClipRect.Min.x = backup_clip_rect_min_x;
6164         window->ClipRect.Max.x = backup_clip_rect_max_x;
6165     }
6166 
6167     if (!item_add)
6168         return false;
6169 
6170     const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
6171     if (disabled_item && !disabled_global) // Only testing this as an optimization
6172         BeginDisabled(true);
6173 
6174     // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
6175     // which would be advantageous since most selectable are not selected.
6176     if (span_all_columns && window->DC.CurrentColumns)
6177         PushColumnsBackground();
6178     else if (span_all_columns && g.CurrentTable)
6179         TablePushBackgroundChannel();
6180 
6181     // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
6182     ImGuiButtonFlags button_flags = 0;
6183     if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
6184     if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
6185     if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
6186     if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
6187     if (flags & ImGuiSelectableFlags_AllowItemOverlap)  { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
6188 
6189     const bool was_selected = selected;
6190     bool hovered, held;
6191     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
6192 
6193     // Auto-select when moved into
6194     // - This will be more fully fleshed in the range-select branch
6195     // - This is not exposed as it won't nicely work with some user side handling of shift/control
6196     // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
6197     //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
6198     //   - (2) usage will fail with clipped items
6199     //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
6200     if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent)
6201         if (g.NavJustMovedToId == id)
6202             selected = pressed = true;
6203 
6204     // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
6205     if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
6206     {
6207         if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
6208         {
6209             SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos));
6210             g.NavDisableHighlight = true;
6211         }
6212     }
6213     if (pressed)
6214         MarkItemEdited(id);
6215 
6216     if (flags & ImGuiSelectableFlags_AllowItemOverlap)
6217         SetItemAllowOverlap();
6218 
6219     // In this branch, Selectable() cannot toggle the selection so this will never trigger.
6220     if (selected != was_selected) //-V547
6221         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
6222 
6223     // Render
6224     if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld))
6225         hovered = true;
6226     if (hovered || selected)
6227     {
6228         const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6229         RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
6230     }
6231     RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
6232 
6233     if (span_all_columns && window->DC.CurrentColumns)
6234         PopColumnsBackground();
6235     else if (span_all_columns && g.CurrentTable)
6236         TablePopBackgroundChannel();
6237 
6238     RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
6239 
6240     // Automatically close popups
6241     if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup))
6242         CloseCurrentPopup();
6243 
6244     if (disabled_item && !disabled_global)
6245         EndDisabled();
6246 
6247     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
6248     return pressed; //-V1020
6249 }
6250 
Selectable(const char * label,bool * p_selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)6251 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6252 {
6253     if (Selectable(label, *p_selected, flags, size_arg))
6254     {
6255         *p_selected = !*p_selected;
6256         return true;
6257     }
6258     return false;
6259 }
6260 
6261 //-------------------------------------------------------------------------
6262 // [SECTION] Widgets: ListBox
6263 //-------------------------------------------------------------------------
6264 // - BeginListBox()
6265 // - EndListBox()
6266 // - ListBox()
6267 //-------------------------------------------------------------------------
6268 
6269 // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
6270 // Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height).
BeginListBox(const char * label,const ImVec2 & size_arg)6271 bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
6272 {
6273     ImGuiContext& g = *GImGui;
6274     ImGuiWindow* window = GetCurrentWindow();
6275     if (window->SkipItems)
6276         return false;
6277 
6278     const ImGuiStyle& style = g.Style;
6279     const ImGuiID id = GetID(label);
6280     const ImVec2 label_size = CalcTextSize(label, NULL, true);
6281 
6282     // Size default to hold ~7.25 items.
6283     // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
6284     ImVec2 size = ImFloor(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
6285     ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
6286     ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
6287     ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
6288     g.NextItemData.ClearFlags();
6289 
6290     if (!IsRectVisible(bb.Min, bb.Max))
6291     {
6292         ItemSize(bb.GetSize(), style.FramePadding.y);
6293         ItemAdd(bb, 0, &frame_bb);
6294         return false;
6295     }
6296 
6297     // FIXME-OPT: We could omit the BeginGroup() if label_size.x but would need to omit the EndGroup() as well.
6298     BeginGroup();
6299     if (label_size.x > 0.0f)
6300     {
6301         ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
6302         RenderText(label_pos, label);
6303         window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
6304     }
6305 
6306     BeginChildFrame(id, frame_bb.GetSize());
6307     return true;
6308 }
6309 
6310 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
6311 // OBSOLETED in 1.81 (from February 2021)
ListBoxHeader(const char * label,int items_count,int height_in_items)6312 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
6313 {
6314     // If height_in_items == -1, default height is maximum 7.
6315     ImGuiContext& g = *GImGui;
6316     float height_in_items_f = (height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f;
6317     ImVec2 size;
6318     size.x = 0.0f;
6319     size.y = GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f;
6320     return BeginListBox(label, size);
6321 }
6322 #endif
6323 
EndListBox()6324 void ImGui::EndListBox()
6325 {
6326     ImGuiContext& g = *GImGui;
6327     ImGuiWindow* window = g.CurrentWindow;
6328     IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
6329     IM_UNUSED(window);
6330 
6331     EndChildFrame();
6332     EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
6333 }
6334 
ListBox(const char * label,int * current_item,const char * const items[],int items_count,int height_items)6335 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
6336 {
6337     const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
6338     return value_changed;
6339 }
6340 
6341 // This is merely a helper around BeginListBox(), EndListBox().
6342 // Considering using those directly to submit custom data or store selection differently.
ListBox(const char * label,int * current_item,bool (* items_getter)(void *,int,const char **),void * data,int items_count,int height_in_items)6343 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)
6344 {
6345     ImGuiContext& g = *GImGui;
6346 
6347     // Calculate size from "height_in_items"
6348     if (height_in_items < 0)
6349         height_in_items = ImMin(items_count, 7);
6350     float height_in_items_f = height_in_items + 0.25f;
6351     ImVec2 size(0.0f, ImFloor(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
6352 
6353     if (!BeginListBox(label, size))
6354         return false;
6355 
6356     // Assume all items have even height (= 1 line of text). If you need items of different height,
6357     // you can create a custom version of ListBox() in your code without using the clipper.
6358     bool value_changed = false;
6359     ImGuiListClipper clipper;
6360     clipper.Begin(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.
6361     while (clipper.Step())
6362         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
6363         {
6364             const char* item_text;
6365             if (!items_getter(data, i, &item_text))
6366                 item_text = "*Unknown item*";
6367 
6368             PushID(i);
6369             const bool item_selected = (i == *current_item);
6370             if (Selectable(item_text, item_selected))
6371             {
6372                 *current_item = i;
6373                 value_changed = true;
6374             }
6375             if (item_selected)
6376                 SetItemDefaultFocus();
6377             PopID();
6378         }
6379     EndListBox();
6380 
6381     if (value_changed)
6382         MarkItemEdited(g.LastItemData.ID);
6383 
6384     return value_changed;
6385 }
6386 
6387 //-------------------------------------------------------------------------
6388 // [SECTION] Widgets: PlotLines, PlotHistogram
6389 //-------------------------------------------------------------------------
6390 // - PlotEx() [Internal]
6391 // - PlotLines()
6392 // - PlotHistogram()
6393 //-------------------------------------------------------------------------
6394 // Plot/Graph widgets are not very good.
6395 // Consider writing your own, or using a third-party one, see:
6396 // - ImPlot https://github.com/epezent/implot
6397 // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
6398 //-------------------------------------------------------------------------
6399 
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)6400 int 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)
6401 {
6402     ImGuiContext& g = *GImGui;
6403     ImGuiWindow* window = GetCurrentWindow();
6404     if (window->SkipItems)
6405         return -1;
6406 
6407     const ImGuiStyle& style = g.Style;
6408     const ImGuiID id = window->GetID(label);
6409 
6410     const ImVec2 label_size = CalcTextSize(label, NULL, true);
6411     if (frame_size.x == 0.0f)
6412         frame_size.x = CalcItemWidth();
6413     if (frame_size.y == 0.0f)
6414         frame_size.y = label_size.y + (style.FramePadding.y * 2);
6415 
6416     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
6417     const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
6418     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));
6419     ItemSize(total_bb, style.FramePadding.y);
6420     if (!ItemAdd(total_bb, 0, &frame_bb))
6421         return -1;
6422     const bool hovered = ItemHoverable(frame_bb, id);
6423 
6424     // Determine scale from values if not specified
6425     if (scale_min == FLT_MAX || scale_max == FLT_MAX)
6426     {
6427         float v_min = FLT_MAX;
6428         float v_max = -FLT_MAX;
6429         for (int i = 0; i < values_count; i++)
6430         {
6431             const float v = values_getter(data, i);
6432             if (v != v) // Ignore NaN values
6433                 continue;
6434             v_min = ImMin(v_min, v);
6435             v_max = ImMax(v_max, v);
6436         }
6437         if (scale_min == FLT_MAX)
6438             scale_min = v_min;
6439         if (scale_max == FLT_MAX)
6440             scale_max = v_max;
6441     }
6442 
6443     RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
6444 
6445     const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
6446     int idx_hovered = -1;
6447     if (values_count >= values_count_min)
6448     {
6449         int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
6450         int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
6451 
6452         // Tooltip on hover
6453         if (hovered && inner_bb.Contains(g.IO.MousePos))
6454         {
6455             const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
6456             const int v_idx = (int)(t * item_count);
6457             IM_ASSERT(v_idx >= 0 && v_idx < values_count);
6458 
6459             const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
6460             const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
6461             if (plot_type == ImGuiPlotType_Lines)
6462                 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
6463             else if (plot_type == ImGuiPlotType_Histogram)
6464                 SetTooltip("%d: %8.4g", v_idx, v0);
6465             idx_hovered = v_idx;
6466         }
6467 
6468         const float t_step = 1.0f / (float)res_w;
6469         const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
6470 
6471         float v0 = values_getter(data, (0 + values_offset) % values_count);
6472         float t0 = 0.0f;
6473         ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
6474         float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands
6475 
6476         const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
6477         const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
6478 
6479         for (int n = 0; n < res_w; n++)
6480         {
6481             const float t1 = t0 + t_step;
6482             const int v1_idx = (int)(t0 * item_count + 0.5f);
6483             IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
6484             const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
6485             const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
6486 
6487             // 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.
6488             ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
6489             ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
6490             if (plot_type == ImGuiPlotType_Lines)
6491             {
6492                 window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
6493             }
6494             else if (plot_type == ImGuiPlotType_Histogram)
6495             {
6496                 if (pos1.x >= pos0.x + 2.0f)
6497                     pos1.x -= 1.0f;
6498                 window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
6499             }
6500 
6501             t0 = t1;
6502             tp0 = tp1;
6503         }
6504     }
6505 
6506     // Text overlay
6507     if (overlay_text)
6508         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));
6509 
6510     if (label_size.x > 0.0f)
6511         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
6512 
6513     // Return hovered index or -1 if none are hovered.
6514     // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().
6515     return idx_hovered;
6516 }
6517 
6518 struct ImGuiPlotArrayGetterData
6519 {
6520     const float* Values;
6521     int Stride;
6522 
ImGuiPlotArrayGetterDataImGuiPlotArrayGetterData6523     ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
6524 };
6525 
Plot_ArrayGetter(void * data,int idx)6526 static float Plot_ArrayGetter(void* data, int idx)
6527 {
6528     ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
6529     const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
6530     return v;
6531 }
6532 
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)6533 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)
6534 {
6535     ImGuiPlotArrayGetterData data(values, stride);
6536     PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6537 }
6538 
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)6539 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)
6540 {
6541     PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6542 }
6543 
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)6544 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)
6545 {
6546     ImGuiPlotArrayGetterData data(values, stride);
6547     PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6548 }
6549 
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)6550 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)
6551 {
6552     PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6553 }
6554 
6555 //-------------------------------------------------------------------------
6556 // [SECTION] Widgets: Value helpers
6557 // Those is not very useful, legacy API.
6558 //-------------------------------------------------------------------------
6559 // - Value()
6560 //-------------------------------------------------------------------------
6561 
Value(const char * prefix,bool b)6562 void ImGui::Value(const char* prefix, bool b)
6563 {
6564     Text("%s: %s", prefix, (b ? "true" : "false"));
6565 }
6566 
Value(const char * prefix,int v)6567 void ImGui::Value(const char* prefix, int v)
6568 {
6569     Text("%s: %d", prefix, v);
6570 }
6571 
Value(const char * prefix,unsigned int v)6572 void ImGui::Value(const char* prefix, unsigned int v)
6573 {
6574     Text("%s: %d", prefix, v);
6575 }
6576 
Value(const char * prefix,float v,const char * float_format)6577 void ImGui::Value(const char* prefix, float v, const char* float_format)
6578 {
6579     if (float_format)
6580     {
6581         char fmt[64];
6582         ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
6583         Text(fmt, prefix, v);
6584     }
6585     else
6586     {
6587         Text("%s: %.3f", prefix, v);
6588     }
6589 }
6590 
6591 //-------------------------------------------------------------------------
6592 // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
6593 //-------------------------------------------------------------------------
6594 // - ImGuiMenuColumns [Internal]
6595 // - BeginMenuBar()
6596 // - EndMenuBar()
6597 // - BeginMainMenuBar()
6598 // - EndMainMenuBar()
6599 // - BeginMenu()
6600 // - EndMenu()
6601 // - MenuItemEx() [Internal]
6602 // - MenuItem()
6603 //-------------------------------------------------------------------------
6604 
6605 // Helpers for internal use
Update(float spacing,bool window_reappearing)6606 void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
6607 {
6608     if (window_reappearing)
6609         memset(Widths, 0, sizeof(Widths));
6610     Spacing = (ImU16)spacing;
6611     CalcNextTotalWidth(true);
6612     memset(Widths, 0, sizeof(Widths));
6613     TotalWidth = NextTotalWidth;
6614     NextTotalWidth = 0;
6615 }
6616 
CalcNextTotalWidth(bool update_offsets)6617 void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
6618 {
6619     ImU16 offset = 0;
6620     bool want_spacing = false;
6621     for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
6622     {
6623         ImU16 width = Widths[i];
6624         if (want_spacing && width > 0)
6625             offset += Spacing;
6626         want_spacing |= (width > 0);
6627         if (update_offsets)
6628         {
6629             if (i == 1) { OffsetLabel = offset; }
6630             if (i == 2) { OffsetShortcut = offset; }
6631             if (i == 3) { OffsetMark = offset; }
6632         }
6633         offset += width;
6634     }
6635     NextTotalWidth = offset;
6636 }
6637 
DeclColumns(float w_icon,float w_label,float w_shortcut,float w_mark)6638 float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
6639 {
6640     Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
6641     Widths[1] = ImMax(Widths[1], (ImU16)w_label);
6642     Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
6643     Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
6644     CalcNextTotalWidth(false);
6645     return (float)ImMax(TotalWidth, NextTotalWidth);
6646 }
6647 
6648 // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
6649 // Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
6650 // Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.
6651 // Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
BeginMenuBar()6652 bool ImGui::BeginMenuBar()
6653 {
6654     ImGuiWindow* window = GetCurrentWindow();
6655     if (window->SkipItems)
6656         return false;
6657     if (!(window->Flags & ImGuiWindowFlags_MenuBar))
6658         return false;
6659 
6660     IM_ASSERT(!window->DC.MenuBarAppending);
6661     BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
6662     PushID("##menubar");
6663 
6664     // 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.
6665     // 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.
6666     ImRect bar_rect = window->MenuBarRect();
6667     ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y));
6668     clip_rect.ClipWith(window->OuterRectClipped);
6669     PushClipRect(clip_rect.Min, clip_rect.Max, false);
6670 
6671     // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).
6672     window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
6673     window->DC.LayoutType = ImGuiLayoutType_Horizontal;
6674     window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
6675     window->DC.MenuBarAppending = true;
6676     AlignTextToFramePadding();
6677     return true;
6678 }
6679 
EndMenuBar()6680 void ImGui::EndMenuBar()
6681 {
6682     ImGuiWindow* window = GetCurrentWindow();
6683     if (window->SkipItems)
6684         return;
6685     ImGuiContext& g = *GImGui;
6686 
6687     // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
6688     if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
6689     {
6690         // Try to find out if the request is for one of our child menu
6691         ImGuiWindow* nav_earliest_child = g.NavWindow;
6692         while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
6693             nav_earliest_child = nav_earliest_child->ParentWindow;
6694         if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
6695         {
6696             // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
6697             // 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 bothering)
6698             const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
6699             IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check
6700             FocusWindow(window);
6701             SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
6702             g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
6703             g.NavDisableMouseHover = g.NavMousePosDirty = true;
6704             NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags); // Repeat
6705         }
6706     }
6707 
6708     IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
6709     IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
6710     IM_ASSERT(window->DC.MenuBarAppending);
6711     PopClipRect();
6712     PopID();
6713     window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
6714     g.GroupStack.back().EmitItem = false;
6715     EndGroup(); // Restore position on layer 0
6716     window->DC.LayoutType = ImGuiLayoutType_Vertical;
6717     window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
6718     window->DC.MenuBarAppending = false;
6719 }
6720 
6721 // Important: calling order matters!
6722 // FIXME: Somehow overlapping with docking tech.
6723 // FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
BeginViewportSideBar(const char * name,ImGuiViewport * viewport_p,ImGuiDir dir,float axis_size,ImGuiWindowFlags window_flags)6724 bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
6725 {
6726     IM_ASSERT(dir != ImGuiDir_None);
6727 
6728     ImGuiWindow* bar_window = FindWindowByName(name);
6729     if (bar_window == NULL || bar_window->BeginCount == 0)
6730     {
6731         // Calculate and set window size/position
6732         ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
6733         ImRect avail_rect = viewport->GetBuildWorkRect();
6734         ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
6735         ImVec2 pos = avail_rect.Min;
6736         if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
6737             pos[axis] = avail_rect.Max[axis] - axis_size;
6738         ImVec2 size = avail_rect.GetSize();
6739         size[axis] = axis_size;
6740         SetNextWindowPos(pos);
6741         SetNextWindowSize(size);
6742 
6743         // Report our size into work area (for next frame) using actual window size
6744         if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
6745             viewport->BuildWorkOffsetMin[axis] += axis_size;
6746         else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
6747             viewport->BuildWorkOffsetMax[axis] -= axis_size;
6748     }
6749 
6750     window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
6751     PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
6752     PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
6753     bool is_open = Begin(name, NULL, window_flags);
6754     PopStyleVar(2);
6755 
6756     return is_open;
6757 }
6758 
BeginMainMenuBar()6759 bool ImGui::BeginMainMenuBar()
6760 {
6761     ImGuiContext& g = *GImGui;
6762     ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
6763 
6764     // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
6765     // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
6766     // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
6767     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
6768     ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
6769     float height = GetFrameHeight();
6770     bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
6771     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
6772 
6773     if (is_open)
6774         BeginMenuBar();
6775     else
6776         End();
6777     return is_open;
6778 }
6779 
EndMainMenuBar()6780 void ImGui::EndMainMenuBar()
6781 {
6782     EndMenuBar();
6783 
6784     // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
6785     // FIXME: With this strategy we won't be able to restore a NULL focus.
6786     ImGuiContext& g = *GImGui;
6787     if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
6788         FocusTopMostWindowUnderOne(g.NavWindow, NULL);
6789 
6790     End();
6791 }
6792 
BeginMenuEx(const char * label,const char * icon,bool enabled)6793 bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
6794 {
6795     ImGuiWindow* window = GetCurrentWindow();
6796     if (window->SkipItems)
6797         return false;
6798 
6799     ImGuiContext& g = *GImGui;
6800     const ImGuiStyle& style = g.Style;
6801     const ImGuiID id = window->GetID(label);
6802     bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
6803 
6804     // 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)
6805     ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
6806     if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu))
6807         flags |= ImGuiWindowFlags_ChildWindow;
6808 
6809     // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
6810     // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame.
6811     // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
6812     if (g.MenusIdSubmittedThisFrame.contains(id))
6813     {
6814         if (menu_is_open)
6815             menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
6816         else
6817             g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
6818         return menu_is_open;
6819     }
6820 
6821     // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
6822     g.MenusIdSubmittedThisFrame.push_back(id);
6823 
6824     ImVec2 label_size = CalcTextSize(label, NULL, true);
6825     bool pressed;
6826     bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
6827     ImGuiWindow* backed_nav_window = g.NavWindow;
6828     if (menuset_is_open)
6829         g.NavWindow = window;  // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
6830 
6831     // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
6832     // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
6833     // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
6834     ImVec2 popup_pos, pos = window->DC.CursorPos;
6835     PushID(label);
6836     if (!enabled)
6837         BeginDisabled();
6838     const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
6839     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
6840     {
6841         // Menu inside an horizontal menu bar
6842         // Selectable extend their highlight by half ItemSpacing in each direction.
6843         // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
6844         popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
6845         window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
6846         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
6847         float w = label_size.x;
6848         ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
6849         pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups, ImVec2(w, 0.0f));
6850         RenderText(text_pos, label);
6851         PopStyleVar();
6852         window->DC.CursorPos.x += IM_FLOOR(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().
6853     }
6854     else
6855     {
6856         // Menu inside a menu
6857         // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
6858         //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
6859         popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
6860         float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
6861         float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
6862         float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
6863         float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
6864         ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
6865         pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
6866         RenderText(text_pos, label);
6867         if (icon_w > 0.0f)
6868             RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
6869         RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
6870     }
6871     if (!enabled)
6872         EndDisabled();
6873 
6874     const bool hovered = (g.HoveredId == id) && enabled;
6875     if (menuset_is_open)
6876         g.NavWindow = backed_nav_window;
6877 
6878     bool want_open = false;
6879     bool want_close = false;
6880     if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
6881     {
6882         // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
6883         // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
6884         bool moving_toward_other_child_menu = false;
6885 
6886         ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL;
6887         if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar))
6888         {
6889             // FIXME-DPI: Values should be derived from a master "scale" factor.
6890             ImRect next_window_rect = child_menu_window->Rect();
6891             ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
6892             ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
6893             ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
6894             float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f);    // add a bit of extra slack.
6895             ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
6896             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?
6897             tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
6898             moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
6899             //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
6900         }
6901 
6902         // FIXME: Hovering a disabled BeginMenu or MenuItem won't close us
6903         if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu)
6904             want_close = true;
6905 
6906         if (!menu_is_open && hovered && pressed) // Click to open
6907             want_open = true;
6908         else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open
6909             want_open = true;
6910 
6911         if (g.NavActivateId == id)
6912         {
6913             want_close = menu_is_open;
6914             want_open = !menu_is_open;
6915         }
6916         if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
6917         {
6918             want_open = true;
6919             NavMoveRequestCancel();
6920         }
6921     }
6922     else
6923     {
6924         // Menu bar
6925         if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
6926         {
6927             want_close = true;
6928             want_open = menu_is_open = false;
6929         }
6930         else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
6931         {
6932             want_open = true;
6933         }
6934         else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
6935         {
6936             want_open = true;
6937             NavMoveRequestCancel();
6938         }
6939     }
6940 
6941     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.. }'
6942         want_close = true;
6943     if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
6944         ClosePopupToLevel(g.BeginPopupStack.Size, true);
6945 
6946     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
6947     PopID();
6948 
6949     if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
6950     {
6951         // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
6952         OpenPopup(label);
6953         return false;
6954     }
6955 
6956     menu_is_open |= want_open;
6957     if (want_open)
6958         OpenPopup(label);
6959 
6960     if (menu_is_open)
6961     {
6962         SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
6963         menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
6964     }
6965     else
6966     {
6967         g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
6968     }
6969 
6970     return menu_is_open;
6971 }
6972 
BeginMenu(const char * label,bool enabled)6973 bool ImGui::BeginMenu(const char* label, bool enabled)
6974 {
6975     return BeginMenuEx(label, NULL, enabled);
6976 }
6977 
EndMenu()6978 void ImGui::EndMenu()
6979 {
6980     // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
6981     // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
6982     // 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.
6983     ImGuiContext& g = *GImGui;
6984     ImGuiWindow* window = g.CurrentWindow;
6985     if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
6986     {
6987         ClosePopupToLevel(g.BeginPopupStack.Size, true);
6988         NavMoveRequestCancel();
6989     }
6990 
6991     EndPopup();
6992 }
6993 
MenuItemEx(const char * label,const char * icon,const char * shortcut,bool selected,bool enabled)6994 bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
6995 {
6996     ImGuiWindow* window = GetCurrentWindow();
6997     if (window->SkipItems)
6998         return false;
6999 
7000     ImGuiContext& g = *GImGui;
7001     ImGuiStyle& style = g.Style;
7002     ImVec2 pos = window->DC.CursorPos;
7003     ImVec2 label_size = CalcTextSize(label, NULL, true);
7004 
7005     // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
7006     // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
7007     bool pressed;
7008     PushID(label);
7009     if (!enabled)
7010         BeginDisabled(true);
7011     const ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover;
7012     const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
7013     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
7014     {
7015         // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
7016         // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
7017         float w = label_size.x;
7018         window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
7019         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
7020         pressed = Selectable("", selected, flags, ImVec2(w, 0.0f));
7021         PopStyleVar();
7022         RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
7023         window->DC.CursorPos.x += IM_FLOOR(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().
7024     }
7025     else
7026     {
7027         // Menu item inside a vertical menu
7028         // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
7029         //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
7030         float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
7031         float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
7032         float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
7033         float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
7034         float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
7035         pressed = Selectable("", false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
7036         RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
7037         if (icon_w > 0.0f)
7038             RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
7039         if (shortcut_w > 0.0f)
7040         {
7041             PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
7042             RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
7043             PopStyleColor();
7044         }
7045         if (selected)
7046             RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize  * 0.866f);
7047     }
7048     IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
7049     if (!enabled)
7050         EndDisabled();
7051     PopID();
7052 
7053     return pressed;
7054 }
7055 
MenuItem(const char * label,const char * shortcut,bool selected,bool enabled)7056 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
7057 {
7058     return MenuItemEx(label, NULL, shortcut, selected, enabled);
7059 }
7060 
MenuItem(const char * label,const char * shortcut,bool * p_selected,bool enabled)7061 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
7062 {
7063     if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
7064     {
7065         if (p_selected)
7066             *p_selected = !*p_selected;
7067         return true;
7068     }
7069     return false;
7070 }
7071 
7072 //-------------------------------------------------------------------------
7073 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
7074 //-------------------------------------------------------------------------
7075 // - BeginTabBar()
7076 // - BeginTabBarEx() [Internal]
7077 // - EndTabBar()
7078 // - TabBarLayout() [Internal]
7079 // - TabBarCalcTabID() [Internal]
7080 // - TabBarCalcMaxTabWidth() [Internal]
7081 // - TabBarFindTabById() [Internal]
7082 // - TabBarRemoveTab() [Internal]
7083 // - TabBarCloseTab() [Internal]
7084 // - TabBarScrollClamp() [Internal]
7085 // - TabBarScrollToTab() [Internal]
7086 // - TabBarQueueChangeTabOrder() [Internal]
7087 // - TabBarScrollingButtons() [Internal]
7088 // - TabBarTabListPopupButton() [Internal]
7089 //-------------------------------------------------------------------------
7090 
7091 struct ImGuiTabBarSection
7092 {
7093     int                 TabCount;               // Number of tabs in this section.
7094     float               Width;                  // Sum of width of tabs in this section (after shrinking down)
7095     float               Spacing;                // Horizontal spacing at the end of the section.
7096 
ImGuiTabBarSectionImGuiTabBarSection7097     ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
7098 };
7099 
7100 namespace ImGui
7101 {
7102     static void             TabBarLayout(ImGuiTabBar* tab_bar);
7103     static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
7104     static float            TabBarCalcMaxTabWidth();
7105     static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
7106     static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
7107     static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
7108     static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
7109 }
7110 
ImGuiTabBar()7111 ImGuiTabBar::ImGuiTabBar()
7112 {
7113     memset(this, 0, sizeof(*this));
7114     CurrFrameVisible = PrevFrameVisible = -1;
7115     LastTabItemIdx = -1;
7116 }
7117 
TabItemGetSectionIdx(const ImGuiTabItem * tab)7118 static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
7119 {
7120     return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
7121 }
7122 
TabItemComparerBySection(const void * lhs,const void * rhs)7123 static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
7124 {
7125     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
7126     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
7127     const int a_section = TabItemGetSectionIdx(a);
7128     const int b_section = TabItemGetSectionIdx(b);
7129     if (a_section != b_section)
7130         return a_section - b_section;
7131     return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
7132 }
7133 
TabItemComparerByBeginOrder(const void * lhs,const void * rhs)7134 static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
7135 {
7136     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
7137     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
7138     return (int)(a->BeginOrder - b->BeginOrder);
7139 }
7140 
GetTabBarFromTabBarRef(const ImGuiPtrOrIndex & ref)7141 static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
7142 {
7143     ImGuiContext& g = *GImGui;
7144     return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
7145 }
7146 
GetTabBarRefFromTabBar(ImGuiTabBar * tab_bar)7147 static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
7148 {
7149     ImGuiContext& g = *GImGui;
7150     if (g.TabBars.Contains(tab_bar))
7151         return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
7152     return ImGuiPtrOrIndex(tab_bar);
7153 }
7154 
BeginTabBar(const char * str_id,ImGuiTabBarFlags flags)7155 bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
7156 {
7157     ImGuiContext& g = *GImGui;
7158     ImGuiWindow* window = g.CurrentWindow;
7159     if (window->SkipItems)
7160         return false;
7161 
7162     ImGuiID id = window->GetID(str_id);
7163     ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
7164     ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
7165     tab_bar->ID = id;
7166     return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
7167 }
7168 
BeginTabBarEx(ImGuiTabBar * tab_bar,const ImRect & tab_bar_bb,ImGuiTabBarFlags flags)7169 bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
7170 {
7171     ImGuiContext& g = *GImGui;
7172     ImGuiWindow* window = g.CurrentWindow;
7173     if (window->SkipItems)
7174         return false;
7175 
7176     if ((flags & ImGuiTabBarFlags_DockNode) == 0)
7177         PushOverrideID(tab_bar->ID);
7178 
7179     // Add to stack
7180     g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
7181     g.CurrentTabBar = tab_bar;
7182 
7183     // Append with multiple BeginTabBar()/EndTabBar() pairs.
7184     tab_bar->BackupCursorPos = window->DC.CursorPos;
7185     if (tab_bar->CurrFrameVisible == g.FrameCount)
7186     {
7187         window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
7188         tab_bar->BeginCount++;
7189         return true;
7190     }
7191 
7192     // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
7193     if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
7194         if (tab_bar->Tabs.Size > 1)
7195             ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
7196     tab_bar->TabsAddedNew = false;
7197 
7198     // Flags
7199     if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
7200         flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
7201 
7202     tab_bar->Flags = flags;
7203     tab_bar->BarRect = tab_bar_bb;
7204     tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
7205     tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
7206     tab_bar->CurrFrameVisible = g.FrameCount;
7207     tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
7208     tab_bar->CurrTabsContentsHeight = 0.0f;
7209     tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
7210     tab_bar->FramePadding = g.Style.FramePadding;
7211     tab_bar->TabsActiveCount = 0;
7212     tab_bar->BeginCount = 1;
7213 
7214     // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
7215     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
7216 
7217     // Draw separator
7218     const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
7219     const float y = tab_bar->BarRect.Max.y - 1.0f;
7220     {
7221         const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f);
7222         const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f);
7223         window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
7224     }
7225     return true;
7226 }
7227 
EndTabBar()7228 void    ImGui::EndTabBar()
7229 {
7230     ImGuiContext& g = *GImGui;
7231     ImGuiWindow* window = g.CurrentWindow;
7232     if (window->SkipItems)
7233         return;
7234 
7235     ImGuiTabBar* tab_bar = g.CurrentTabBar;
7236     if (tab_bar == NULL)
7237     {
7238         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
7239         return;
7240     }
7241 
7242     // Fallback in case no TabItem have been submitted
7243     if (tab_bar->WantLayout)
7244         TabBarLayout(tab_bar);
7245 
7246     // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
7247     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
7248     if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
7249     {
7250         tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
7251         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
7252     }
7253     else
7254     {
7255         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
7256     }
7257     if (tab_bar->BeginCount > 1)
7258         window->DC.CursorPos = tab_bar->BackupCursorPos;
7259 
7260     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
7261         PopID();
7262 
7263     g.CurrentTabBarStack.pop_back();
7264     g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
7265 }
7266 
7267 // This is called only once a frame before by the first call to ItemTab()
7268 // 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)7269 static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
7270 {
7271     ImGuiContext& g = *GImGui;
7272     tab_bar->WantLayout = false;
7273 
7274     // Garbage collect by compacting list
7275     // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
7276     int tab_dst_n = 0;
7277     bool need_sort_by_section = false;
7278     ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
7279     for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
7280     {
7281         ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
7282         if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
7283         {
7284             // Remove tab
7285             if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
7286             if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
7287             if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
7288             continue;
7289         }
7290         if (tab_dst_n != tab_src_n)
7291             tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
7292 
7293         tab = &tab_bar->Tabs[tab_dst_n];
7294         tab->IndexDuringLayout = (ImS16)tab_dst_n;
7295 
7296         // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
7297         int curr_tab_section_n = TabItemGetSectionIdx(tab);
7298         if (tab_dst_n > 0)
7299         {
7300             ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
7301             int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
7302             if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
7303                 need_sort_by_section = true;
7304             if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
7305                 need_sort_by_section = true;
7306         }
7307 
7308         sections[curr_tab_section_n].TabCount++;
7309         tab_dst_n++;
7310     }
7311     if (tab_bar->Tabs.Size != tab_dst_n)
7312         tab_bar->Tabs.resize(tab_dst_n);
7313 
7314     if (need_sort_by_section)
7315         ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
7316 
7317     // Calculate spacing between sections
7318     sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
7319     sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
7320 
7321     // Setup next selected tab
7322     ImGuiID scroll_to_tab_id = 0;
7323     if (tab_bar->NextSelectedTabId)
7324     {
7325         tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
7326         tab_bar->NextSelectedTabId = 0;
7327         scroll_to_tab_id = tab_bar->SelectedTabId;
7328     }
7329 
7330     // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
7331     if (tab_bar->ReorderRequestTabId != 0)
7332     {
7333         if (TabBarProcessReorder(tab_bar))
7334             if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
7335                 scroll_to_tab_id = tab_bar->ReorderRequestTabId;
7336         tab_bar->ReorderRequestTabId = 0;
7337     }
7338 
7339     // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
7340     const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
7341     if (tab_list_popup_button)
7342         if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
7343             scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
7344 
7345     // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
7346     // (whereas our tabs are stored as: leading, central, trailing)
7347     int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
7348     g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
7349 
7350     // Compute ideal tabs widths + store them into shrink buffer
7351     ImGuiTabItem* most_recently_selected_tab = NULL;
7352     int curr_section_n = -1;
7353     bool found_selected_tab_id = false;
7354     for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
7355     {
7356         ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
7357         IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
7358 
7359         if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
7360             most_recently_selected_tab = tab;
7361         if (tab->ID == tab_bar->SelectedTabId)
7362             found_selected_tab_id = true;
7363         if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
7364             scroll_to_tab_id = tab->ID;
7365 
7366         // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
7367         // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
7368         // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
7369         const char* tab_name = tab_bar->GetTabName(tab);
7370         const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
7371         tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
7372 
7373         int section_n = TabItemGetSectionIdx(tab);
7374         ImGuiTabBarSection* section = &sections[section_n];
7375         section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
7376         curr_section_n = section_n;
7377 
7378         // Store data so we can build an array sorted by width if we need to shrink tabs down
7379         IM_MSVC_WARNING_SUPPRESS(6385);
7380         int shrink_buffer_index = shrink_buffer_indexes[section_n]++;
7381         g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n;
7382         g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth;
7383 
7384         IM_ASSERT(tab->ContentWidth > 0.0f);
7385         tab->Width = tab->ContentWidth;
7386     }
7387 
7388     // Compute total ideal width (used for e.g. auto-resizing a window)
7389     tab_bar->WidthAllTabsIdeal = 0.0f;
7390     for (int section_n = 0; section_n < 3; section_n++)
7391         tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
7392 
7393     // Horizontal scrolling buttons
7394     // (note that TabBarScrollButtons() will alter BarRect.Max.x)
7395     if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
7396         if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
7397         {
7398             scroll_to_tab_id = scroll_and_select_tab->ID;
7399             if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
7400                 tab_bar->SelectedTabId = scroll_to_tab_id;
7401         }
7402 
7403     // Shrink widths if full tabs don't fit in their allocated space
7404     float section_0_w = sections[0].Width + sections[0].Spacing;
7405     float section_1_w = sections[1].Width + sections[1].Spacing;
7406     float section_2_w = sections[2].Width + sections[2].Spacing;
7407     bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
7408     float width_excess;
7409     if (central_section_is_visible)
7410         width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
7411     else
7412         width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
7413 
7414     // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
7415     if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
7416     {
7417         int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
7418         int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
7419         ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
7420 
7421         // Apply shrunk values into tabs and sections
7422         for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
7423         {
7424             ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
7425             float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width);
7426             if (shrinked_width < 0.0f)
7427                 continue;
7428 
7429             int section_n = TabItemGetSectionIdx(tab);
7430             sections[section_n].Width -= (tab->Width - shrinked_width);
7431             tab->Width = shrinked_width;
7432         }
7433     }
7434 
7435     // Layout all active tabs
7436     int section_tab_index = 0;
7437     float tab_offset = 0.0f;
7438     tab_bar->WidthAllTabs = 0.0f;
7439     for (int section_n = 0; section_n < 3; section_n++)
7440     {
7441         ImGuiTabBarSection* section = &sections[section_n];
7442         if (section_n == 2)
7443             tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
7444 
7445         for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
7446         {
7447             ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
7448             tab->Offset = tab_offset;
7449             tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
7450         }
7451         tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
7452         tab_offset += section->Spacing;
7453         section_tab_index += section->TabCount;
7454     }
7455 
7456     // If we have lost the selected tab, select the next most recently active one
7457     if (found_selected_tab_id == false)
7458         tab_bar->SelectedTabId = 0;
7459     if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
7460         scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
7461 
7462     // Lock in visible tab
7463     tab_bar->VisibleTabId = tab_bar->SelectedTabId;
7464     tab_bar->VisibleTabWasSubmitted = false;
7465 
7466     // Update scrolling
7467     if (scroll_to_tab_id != 0)
7468         TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
7469     tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
7470     tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
7471     if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
7472     {
7473         // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
7474         // Teleport if we are aiming far off the visible line
7475         tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
7476         tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
7477         const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
7478         tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
7479     }
7480     else
7481     {
7482         tab_bar->ScrollingSpeed = 0.0f;
7483     }
7484     tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
7485     tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
7486 
7487     // Clear name buffers
7488     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
7489         tab_bar->TabsNames.Buf.resize(0);
7490 
7491     // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
7492     ImGuiWindow* window = g.CurrentWindow;
7493     window->DC.CursorPos = tab_bar->BarRect.Min;
7494     ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
7495     window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
7496 }
7497 
7498 // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
TabBarCalcTabID(ImGuiTabBar * tab_bar,const char * label)7499 static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
7500 {
7501     if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
7502     {
7503         ImGuiID id = ImHashStr(label);
7504         KeepAliveID(id);
7505         return id;
7506     }
7507     else
7508     {
7509         ImGuiWindow* window = GImGui->CurrentWindow;
7510         return window->GetID(label);
7511     }
7512 }
7513 
TabBarCalcMaxTabWidth()7514 static float ImGui::TabBarCalcMaxTabWidth()
7515 {
7516     ImGuiContext& g = *GImGui;
7517     return g.FontSize * 20.0f;
7518 }
7519 
TabBarFindTabByID(ImGuiTabBar * tab_bar,ImGuiID tab_id)7520 ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
7521 {
7522     if (tab_id != 0)
7523         for (int n = 0; n < tab_bar->Tabs.Size; n++)
7524             if (tab_bar->Tabs[n].ID == tab_id)
7525                 return &tab_bar->Tabs[n];
7526     return NULL;
7527 }
7528 
7529 // 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)7530 void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
7531 {
7532     if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
7533         tab_bar->Tabs.erase(tab);
7534     if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
7535     if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
7536     if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
7537 }
7538 
7539 // Called on manual closure attempt
TabBarCloseTab(ImGuiTabBar * tab_bar,ImGuiTabItem * tab)7540 void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
7541 {
7542     IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button));
7543     if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
7544     {
7545         // This will remove a frame of lag for selecting another tab on closure.
7546         // 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
7547         tab->WantClose = true;
7548         if (tab_bar->VisibleTabId == tab->ID)
7549         {
7550             tab->LastFrameVisible = -1;
7551             tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
7552         }
7553     }
7554     else
7555     {
7556         // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
7557         if (tab_bar->VisibleTabId != tab->ID)
7558             tab_bar->NextSelectedTabId = tab->ID;
7559     }
7560 }
7561 
TabBarScrollClamp(ImGuiTabBar * tab_bar,float scrolling)7562 static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
7563 {
7564     scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
7565     return ImMax(scrolling, 0.0f);
7566 }
7567 
7568 // Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
TabBarScrollToTab(ImGuiTabBar * tab_bar,ImGuiID tab_id,ImGuiTabBarSection * sections)7569 static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
7570 {
7571     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
7572     if (tab == NULL)
7573         return;
7574     if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
7575         return;
7576 
7577     ImGuiContext& g = *GImGui;
7578     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)
7579     int order = tab_bar->GetTabOrder(tab);
7580 
7581     // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
7582     // FIXME: This is all confusing.
7583     float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
7584 
7585     // We make all tabs positions all relative Sections[0].Width to make code simpler
7586     float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
7587     float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
7588     tab_bar->ScrollingTargetDistToVisibility = 0.0f;
7589     if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
7590     {
7591         // Scroll to the left
7592         tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
7593         tab_bar->ScrollingTarget = tab_x1;
7594     }
7595     else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
7596     {
7597         // Scroll to the right
7598         tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
7599         tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
7600     }
7601 }
7602 
TabBarQueueReorder(ImGuiTabBar * tab_bar,const ImGuiTabItem * tab,int offset)7603 void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset)
7604 {
7605     IM_ASSERT(offset != 0);
7606     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
7607     tab_bar->ReorderRequestTabId = tab->ID;
7608     tab_bar->ReorderRequestOffset = (ImS16)offset;
7609 }
7610 
TabBarQueueReorderFromMousePos(ImGuiTabBar * tab_bar,const ImGuiTabItem * src_tab,ImVec2 mouse_pos)7611 void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos)
7612 {
7613     ImGuiContext& g = *GImGui;
7614     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
7615     if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
7616         return;
7617 
7618     const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
7619     const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
7620 
7621     // Count number of contiguous tabs we are crossing over
7622     const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
7623     const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
7624     int dst_idx = src_idx;
7625     for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
7626     {
7627         // Reordered tabs must share the same section
7628         const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
7629         if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
7630             break;
7631         if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
7632             break;
7633         dst_idx = i;
7634 
7635         // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
7636         const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
7637         const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
7638         //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
7639         if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
7640             break;
7641     }
7642 
7643     if (dst_idx != src_idx)
7644         TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
7645 }
7646 
TabBarProcessReorder(ImGuiTabBar * tab_bar)7647 bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
7648 {
7649     ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
7650     if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
7651         return false;
7652 
7653     //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
7654     int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset;
7655     if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
7656         return false;
7657 
7658     // Reordered tabs must share the same section
7659     // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
7660     ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
7661     if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
7662         return false;
7663     if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
7664         return false;
7665 
7666     ImGuiTabItem item_tmp = *tab1;
7667     ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
7668     ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
7669     const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
7670     memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
7671     *tab2 = item_tmp;
7672 
7673     if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
7674         MarkIniSettingsDirty();
7675     return true;
7676 }
7677 
TabBarScrollingButtons(ImGuiTabBar * tab_bar)7678 static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
7679 {
7680     ImGuiContext& g = *GImGui;
7681     ImGuiWindow* window = g.CurrentWindow;
7682 
7683     const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
7684     const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
7685 
7686     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
7687     //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));
7688 
7689     int select_dir = 0;
7690     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
7691     arrow_col.w *= 0.5f;
7692 
7693     PushStyleColor(ImGuiCol_Text, arrow_col);
7694     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
7695     const float backup_repeat_delay = g.IO.KeyRepeatDelay;
7696     const float backup_repeat_rate = g.IO.KeyRepeatRate;
7697     g.IO.KeyRepeatDelay = 0.250f;
7698     g.IO.KeyRepeatRate = 0.200f;
7699     float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
7700     window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
7701     if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
7702         select_dir = -1;
7703     window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
7704     if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
7705         select_dir = +1;
7706     PopStyleColor(2);
7707     g.IO.KeyRepeatRate = backup_repeat_rate;
7708     g.IO.KeyRepeatDelay = backup_repeat_delay;
7709 
7710     ImGuiTabItem* tab_to_scroll_to = NULL;
7711     if (select_dir != 0)
7712         if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
7713         {
7714             int selected_order = tab_bar->GetTabOrder(tab_item);
7715             int target_order = selected_order + select_dir;
7716 
7717             // Skip tab item buttons until another tab item is found or end is reached
7718             while (tab_to_scroll_to == NULL)
7719             {
7720                 // If we are at the end of the list, still scroll to make our tab visible
7721                 tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
7722 
7723                 // Cross through buttons
7724                 // (even if first/last item is a button, return it so we can update the scroll)
7725                 if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
7726                 {
7727                     target_order += select_dir;
7728                     selected_order += select_dir;
7729                     tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
7730                 }
7731             }
7732         }
7733     window->DC.CursorPos = backup_cursor_pos;
7734     tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
7735 
7736     return tab_to_scroll_to;
7737 }
7738 
TabBarTabListPopupButton(ImGuiTabBar * tab_bar)7739 static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
7740 {
7741     ImGuiContext& g = *GImGui;
7742     ImGuiWindow* window = g.CurrentWindow;
7743 
7744     // We use g.Style.FramePadding.y to match the square ArrowButton size
7745     const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
7746     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
7747     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
7748     tab_bar->BarRect.Min.x += tab_list_popup_button_width;
7749 
7750     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
7751     arrow_col.w *= 0.5f;
7752     PushStyleColor(ImGuiCol_Text, arrow_col);
7753     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
7754     bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
7755     PopStyleColor(2);
7756 
7757     ImGuiTabItem* tab_to_select = NULL;
7758     if (open)
7759     {
7760         for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
7761         {
7762             ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
7763             if (tab->Flags & ImGuiTabItemFlags_Button)
7764                 continue;
7765 
7766             const char* tab_name = tab_bar->GetTabName(tab);
7767             if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
7768                 tab_to_select = tab;
7769         }
7770         EndCombo();
7771     }
7772 
7773     window->DC.CursorPos = backup_cursor_pos;
7774     return tab_to_select;
7775 }
7776 
7777 //-------------------------------------------------------------------------
7778 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
7779 //-------------------------------------------------------------------------
7780 // - BeginTabItem()
7781 // - EndTabItem()
7782 // - TabItemButton()
7783 // - TabItemEx() [Internal]
7784 // - SetTabItemClosed()
7785 // - TabItemCalcSize() [Internal]
7786 // - TabItemBackground() [Internal]
7787 // - TabItemLabelAndCloseButton() [Internal]
7788 //-------------------------------------------------------------------------
7789 
BeginTabItem(const char * label,bool * p_open,ImGuiTabItemFlags flags)7790 bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
7791 {
7792     ImGuiContext& g = *GImGui;
7793     ImGuiWindow* window = g.CurrentWindow;
7794     if (window->SkipItems)
7795         return false;
7796 
7797     ImGuiTabBar* tab_bar = g.CurrentTabBar;
7798     if (tab_bar == NULL)
7799     {
7800         IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
7801         return false;
7802     }
7803     IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
7804 
7805     bool ret = TabItemEx(tab_bar, label, p_open, flags);
7806     if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
7807     {
7808         ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
7809         PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
7810     }
7811     return ret;
7812 }
7813 
EndTabItem()7814 void    ImGui::EndTabItem()
7815 {
7816     ImGuiContext& g = *GImGui;
7817     ImGuiWindow* window = g.CurrentWindow;
7818     if (window->SkipItems)
7819         return;
7820 
7821     ImGuiTabBar* tab_bar = g.CurrentTabBar;
7822     if (tab_bar == NULL)
7823     {
7824         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
7825         return;
7826     }
7827     IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
7828     ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
7829     if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
7830         PopID();
7831 }
7832 
TabItemButton(const char * label,ImGuiTabItemFlags flags)7833 bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
7834 {
7835     ImGuiContext& g = *GImGui;
7836     ImGuiWindow* window = g.CurrentWindow;
7837     if (window->SkipItems)
7838         return false;
7839 
7840     ImGuiTabBar* tab_bar = g.CurrentTabBar;
7841     if (tab_bar == NULL)
7842     {
7843         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
7844         return false;
7845     }
7846     return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder);
7847 }
7848 
TabItemEx(ImGuiTabBar * tab_bar,const char * label,bool * p_open,ImGuiTabItemFlags flags)7849 bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
7850 {
7851     // Layout whole tab bar if not already done
7852     if (tab_bar->WantLayout)
7853         TabBarLayout(tab_bar);
7854 
7855     ImGuiContext& g = *GImGui;
7856     ImGuiWindow* window = g.CurrentWindow;
7857     if (window->SkipItems)
7858         return false;
7859 
7860     const ImGuiStyle& style = g.Style;
7861     const ImGuiID id = TabBarCalcTabID(tab_bar, label);
7862 
7863     // If the user called us with *p_open == false, we early out and don't render.
7864     // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
7865     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
7866     if (p_open && !*p_open)
7867     {
7868         PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
7869         ItemAdd(ImRect(), id);
7870         PopItemFlag();
7871         return false;
7872     }
7873 
7874     IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
7875     IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
7876 
7877     // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
7878     if (flags & ImGuiTabItemFlags_NoCloseButton)
7879         p_open = NULL;
7880     else if (p_open == NULL)
7881         flags |= ImGuiTabItemFlags_NoCloseButton;
7882 
7883     // Calculate tab contents size
7884     ImVec2 size = TabItemCalcSize(label, p_open != NULL);
7885 
7886     // Acquire tab data
7887     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
7888     bool tab_is_new = false;
7889     if (tab == NULL)
7890     {
7891         tab_bar->Tabs.push_back(ImGuiTabItem());
7892         tab = &tab_bar->Tabs.back();
7893         tab->ID = id;
7894         tab->Width = size.x;
7895         tab_bar->TabsAddedNew = true;
7896         tab_is_new = true;
7897     }
7898     tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
7899     tab->ContentWidth = size.x;
7900     tab->BeginOrder = tab_bar->TabsActiveCount++;
7901 
7902     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
7903     const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
7904     const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
7905     const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
7906     tab->LastFrameVisible = g.FrameCount;
7907     tab->Flags = flags;
7908 
7909     // Append name with zero-terminator
7910     tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
7911     tab_bar->TabsNames.append(label, label + strlen(label) + 1);
7912 
7913     // Update selected tab
7914     if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
7915         if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
7916             if (!is_tab_button)
7917                 tab_bar->NextSelectedTabId = id;  // New tabs gets activated
7918     if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar
7919         if (!is_tab_button)
7920             tab_bar->NextSelectedTabId = id;
7921 
7922     // Lock visibility
7923     // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
7924     bool tab_contents_visible = (tab_bar->VisibleTabId == id);
7925     if (tab_contents_visible)
7926         tab_bar->VisibleTabWasSubmitted = true;
7927 
7928     // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
7929     if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
7930         if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
7931             tab_contents_visible = true;
7932 
7933     // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
7934     // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
7935     if (tab_appearing && (!tab_bar_appearing || tab_is_new))
7936     {
7937         PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
7938         ItemAdd(ImRect(), id);
7939         PopItemFlag();
7940         if (is_tab_button)
7941             return false;
7942         return tab_contents_visible;
7943     }
7944 
7945     if (tab_bar->SelectedTabId == id)
7946         tab->LastFrameSelected = g.FrameCount;
7947 
7948     // Backup current layout position
7949     const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
7950 
7951     // Layout
7952     const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
7953     size.x = tab->Width;
7954     if (is_central_section)
7955         window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
7956     else
7957         window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
7958     ImVec2 pos = window->DC.CursorPos;
7959     ImRect bb(pos, pos + size);
7960 
7961     // 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)
7962     const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
7963     if (want_clip_rect)
7964         PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
7965 
7966     ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
7967     ItemSize(bb.GetSize(), style.FramePadding.y);
7968     window->DC.CursorMaxPos = backup_cursor_max_pos;
7969 
7970     if (!ItemAdd(bb, id))
7971     {
7972         if (want_clip_rect)
7973             PopClipRect();
7974         window->DC.CursorPos = backup_main_cursor_pos;
7975         return tab_contents_visible;
7976     }
7977 
7978     // Click to Select a tab
7979     ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap);
7980     if (g.DragDropActive)
7981         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
7982     bool hovered, held;
7983     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
7984     if (pressed && !is_tab_button)
7985         tab_bar->NextSelectedTabId = id;
7986 
7987     // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
7988     if (g.ActiveId != id)
7989         SetItemAllowOverlap();
7990 
7991     // Drag and drop: re-order tabs
7992     if (held && !tab_appearing && IsMouseDragging(0))
7993     {
7994         if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
7995         {
7996             // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
7997             if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
7998             {
7999                 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
8000             }
8001             else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
8002             {
8003                 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
8004             }
8005         }
8006     }
8007 
8008 #if 0
8009     if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
8010     {
8011         // Enlarge tab display when hovering
8012         bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
8013         display_draw_list = GetForegroundDrawList(window);
8014         TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
8015     }
8016 #endif
8017 
8018     // Render tab shape
8019     ImDrawList* display_draw_list = window->DrawList;
8020     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));
8021     TabItemBackground(display_draw_list, bb, flags, tab_col);
8022     RenderNavHighlight(bb, id);
8023 
8024     // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
8025     const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
8026     if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
8027         if (!is_tab_button)
8028             tab_bar->NextSelectedTabId = id;
8029 
8030     if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
8031         flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
8032 
8033     // Render tab label, process close button
8034     const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0;
8035     bool just_closed;
8036     bool text_clipped;
8037     TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
8038     if (just_closed && p_open != NULL)
8039     {
8040         *p_open = false;
8041         TabBarCloseTab(tab_bar, tab);
8042     }
8043 
8044     // Restore main window position so user can draw there
8045     if (want_clip_rect)
8046         PopClipRect();
8047     window->DC.CursorPos = backup_main_cursor_pos;
8048 
8049     // Tooltip
8050     // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
8051     // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
8052     // FIXME: This is a mess.
8053     // FIXME: We may want disabled tab to still display the tooltip?
8054     if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered())
8055         if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
8056             SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
8057 
8058     IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
8059     if (is_tab_button)
8060         return pressed;
8061     return tab_contents_visible;
8062 }
8063 
8064 // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
8065 // To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
8066 // Tabs closed by the close button will automatically be flagged to avoid this issue.
SetTabItemClosed(const char * label)8067 void    ImGui::SetTabItemClosed(const char* label)
8068 {
8069     ImGuiContext& g = *GImGui;
8070     bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
8071     if (is_within_manual_tab_bar)
8072     {
8073         ImGuiTabBar* tab_bar = g.CurrentTabBar;
8074         ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
8075         if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
8076             tab->WantClose = true; // Will be processed by next call to TabBarLayout()
8077     }
8078 }
8079 
TabItemCalcSize(const char * label,bool has_close_button)8080 ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
8081 {
8082     ImGuiContext& g = *GImGui;
8083     ImVec2 label_size = CalcTextSize(label, NULL, true);
8084     ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
8085     if (has_close_button)
8086         size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
8087     else
8088         size.x += g.Style.FramePadding.x + 1.0f;
8089     return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
8090 }
8091 
TabItemBackground(ImDrawList * draw_list,const ImRect & bb,ImGuiTabItemFlags flags,ImU32 col)8092 void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
8093 {
8094     // 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.
8095     ImGuiContext& g = *GImGui;
8096     const float width = bb.GetWidth();
8097     IM_UNUSED(flags);
8098     IM_ASSERT(width > 0.0f);
8099     const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
8100     const float y1 = bb.Min.y + 1.0f;
8101     const float y2 = bb.Max.y - 1.0f;
8102     draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
8103     draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
8104     draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
8105     draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
8106     draw_list->PathFillConvex(col);
8107     if (g.Style.TabBorderSize > 0.0f)
8108     {
8109         draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
8110         draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
8111         draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
8112         draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
8113         draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
8114     }
8115 }
8116 
8117 // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
8118 // 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,bool is_contents_visible,bool * out_just_closed,bool * out_text_clipped)8119 void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped)
8120 {
8121     ImGuiContext& g = *GImGui;
8122     ImVec2 label_size = CalcTextSize(label, NULL, true);
8123 
8124     if (out_just_closed)
8125         *out_just_closed = false;
8126     if (out_text_clipped)
8127         *out_text_clipped = false;
8128 
8129     if (bb.GetWidth() <= 1.0f)
8130         return;
8131 
8132     // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
8133     // But right now if you want to alter text color of tabs this is what you need to do.
8134 #if 0
8135     const float backup_alpha = g.Style.Alpha;
8136     if (!is_contents_visible)
8137         g.Style.Alpha *= 0.7f;
8138 #endif
8139 
8140     // Render text label (with clipping + alpha gradient) + unsaved marker
8141     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);
8142     ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
8143 
8144     // Return clipped state ignoring the close button
8145     if (out_text_clipped)
8146     {
8147         *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
8148         //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
8149     }
8150 
8151     const float button_sz = g.FontSize;
8152     const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x * 2.0f - button_sz), bb.Min.y);
8153 
8154     // Close Button & Unsaved Marker
8155     // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
8156     //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
8157     //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
8158     //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
8159     bool close_button_pressed = false;
8160     bool close_button_visible = false;
8161     if (close_button_id != 0)
8162         if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
8163             if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
8164                 close_button_visible = true;
8165     bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
8166 
8167     if (close_button_visible)
8168     {
8169         ImGuiLastItemData last_item_backup = g.LastItemData;
8170         PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding);
8171         if (CloseButton(close_button_id, button_pos))
8172             close_button_pressed = true;
8173         PopStyleVar();
8174         g.LastItemData = last_item_backup;
8175 
8176         // Close with middle mouse button
8177         if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
8178             close_button_pressed = true;
8179     }
8180     else if (unsaved_marker_visible)
8181     {
8182         const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz) + g.Style.FramePadding * 2.0f);
8183         RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
8184     }
8185 
8186     // This is all rather complicated
8187     // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
8188     // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..
8189     float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
8190     if (close_button_visible || unsaved_marker_visible)
8191     {
8192         text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
8193         text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
8194         ellipsis_max_x = text_pixel_clip_bb.Max.x;
8195     }
8196     RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
8197 
8198 #if 0
8199     if (!is_contents_visible)
8200         g.Style.Alpha = backup_alpha;
8201 #endif
8202 
8203     if (out_just_closed)
8204         *out_just_closed = close_button_pressed;
8205 }
8206 
8207 
8208 #endif // #ifndef IMGUI_DISABLE
8209