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