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