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