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