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