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