1 // dear imgui, v1.84 WIP
2 // (widgets code)
3
4 /*
5
6 Index of this file:
7
8 // [SECTION] Forward Declarations
9 // [SECTION] Widgets: Text, etc.
10 // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
11 // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
12 // [SECTION] Widgets: ComboBox
13 // [SECTION] Data Type and Data Formatting Helpers
14 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
15 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
16 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
17 // [SECTION] Widgets: InputText, InputTextMultiline
18 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
19 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
20 // [SECTION] Widgets: Selectable
21 // [SECTION] Widgets: ListBox
22 // [SECTION] Widgets: PlotLines, PlotHistogram
23 // [SECTION] Widgets: Value helpers
24 // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
25 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
26 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
27 // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
28
29 */
30
31 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
32 #define _CRT_SECURE_NO_WARNINGS
33 #endif
34
35 #include "imgui.h"
36 #ifndef IMGUI_DISABLE
37
38 #ifndef IMGUI_DEFINE_MATH_OPERATORS
39 #define IMGUI_DEFINE_MATH_OPERATORS
40 #endif
41 #include "imgui_internal.h"
42
43 // System includes
44 #include <ctype.h> // toupper
45 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
46 #include <stddef.h> // intptr_t
47 #else
48 #include <stdint.h> // intptr_t
49 #endif
50
51 //-------------------------------------------------------------------------
52 // Warnings
53 //-------------------------------------------------------------------------
54
55 // Visual Studio warnings
56 #ifdef _MSC_VER
57 #pragma warning (disable: 4127) // condition expression is constant
58 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
59 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
60 #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
61 #endif
62 #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
63 #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
64 #endif
65
66 // Clang/GCC warnings with -Weverything
67 #if defined(__clang__)
68 #if __has_warning("-Wunknown-warning-option")
69 #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
70 #endif
71 #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
72 #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
73 #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
74 #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
75 #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
76 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0
77 #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
78 #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
79 #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
80 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
81 #elif defined(__GNUC__)
82 #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
83 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
84 #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
85 #endif
86
87 //-------------------------------------------------------------------------
88 // Data
89 //-------------------------------------------------------------------------
90
91 // Widgets
92 static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
93 static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags.
94
95 // Those MIN/MAX values are not define because we need to point to them
96 static const signed char IM_S8_MIN = -128;
97 static const signed char IM_S8_MAX = 127;
98 static const unsigned char IM_U8_MIN = 0;
99 static const unsigned char IM_U8_MAX = 0xFF;
100 static const signed short IM_S16_MIN = -32768;
101 static const signed short IM_S16_MAX = 32767;
102 static const unsigned short IM_U16_MIN = 0;
103 static const unsigned short IM_U16_MAX = 0xFFFF;
104 static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
105 static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
106 static const ImU32 IM_U32_MIN = 0;
107 static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
108 #ifdef LLONG_MIN
109 static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
110 static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
111 #else
112 static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
113 static const ImS64 IM_S64_MAX = 9223372036854775807LL;
114 #endif
115 static const ImU64 IM_U64_MIN = 0;
116 #ifdef ULLONG_MAX
117 static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
118 #else
119 static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
120 #endif
121
122 //-------------------------------------------------------------------------
123 // [SECTION] Forward Declarations
124 //-------------------------------------------------------------------------
125
126 // For InputTextEx()
127 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
128 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
129 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
130
131 //-------------------------------------------------------------------------
132 // [SECTION] Widgets: Text, etc.
133 //-------------------------------------------------------------------------
134 // - TextEx() [Internal]
135 // - TextUnformatted()
136 // - Text()
137 // - TextV()
138 // - TextColored()
139 // - TextColoredV()
140 // - TextDisabled()
141 // - TextDisabledV()
142 // - TextWrapped()
143 // - TextWrappedV()
144 // - LabelText()
145 // - LabelTextV()
146 // - BulletText()
147 // - BulletTextV()
148 //-------------------------------------------------------------------------
149
TextEx(const char * text,const char * text_end,ImGuiTextFlags flags)150 void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
151 {
152 ImGuiWindow* window = GetCurrentWindow();
153 if (window->SkipItems)
154 return;
155
156 ImGuiContext& g = *GImGui;
157 IM_ASSERT(text != NULL);
158 const char* text_begin = text;
159 if (text_end == NULL)
160 text_end = text + strlen(text); // FIXME-OPT
161
162 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
163 const float wrap_pos_x = window->DC.TextWrapPos;
164 const bool wrap_enabled = (wrap_pos_x >= 0.0f);
165 if (text_end - text > 2000 && !wrap_enabled)
166 {
167 // Long text!
168 // Perform manual coarse clipping to optimize for long multi-line text
169 // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
170 // - 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.
171 // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
172 const char* line = text;
173 const float line_height = GetTextLineHeight();
174 ImVec2 text_size(0, 0);
175
176 // Lines to skip (can't skip when logging text)
177 ImVec2 pos = text_pos;
178 if (!g.LogEnabled)
179 {
180 int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
181 if (lines_skippable > 0)
182 {
183 int lines_skipped = 0;
184 while (line < text_end && lines_skipped < lines_skippable)
185 {
186 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
187 if (!line_end)
188 line_end = text_end;
189 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
190 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
191 line = line_end + 1;
192 lines_skipped++;
193 }
194 pos.y += lines_skipped * line_height;
195 }
196 }
197
198 // Lines to render
199 if (line < text_end)
200 {
201 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
202 while (line < text_end)
203 {
204 if (IsClippedEx(line_rect, 0, false))
205 break;
206
207 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
208 if (!line_end)
209 line_end = text_end;
210 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
211 RenderText(pos, line, line_end, false);
212 line = line_end + 1;
213 line_rect.Min.y += line_height;
214 line_rect.Max.y += line_height;
215 pos.y += line_height;
216 }
217
218 // Count remaining lines
219 int lines_skipped = 0;
220 while (line < text_end)
221 {
222 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
223 if (!line_end)
224 line_end = text_end;
225 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
226 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
227 line = line_end + 1;
228 lines_skipped++;
229 }
230 pos.y += lines_skipped * line_height;
231 }
232 text_size.y = (pos - text_pos).y;
233
234 ImRect bb(text_pos, text_pos + text_size);
235 ItemSize(text_size, 0.0f);
236 ItemAdd(bb, 0);
237 }
238 else
239 {
240 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
241 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
242
243 ImRect bb(text_pos, text_pos + text_size);
244 ItemSize(text_size, 0.0f);
245 if (!ItemAdd(bb, 0))
246 return;
247
248 // Render (we don't hide text after ## in this end-user function)
249 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
250 }
251 }
252
TextUnformatted(const char * text,const char * text_end)253 void ImGui::TextUnformatted(const char* text, const char* text_end)
254 {
255 TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
256 }
257
Text(const char * fmt,...)258 void ImGui::Text(const char* fmt, ...)
259 {
260 va_list args;
261 va_start(args, fmt);
262 TextV(fmt, args);
263 va_end(args);
264 }
265
TextV(const char * fmt,va_list args)266 void ImGui::TextV(const char* fmt, va_list args)
267 {
268 ImGuiWindow* window = GetCurrentWindow();
269 if (window->SkipItems)
270 return;
271
272 ImGuiContext& g = *GImGui;
273 const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
274 TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
275 }
276
TextColored(const ImVec4 & col,const char * fmt,...)277 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
278 {
279 va_list args;
280 va_start(args, fmt);
281 TextColoredV(col, fmt, args);
282 va_end(args);
283 }
284
TextColoredV(const ImVec4 & col,const char * fmt,va_list args)285 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
286 {
287 PushStyleColor(ImGuiCol_Text, col);
288 if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
289 TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
290 else
291 TextV(fmt, args);
292 PopStyleColor();
293 }
294
TextDisabled(const char * fmt,...)295 void ImGui::TextDisabled(const char* fmt, ...)
296 {
297 va_list args;
298 va_start(args, fmt);
299 TextDisabledV(fmt, args);
300 va_end(args);
301 }
302
TextDisabledV(const char * fmt,va_list args)303 void ImGui::TextDisabledV(const char* fmt, va_list args)
304 {
305 ImGuiContext& g = *GImGui;
306 PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
307 if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
308 TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
309 else
310 TextV(fmt, args);
311 PopStyleColor();
312 }
313
TextWrapped(const char * fmt,...)314 void ImGui::TextWrapped(const char* fmt, ...)
315 {
316 va_list args;
317 va_start(args, fmt);
318 TextWrappedV(fmt, args);
319 va_end(args);
320 }
321
TextWrappedV(const char * fmt,va_list args)322 void ImGui::TextWrappedV(const char* fmt, va_list args)
323 {
324 ImGuiContext& g = *GImGui;
325 bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set
326 if (need_backup)
327 PushTextWrapPos(0.0f);
328 if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
329 TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
330 else
331 TextV(fmt, args);
332 if (need_backup)
333 PopTextWrapPos();
334 }
335
LabelText(const char * label,const char * fmt,...)336 void ImGui::LabelText(const char* label, const char* fmt, ...)
337 {
338 va_list args;
339 va_start(args, fmt);
340 LabelTextV(label, fmt, args);
341 va_end(args);
342 }
343
344 // Add a label+text combo aligned to other label+value widgets
LabelTextV(const char * label,const char * fmt,va_list args)345 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
346 {
347 ImGuiWindow* window = GetCurrentWindow();
348 if (window->SkipItems)
349 return;
350
351 ImGuiContext& g = *GImGui;
352 const ImGuiStyle& style = g.Style;
353 const float w = CalcItemWidth();
354
355 const char* value_text_begin = &g.TempBuffer[0];
356 const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
357 const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
358 const ImVec2 label_size = CalcTextSize(label, NULL, true);
359
360 const ImVec2 pos = window->DC.CursorPos;
361 const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
362 const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2));
363 ItemSize(total_bb, style.FramePadding.y);
364 if (!ItemAdd(total_bb, 0))
365 return;
366
367 // Render
368 RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
369 if (label_size.x > 0.0f)
370 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
371 }
372
BulletText(const char * fmt,...)373 void ImGui::BulletText(const char* fmt, ...)
374 {
375 va_list args;
376 va_start(args, fmt);
377 BulletTextV(fmt, args);
378 va_end(args);
379 }
380
381 // Text with a little bullet aligned to the typical tree node.
BulletTextV(const char * fmt,va_list args)382 void ImGui::BulletTextV(const char* fmt, va_list args)
383 {
384 ImGuiWindow* window = GetCurrentWindow();
385 if (window->SkipItems)
386 return;
387
388 ImGuiContext& g = *GImGui;
389 const ImGuiStyle& style = g.Style;
390
391 const char* text_begin = g.TempBuffer;
392 const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
393 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
394 const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding
395 ImVec2 pos = window->DC.CursorPos;
396 pos.y += window->DC.CurrLineTextBaseOffset;
397 ItemSize(total_size, 0.0f);
398 const ImRect bb(pos, pos + total_size);
399 if (!ItemAdd(bb, 0))
400 return;
401
402 // Render
403 ImU32 text_col = GetColorU32(ImGuiCol_Text);
404 RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
405 RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
406 }
407
408 //-------------------------------------------------------------------------
409 // [SECTION] Widgets: Main
410 //-------------------------------------------------------------------------
411 // - ButtonBehavior() [Internal]
412 // - Button()
413 // - SmallButton()
414 // - InvisibleButton()
415 // - ArrowButton()
416 // - CloseButton() [Internal]
417 // - CollapseButton() [Internal]
418 // - GetWindowScrollbarID() [Internal]
419 // - GetWindowScrollbarRect() [Internal]
420 // - Scrollbar() [Internal]
421 // - ScrollbarEx() [Internal]
422 // - Image()
423 // - ImageButton()
424 // - Checkbox()
425 // - CheckboxFlagsT() [Internal]
426 // - CheckboxFlags()
427 // - RadioButton()
428 // - ProgressBar()
429 // - Bullet()
430 //-------------------------------------------------------------------------
431
432 // The ButtonBehavior() function is key to many interactions and used by many/most widgets.
433 // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
434 // this code is a little complex.
435 // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
436 // See the series of events below and the corresponding state reported by dear imgui:
437 //------------------------------------------------------------------------------------------------------------------------------------------------
438 // with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
439 // Frame N+0 (mouse is outside bb) - - - - - -
440 // Frame N+1 (mouse moves inside bb) - true - - - -
441 // Frame N+2 (mouse button is down) - true true true - true
442 // Frame N+3 (mouse button is down) - true true - - -
443 // Frame N+4 (mouse moves outside bb) - - true - - -
444 // Frame N+5 (mouse moves inside bb) - true true - - -
445 // Frame N+6 (mouse button is released) true true - - true -
446 // Frame N+7 (mouse button is released) - true - - - -
447 // Frame N+8 (mouse moves outside bb) - - - - - -
448 //------------------------------------------------------------------------------------------------------------------------------------------------
449 // with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
450 // Frame N+2 (mouse button is down) true true true true - true
451 // Frame N+3 (mouse button is down) - true true - - -
452 // Frame N+6 (mouse button is released) - true - - true -
453 // Frame N+7 (mouse button is released) - true - - - -
454 //------------------------------------------------------------------------------------------------------------------------------------------------
455 // with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
456 // Frame N+2 (mouse button is down) - true - - - true
457 // Frame N+3 (mouse button is down) - true - - - -
458 // Frame N+6 (mouse button is released) true true - - - -
459 // Frame N+7 (mouse button is released) - true - - - -
460 //------------------------------------------------------------------------------------------------------------------------------------------------
461 // with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
462 // Frame N+0 (mouse button is down) - true - - - true
463 // Frame N+1 (mouse button is down) - true - - - -
464 // Frame N+2 (mouse button is released) - true - - - -
465 // Frame N+3 (mouse button is released) - true - - - -
466 // Frame N+4 (mouse button is down) true true true true - true
467 // Frame N+5 (mouse button is down) - true true - - -
468 // Frame N+6 (mouse button is released) - true - - true -
469 // Frame N+7 (mouse button is released) - true - - - -
470 //------------------------------------------------------------------------------------------------------------------------------------------------
471 // Note that some combinations are supported,
472 // - PressedOnDragDropHold can generally be associated with any flag.
473 // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
474 //------------------------------------------------------------------------------------------------------------------------------------------------
475 // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
476 // Repeat+ Repeat+ Repeat+ Repeat+
477 // PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick
478 //-------------------------------------------------------------------------------------------------------------------------------------------------
479 // Frame N+0 (mouse button is down) - true - true
480 // ... - - - -
481 // Frame N + RepeatDelay true true - true
482 // ... - - - -
483 // Frame N + RepeatDelay + RepeatRate*N true true - true
484 //-------------------------------------------------------------------------------------------------------------------------------------------------
485
ButtonBehavior(const ImRect & bb,ImGuiID id,bool * out_hovered,bool * out_held,ImGuiButtonFlags flags)486 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
487 {
488 ImGuiContext& g = *GImGui;
489 ImGuiWindow* window = GetCurrentWindow();
490
491 // Default only reacts to left mouse button
492 if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
493 flags |= ImGuiButtonFlags_MouseButtonDefault_;
494
495 // Default behavior requires click + release inside bounding box
496 if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
497 flags |= ImGuiButtonFlags_PressedOnDefault_;
498
499 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
500 const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
501 if (flatten_hovered_children)
502 g.HoveredWindow = window;
503
504 #ifdef IMGUI_ENABLE_TEST_ENGINE
505 if (id != 0 && g.LastItemData.ID != id)
506 IMGUI_TEST_ENGINE_ITEM_ADD(bb, id);
507 #endif
508
509 bool pressed = false;
510 bool hovered = ItemHoverable(bb, id);
511
512 // Drag source doesn't report as hovered
513 if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
514 hovered = false;
515
516 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
517 if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
518 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
519 {
520 hovered = true;
521 SetHoveredID(id);
522 if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
523 {
524 pressed = true;
525 g.DragDropHoldJustPressedId = id;
526 FocusWindow(window);
527 }
528 }
529
530 if (flatten_hovered_children)
531 g.HoveredWindow = backup_hovered_window;
532
533 // 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.
534 if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
535 hovered = false;
536
537 // Mouse handling
538 if (hovered)
539 {
540 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
541 {
542 // Poll buttons
543 int mouse_button_clicked = -1;
544 int mouse_button_released = -1;
545 if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { mouse_button_clicked = 0; }
546 else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1]) { mouse_button_clicked = 1; }
547 else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2]) { mouse_button_clicked = 2; }
548 if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; }
549 else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; }
550 else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; }
551
552 if (mouse_button_clicked != -1 && g.ActiveId != id)
553 {
554 if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
555 {
556 SetActiveID(id, window);
557 g.ActiveIdMouseButton = mouse_button_clicked;
558 if (!(flags & ImGuiButtonFlags_NoNavFocus))
559 SetFocusID(id, window);
560 FocusWindow(window);
561 }
562 if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[mouse_button_clicked]))
563 {
564 pressed = true;
565 if (flags & ImGuiButtonFlags_NoHoldingActiveId)
566 ClearActiveID();
567 else
568 SetActiveID(id, window); // Hold on ID
569 g.ActiveIdMouseButton = mouse_button_clicked;
570 FocusWindow(window);
571 }
572 }
573 if ((flags & ImGuiButtonFlags_PressedOnRelease) && mouse_button_released != -1)
574 {
575 // Repeat mode trumps on release behavior
576 const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay;
577 if (!has_repeated_at_least_once)
578 pressed = true;
579 ClearActiveID();
580 }
581
582 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
583 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
584 if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat))
585 if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true))
586 pressed = true;
587 }
588
589 if (pressed)
590 g.NavDisableHighlight = true;
591 }
592
593 // Gamepad/Keyboard navigation
594 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
595 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
596 if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
597 hovered = true;
598 if (g.NavActivateDownId == id)
599 {
600 bool nav_activated_by_code = (g.NavActivateId == id);
601 bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
602 if (nav_activated_by_code || nav_activated_by_inputs)
603 pressed = true;
604 if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
605 {
606 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
607 g.NavActivateId = id; // This is so SetActiveId assign a Nav source
608 SetActiveID(id, window);
609 if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))
610 SetFocusID(id, window);
611 }
612 }
613
614 // Process while held
615 bool held = false;
616 if (g.ActiveId == id)
617 {
618 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
619 {
620 if (g.ActiveIdIsJustActivated)
621 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
622
623 const int mouse_button = g.ActiveIdMouseButton;
624 IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);
625 if (g.IO.MouseDown[mouse_button])
626 {
627 held = true;
628 }
629 else
630 {
631 bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
632 bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
633 if ((release_in || release_anywhere) && !g.DragDropActive)
634 {
635 // Report as pressed when releasing the mouse (this is the most common path)
636 bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[mouse_button];
637 bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
638 if (!is_double_click_release && !is_repeating_already)
639 pressed = true;
640 }
641 ClearActiveID();
642 }
643 if (!(flags & ImGuiButtonFlags_NoNavFocus))
644 g.NavDisableHighlight = true;
645 }
646 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
647 {
648 // When activated using Nav, we hold on the ActiveID until activation button is released
649 if (g.NavActivateDownId != id)
650 ClearActiveID();
651 }
652 if (pressed)
653 g.ActiveIdHasBeenPressedBefore = true;
654 }
655
656 if (out_hovered) *out_hovered = hovered;
657 if (out_held) *out_held = held;
658
659 return pressed;
660 }
661
ButtonEx(const char * label,const ImVec2 & size_arg,ImGuiButtonFlags flags)662 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
663 {
664 ImGuiWindow* window = GetCurrentWindow();
665 if (window->SkipItems)
666 return false;
667
668 ImGuiContext& g = *GImGui;
669 const ImGuiStyle& style = g.Style;
670 const ImGuiID id = window->GetID(label);
671 const ImVec2 label_size = CalcTextSize(label, NULL, true);
672
673 ImVec2 pos = window->DC.CursorPos;
674 if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
675 pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
676 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
677
678 const ImRect bb(pos, pos + size);
679 ItemSize(size, style.FramePadding.y);
680 if (!ItemAdd(bb, id))
681 return false;
682
683 if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
684 flags |= ImGuiButtonFlags_Repeat;
685
686 bool hovered, held;
687 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
688
689 // Render
690 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
691 RenderNavHighlight(bb, id);
692 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
693
694 if (g.LogEnabled)
695 LogSetNextTextDecoration("[", "]");
696 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
697
698 // Automatically close popups
699 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
700 // CloseCurrentPopup();
701
702 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
703 return pressed;
704 }
705
Button(const char * label,const ImVec2 & size_arg)706 bool ImGui::Button(const char* label, const ImVec2& size_arg)
707 {
708 return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
709 }
710
711 // Small buttons fits within text without additional vertical spacing.
SmallButton(const char * label)712 bool ImGui::SmallButton(const char* label)
713 {
714 ImGuiContext& g = *GImGui;
715 float backup_padding_y = g.Style.FramePadding.y;
716 g.Style.FramePadding.y = 0.0f;
717 bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
718 g.Style.FramePadding.y = backup_padding_y;
719 return pressed;
720 }
721
722 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
723 // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
InvisibleButton(const char * str_id,const ImVec2 & size_arg,ImGuiButtonFlags flags)724 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
725 {
726 ImGuiWindow* window = GetCurrentWindow();
727 if (window->SkipItems)
728 return false;
729
730 // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
731 IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
732
733 const ImGuiID id = window->GetID(str_id);
734 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
735 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
736 ItemSize(size);
737 if (!ItemAdd(bb, id))
738 return false;
739
740 bool hovered, held;
741 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
742
743 return pressed;
744 }
745
ArrowButtonEx(const char * str_id,ImGuiDir dir,ImVec2 size,ImGuiButtonFlags flags)746 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
747 {
748 ImGuiWindow* window = GetCurrentWindow();
749 if (window->SkipItems)
750 return false;
751
752 ImGuiContext& g = *GImGui;
753 const ImGuiID id = window->GetID(str_id);
754 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
755 const float default_size = GetFrameHeight();
756 ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
757 if (!ItemAdd(bb, id))
758 return false;
759
760 if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
761 flags |= ImGuiButtonFlags_Repeat;
762
763 bool hovered, held;
764 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
765
766 // Render
767 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
768 const ImU32 text_col = GetColorU32(ImGuiCol_Text);
769 RenderNavHighlight(bb, id);
770 RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
771 RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir);
772
773 return pressed;
774 }
775
ArrowButton(const char * str_id,ImGuiDir dir)776 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
777 {
778 float sz = GetFrameHeight();
779 return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
780 }
781
782 // Button to close a window
CloseButton(ImGuiID id,const ImVec2 & pos)783 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
784 {
785 ImGuiContext& g = *GImGui;
786 ImGuiWindow* window = g.CurrentWindow;
787
788 // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825)
789 // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible?
790 const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
791 ImRect bb_interact = bb;
792 const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
793 if (area_to_visible_ratio < 1.5f)
794 bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f));
795
796 // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
797 // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
798 bool is_clipped = !ItemAdd(bb_interact, id);
799
800 bool hovered, held;
801 bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
802 if (is_clipped)
803 return pressed;
804
805 // Render
806 // FIXME: Clarify this mess
807 ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
808 ImVec2 center = bb.GetCenter();
809 if (hovered)
810 window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
811
812 float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
813 ImU32 cross_col = GetColorU32(ImGuiCol_Text);
814 center -= ImVec2(0.5f, 0.5f);
815 window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
816 window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
817
818 return pressed;
819 }
820
821 // The Collapse button also functions as a Dock Menu button.
CollapseButton(ImGuiID id,const ImVec2 & pos,ImGuiDockNode * dock_node)822 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
823 {
824 ImGuiContext& g = *GImGui;
825 ImGuiWindow* window = g.CurrentWindow;
826
827 ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
828 ItemAdd(bb, id);
829 bool hovered, held;
830 bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
831
832 // Render
833 //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
834 ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
835 ImU32 text_col = GetColorU32(ImGuiCol_Text);
836 ImVec2 center = bb.GetCenter();
837 if (hovered || held)
838 window->DrawList->AddCircleFilled(center + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, bg_col, 12);
839
840 if (dock_node)
841 RenderArrowDockMenu(window->DrawList, bb.Min + g.Style.FramePadding, g.FontSize, text_col);
842 else
843 RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
844
845 // Switch to moving the window after mouse is moved beyond the initial drag threshold
846 if (IsItemActive() && IsMouseDragging(0))
847 StartMouseMovingWindowOrNode(window, dock_node, true);
848
849 return pressed;
850 }
851
GetWindowScrollbarID(ImGuiWindow * window,ImGuiAxis axis)852 ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
853 {
854 return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
855 }
856
857 // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
GetWindowScrollbarRect(ImGuiWindow * window,ImGuiAxis axis)858 ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
859 {
860 const ImRect outer_rect = window->Rect();
861 const ImRect inner_rect = window->InnerRect;
862 const float border_size = window->WindowBorderSize;
863 const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
864 IM_ASSERT(scrollbar_size > 0.0f);
865 if (axis == ImGuiAxis_X)
866 return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x, outer_rect.Max.y);
867 else
868 return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y);
869 }
870
Scrollbar(ImGuiAxis axis)871 void ImGui::Scrollbar(ImGuiAxis axis)
872 {
873 ImGuiContext& g = *GImGui;
874 ImGuiWindow* window = g.CurrentWindow;
875
876 const ImGuiID id = GetWindowScrollbarID(window, axis);
877 KeepAliveID(id);
878
879 // Calculate scrollbar bounding box
880 ImRect bb = GetWindowScrollbarRect(window, axis);
881 ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
882 if (axis == ImGuiAxis_X)
883 {
884 rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
885 if (!window->ScrollbarY)
886 rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
887 }
888 else
889 {
890 if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
891 rounding_corners |= ImDrawFlags_RoundCornersTopRight;
892 if (!window->ScrollbarX)
893 rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
894 }
895 float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
896 float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
897 ScrollbarEx(bb, id, axis, &window->Scroll[axis], size_avail, size_contents, rounding_corners);
898 }
899
900 // Vertical/Horizontal scrollbar
901 // The entire piece of code below is rather confusing because:
902 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
903 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
904 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
905 // Still, the code should probably be made simpler..
ScrollbarEx(const ImRect & bb_frame,ImGuiID id,ImGuiAxis axis,float * p_scroll_v,float size_avail_v,float size_contents_v,ImDrawFlags flags)906 bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawFlags flags)
907 {
908 ImGuiContext& g = *GImGui;
909 ImGuiWindow* window = g.CurrentWindow;
910 if (window->SkipItems)
911 return false;
912
913 const float bb_frame_width = bb_frame.GetWidth();
914 const float bb_frame_height = bb_frame.GetHeight();
915 if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
916 return false;
917
918 // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab)
919 float alpha = 1.0f;
920 if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
921 alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
922 if (alpha <= 0.0f)
923 return false;
924
925 const ImGuiStyle& style = g.Style;
926 const bool allow_interaction = (alpha >= 1.0f);
927
928 ImRect bb = bb_frame;
929 bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
930
931 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
932 const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
933
934 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
935 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
936 IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
937 const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f);
938 const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
939 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
940
941 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
942 bool held = false;
943 bool hovered = false;
944 ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
945
946 float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v);
947 float scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
948 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
949 if (held && allow_interaction && grab_h_norm < 1.0f)
950 {
951 float scrollbar_pos_v = bb.Min[axis];
952 float mouse_pos_v = g.IO.MousePos[axis];
953
954 // Click position in scrollbar normalized space (0.0f->1.0f)
955 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
956 SetHoveredID(id);
957
958 bool seek_absolute = false;
959 if (g.ActiveIdIsJustActivated)
960 {
961 // On initial click calculate the distance between mouse and the center of the grab
962 seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
963 if (seek_absolute)
964 g.ScrollbarClickDeltaToGrabCenter = 0.0f;
965 else
966 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
967 }
968
969 // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
970 // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position
971 const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
972 *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
973
974 // Update values for rendering
975 scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
976 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
977
978 // Update distance to grab now that we have seeked and saturated
979 if (seek_absolute)
980 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
981 }
982
983 // Render
984 const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
985 const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
986 window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
987 ImRect grab_rect;
988 if (axis == ImGuiAxis_X)
989 grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y);
990 else
991 grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels);
992 window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
993
994 return held;
995 }
996
Image(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec4 & tint_col,const ImVec4 & border_col)997 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
998 {
999 ImGuiWindow* window = GetCurrentWindow();
1000 if (window->SkipItems)
1001 return;
1002
1003 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1004 if (border_col.w > 0.0f)
1005 bb.Max += ImVec2(2, 2);
1006 ItemSize(bb);
1007 if (!ItemAdd(bb, 0))
1008 return;
1009
1010 if (border_col.w > 0.0f)
1011 {
1012 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
1013 window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
1014 }
1015 else
1016 {
1017 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
1018 }
1019 }
1020
1021 // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
1022 // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
ImageButtonEx(ImGuiID id,ImTextureID texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec2 & padding,const ImVec4 & bg_col,const ImVec4 & tint_col)1023 bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col)
1024 {
1025 ImGuiContext& g = *GImGui;
1026 ImGuiWindow* window = GetCurrentWindow();
1027 if (window->SkipItems)
1028 return false;
1029
1030 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
1031 ItemSize(bb);
1032 if (!ItemAdd(bb, id))
1033 return false;
1034
1035 bool hovered, held;
1036 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1037
1038 // Render
1039 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1040 RenderNavHighlight(bb, id);
1041 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
1042 if (bg_col.w > 0.0f)
1043 window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
1044 window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1045
1046 return pressed;
1047 }
1048
1049 // frame_padding < 0: uses FramePadding from style (default)
1050 // frame_padding = 0: no framing
1051 // frame_padding > 0: set framing size
ImageButton(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,int frame_padding,const ImVec4 & bg_col,const ImVec4 & tint_col)1052 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)
1053 {
1054 ImGuiContext& g = *GImGui;
1055 ImGuiWindow* window = g.CurrentWindow;
1056 if (window->SkipItems)
1057 return false;
1058
1059 // Default to using texture ID as ID. User can still push string/integer prefixes.
1060 PushID((void*)(intptr_t)user_texture_id);
1061 const ImGuiID id = window->GetID("#image");
1062 PopID();
1063
1064 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding;
1065 return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col);
1066 }
1067
Checkbox(const char * label,bool * v)1068 bool ImGui::Checkbox(const char* label, bool* v)
1069 {
1070 ImGuiWindow* window = GetCurrentWindow();
1071 if (window->SkipItems)
1072 return false;
1073
1074 ImGuiContext& g = *GImGui;
1075 const ImGuiStyle& style = g.Style;
1076 const ImGuiID id = window->GetID(label);
1077 const ImVec2 label_size = CalcTextSize(label, NULL, true);
1078
1079 const float square_sz = GetFrameHeight();
1080 const ImVec2 pos = window->DC.CursorPos;
1081 const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
1082 ItemSize(total_bb, style.FramePadding.y);
1083 if (!ItemAdd(total_bb, id))
1084 {
1085 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1086 return false;
1087 }
1088
1089 bool hovered, held;
1090 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1091 if (pressed)
1092 {
1093 *v = !(*v);
1094 MarkItemEdited(id);
1095 }
1096
1097 const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1098 RenderNavHighlight(total_bb, id);
1099 RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
1100 ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1101 bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
1102 if (mixed_value)
1103 {
1104 // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1105 // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1106 ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)));
1107 window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1108 }
1109 else if (*v)
1110 {
1111 const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
1112 RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1113 }
1114
1115 ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1116 if (g.LogEnabled)
1117 LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1118 if (label_size.x > 0.0f)
1119 RenderText(label_pos, label);
1120
1121 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1122 return pressed;
1123 }
1124
1125 template<typename T>
CheckboxFlagsT(const char * label,T * flags,T flags_value)1126 bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1127 {
1128 bool all_on = (*flags & flags_value) == flags_value;
1129 bool any_on = (*flags & flags_value) != 0;
1130 bool pressed;
1131 if (!all_on && any_on)
1132 {
1133 ImGuiContext& g = *GImGui;
1134 ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
1135 g.CurrentItemFlags |= ImGuiItemFlags_MixedValue;
1136 pressed = Checkbox(label, &all_on);
1137 g.CurrentItemFlags = backup_item_flags;
1138 }
1139 else
1140 {
1141 pressed = Checkbox(label, &all_on);
1142
1143 }
1144 if (pressed)
1145 {
1146 if (all_on)
1147 *flags |= flags_value;
1148 else
1149 *flags &= ~flags_value;
1150 }
1151 return pressed;
1152 }
1153
CheckboxFlags(const char * label,int * flags,int flags_value)1154 bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1155 {
1156 return CheckboxFlagsT(label, flags, flags_value);
1157 }
1158
CheckboxFlags(const char * label,unsigned int * flags,unsigned int flags_value)1159 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1160 {
1161 return CheckboxFlagsT(label, flags, flags_value);
1162 }
1163
CheckboxFlags(const char * label,ImS64 * flags,ImS64 flags_value)1164 bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1165 {
1166 return CheckboxFlagsT(label, flags, flags_value);
1167 }
1168
CheckboxFlags(const char * label,ImU64 * flags,ImU64 flags_value)1169 bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1170 {
1171 return CheckboxFlagsT(label, flags, flags_value);
1172 }
1173
RadioButton(const char * label,bool active)1174 bool ImGui::RadioButton(const char* label, bool active)
1175 {
1176 ImGuiWindow* window = GetCurrentWindow();
1177 if (window->SkipItems)
1178 return false;
1179
1180 ImGuiContext& g = *GImGui;
1181 const ImGuiStyle& style = g.Style;
1182 const ImGuiID id = window->GetID(label);
1183 const ImVec2 label_size = CalcTextSize(label, NULL, true);
1184
1185 const float square_sz = GetFrameHeight();
1186 const ImVec2 pos = window->DC.CursorPos;
1187 const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1188 const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
1189 ItemSize(total_bb, style.FramePadding.y);
1190 if (!ItemAdd(total_bb, id))
1191 return false;
1192
1193 ImVec2 center = check_bb.GetCenter();
1194 center.x = IM_ROUND(center.x);
1195 center.y = IM_ROUND(center.y);
1196 const float radius = (square_sz - 1.0f) * 0.5f;
1197
1198 bool hovered, held;
1199 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1200 if (pressed)
1201 MarkItemEdited(id);
1202
1203 RenderNavHighlight(total_bb, id);
1204 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
1205 if (active)
1206 {
1207 const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
1208 window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
1209 }
1210
1211 if (style.FrameBorderSize > 0.0f)
1212 {
1213 window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
1214 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
1215 }
1216
1217 ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1218 if (g.LogEnabled)
1219 LogRenderedText(&label_pos, active ? "(x)" : "( )");
1220 if (label_size.x > 0.0f)
1221 RenderText(label_pos, label);
1222
1223 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1224 return pressed;
1225 }
1226
1227 // FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it..
RadioButton(const char * label,int * v,int v_button)1228 bool ImGui::RadioButton(const char* label, int* v, int v_button)
1229 {
1230 const bool pressed = RadioButton(label, *v == v_button);
1231 if (pressed)
1232 *v = v_button;
1233 return pressed;
1234 }
1235
1236 // 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)1237 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1238 {
1239 ImGuiWindow* window = GetCurrentWindow();
1240 if (window->SkipItems)
1241 return;
1242
1243 ImGuiContext& g = *GImGui;
1244 const ImGuiStyle& style = g.Style;
1245
1246 ImVec2 pos = window->DC.CursorPos;
1247 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1248 ImRect bb(pos, pos + size);
1249 ItemSize(size, style.FramePadding.y);
1250 if (!ItemAdd(bb, 0))
1251 return;
1252
1253 // Render
1254 fraction = ImSaturate(fraction);
1255 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1256 bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1257 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
1258 RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
1259
1260 // Default displaying the fraction as percentage string, but user can override it
1261 char overlay_buf[32];
1262 if (!overlay)
1263 {
1264 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1265 overlay = overlay_buf;
1266 }
1267
1268 ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1269 if (overlay_size.x > 0.0f)
1270 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);
1271 }
1272
Bullet()1273 void ImGui::Bullet()
1274 {
1275 ImGuiWindow* window = GetCurrentWindow();
1276 if (window->SkipItems)
1277 return;
1278
1279 ImGuiContext& g = *GImGui;
1280 const ImGuiStyle& style = g.Style;
1281 const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
1282 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1283 ItemSize(bb);
1284 if (!ItemAdd(bb, 0))
1285 {
1286 SameLine(0, style.FramePadding.x * 2);
1287 return;
1288 }
1289
1290 // Render and stay on same line
1291 ImU32 text_col = GetColorU32(ImGuiCol_Text);
1292 RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
1293 SameLine(0, style.FramePadding.x * 2.0f);
1294 }
1295
1296 //-------------------------------------------------------------------------
1297 // [SECTION] Widgets: Low-level Layout helpers
1298 //-------------------------------------------------------------------------
1299 // - Spacing()
1300 // - Dummy()
1301 // - NewLine()
1302 // - AlignTextToFramePadding()
1303 // - SeparatorEx() [Internal]
1304 // - Separator()
1305 // - SplitterBehavior() [Internal]
1306 // - ShrinkWidths() [Internal]
1307 //-------------------------------------------------------------------------
1308
Spacing()1309 void ImGui::Spacing()
1310 {
1311 ImGuiWindow* window = GetCurrentWindow();
1312 if (window->SkipItems)
1313 return;
1314 ItemSize(ImVec2(0, 0));
1315 }
1316
Dummy(const ImVec2 & size)1317 void ImGui::Dummy(const ImVec2& size)
1318 {
1319 ImGuiWindow* window = GetCurrentWindow();
1320 if (window->SkipItems)
1321 return;
1322
1323 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1324 ItemSize(size);
1325 ItemAdd(bb, 0);
1326 }
1327
NewLine()1328 void ImGui::NewLine()
1329 {
1330 ImGuiWindow* window = GetCurrentWindow();
1331 if (window->SkipItems)
1332 return;
1333
1334 ImGuiContext& g = *GImGui;
1335 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1336 window->DC.LayoutType = ImGuiLayoutType_Vertical;
1337 if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
1338 ItemSize(ImVec2(0, 0));
1339 else
1340 ItemSize(ImVec2(0.0f, g.FontSize));
1341 window->DC.LayoutType = backup_layout_type;
1342 }
1343
AlignTextToFramePadding()1344 void ImGui::AlignTextToFramePadding()
1345 {
1346 ImGuiWindow* window = GetCurrentWindow();
1347 if (window->SkipItems)
1348 return;
1349
1350 ImGuiContext& g = *GImGui;
1351 window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1352 window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1353 }
1354
1355 // Horizontal/vertical separating line
SeparatorEx(ImGuiSeparatorFlags flags)1356 void ImGui::SeparatorEx(ImGuiSeparatorFlags flags)
1357 {
1358 ImGuiWindow* window = GetCurrentWindow();
1359 if (window->SkipItems)
1360 return;
1361
1362 ImGuiContext& g = *GImGui;
1363 IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected
1364
1365 float thickness_draw = 1.0f;
1366 float thickness_layout = 0.0f;
1367 if (flags & ImGuiSeparatorFlags_Vertical)
1368 {
1369 // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout.
1370 float y1 = window->DC.CursorPos.y;
1371 float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1372 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2));
1373 ItemSize(ImVec2(thickness_layout, 0.0f));
1374 if (!ItemAdd(bb, 0))
1375 return;
1376
1377 // Draw
1378 window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
1379 if (g.LogEnabled)
1380 LogText(" |");
1381 }
1382 else if (flags & ImGuiSeparatorFlags_Horizontal)
1383 {
1384 // Horizontal Separator
1385 float x1 = window->Pos.x;
1386 float x2 = window->Pos.x + window->Size.x;
1387
1388 // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator
1389 if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID)
1390 x1 += window->DC.Indent.x;
1391
1392 ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1393 if (columns)
1394 PushColumnsBackground();
1395
1396 // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1397 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw));
1398 ItemSize(ImVec2(0.0f, thickness_layout));
1399 const bool item_visible = ItemAdd(bb, 0);
1400 if (item_visible)
1401 {
1402 // Draw
1403 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
1404 if (g.LogEnabled)
1405 LogRenderedText(&bb.Min, "--------------------------------\n");
1406
1407 }
1408 if (columns)
1409 {
1410 PopColumnsBackground();
1411 columns->LineMinY = window->DC.CursorPos.y;
1412 }
1413 }
1414 }
1415
Separator()1416 void ImGui::Separator()
1417 {
1418 ImGuiContext& g = *GImGui;
1419 ImGuiWindow* window = g.CurrentWindow;
1420 if (window->SkipItems)
1421 return;
1422
1423 // Those flags should eventually be overridable by the user
1424 ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1425 flags |= ImGuiSeparatorFlags_SpanAllColumns;
1426 SeparatorEx(flags);
1427 }
1428
1429 // 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)1430 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)
1431 {
1432 ImGuiContext& g = *GImGui;
1433 ImGuiWindow* window = g.CurrentWindow;
1434
1435 const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
1436 g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
1437 bool item_add = ItemAdd(bb, id);
1438 g.CurrentItemFlags = item_flags_backup;
1439 if (!item_add)
1440 return false;
1441
1442 bool hovered, held;
1443 ImRect bb_interact = bb;
1444 bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1445 ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
1446 if (hovered)
1447 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1448 if (g.ActiveId != id)
1449 SetItemAllowOverlap();
1450
1451 if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1452 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1453
1454 ImRect bb_render = bb;
1455 if (held)
1456 {
1457 ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
1458 float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
1459
1460 // Minimum pane size
1461 float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1462 float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1463 if (mouse_delta < -size_1_maximum_delta)
1464 mouse_delta = -size_1_maximum_delta;
1465 if (mouse_delta > size_2_maximum_delta)
1466 mouse_delta = size_2_maximum_delta;
1467
1468 // Apply resize
1469 if (mouse_delta != 0.0f)
1470 {
1471 if (mouse_delta < 0.0f)
1472 IM_ASSERT(*size1 + mouse_delta >= min_size1);
1473 if (mouse_delta > 0.0f)
1474 IM_ASSERT(*size2 - mouse_delta >= min_size2);
1475 *size1 += mouse_delta;
1476 *size2 -= mouse_delta;
1477 bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1478 MarkItemEdited(id);
1479 }
1480 }
1481
1482 // Render
1483 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1484 window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1485
1486 return held;
1487 }
1488
ShrinkWidthItemComparer(const void * lhs,const void * rhs)1489 static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1490 {
1491 const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1492 const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1493 if (int d = (int)(b->Width - a->Width))
1494 return d;
1495 return (b->Index - a->Index);
1496 }
1497
1498 // Shrink excess width from a set of item, by removing width from the larger items first.
1499 // Set items Width to -1.0f to disable shrinking this item.
ShrinkWidths(ImGuiShrinkWidthItem * items,int count,float width_excess)1500 void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1501 {
1502 if (count == 1)
1503 {
1504 if (items[0].Width >= 0.0f)
1505 items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
1506 return;
1507 }
1508 ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
1509 int count_same_width = 1;
1510 while (width_excess > 0.0f && count_same_width < count)
1511 {
1512 while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1513 count_same_width++;
1514 float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);
1515 if (max_width_to_remove_per_item <= 0.0f)
1516 break;
1517 float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
1518 for (int item_n = 0; item_n < count_same_width; item_n++)
1519 items[item_n].Width -= width_to_remove_per_item;
1520 width_excess -= width_to_remove_per_item * count_same_width;
1521 }
1522
1523 // Round width and redistribute remainder left-to-right (could make it an option of the function?)
1524 // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.
1525 width_excess = 0.0f;
1526 for (int n = 0; n < count; n++)
1527 {
1528 float width_rounded = ImFloor(items[n].Width);
1529 width_excess += items[n].Width - width_rounded;
1530 items[n].Width = width_rounded;
1531 }
1532 if (width_excess > 0.0f)
1533 for (int n = 0; n < count; n++)
1534 if (items[n].Index < (int)(width_excess + 0.01f))
1535 items[n].Width += 1.0f;
1536 }
1537
1538 //-------------------------------------------------------------------------
1539 // [SECTION] Widgets: ComboBox
1540 //-------------------------------------------------------------------------
1541 // - CalcMaxPopupHeightFromItemCount() [Internal]
1542 // - BeginCombo()
1543 // - BeginComboPopup() [Internal]
1544 // - EndCombo()
1545 // - BeginComboPreview() [Internal]
1546 // - EndComboPreview() [Internal]
1547 // - Combo()
1548 //-------------------------------------------------------------------------
1549
CalcMaxPopupHeightFromItemCount(int items_count)1550 static float CalcMaxPopupHeightFromItemCount(int items_count)
1551 {
1552 ImGuiContext& g = *GImGui;
1553 if (items_count <= 0)
1554 return FLT_MAX;
1555 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1556 }
1557
BeginCombo(const char * label,const char * preview_value,ImGuiComboFlags flags)1558 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1559 {
1560 ImGuiContext& g = *GImGui;
1561 ImGuiWindow* window = GetCurrentWindow();
1562
1563 ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1564 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1565 if (window->SkipItems)
1566 return false;
1567
1568 const ImGuiStyle& style = g.Style;
1569 const ImGuiID id = window->GetID(label);
1570 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1571
1572 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1573 const ImVec2 label_size = CalcTextSize(label, NULL, true);
1574 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
1575 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1576 const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1577 ItemSize(total_bb, style.FramePadding.y);
1578 if (!ItemAdd(total_bb, id, &bb))
1579 return false;
1580
1581 // Open on click
1582 bool hovered, held;
1583 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1584 const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1585 bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1586 if ((pressed || g.NavActivateId == id) && !popup_open)
1587 {
1588 OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1589 popup_open = true;
1590 }
1591
1592 // Render shape
1593 const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1594 const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1595 RenderNavHighlight(bb, id);
1596 if (!(flags & ImGuiComboFlags_NoPreview))
1597 window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1598 if (!(flags & ImGuiComboFlags_NoArrowButton))
1599 {
1600 ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1601 ImU32 text_col = GetColorU32(ImGuiCol_Text);
1602 window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1603 if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1604 RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
1605 }
1606 RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1607
1608 // Custom preview
1609 if (flags & ImGuiComboFlags_CustomPreview)
1610 {
1611 g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1612 IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1613 preview_value = NULL;
1614 }
1615
1616 // Render preview and label
1617 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1618 {
1619 if (g.LogEnabled)
1620 LogSetNextTextDecoration("{", "}");
1621 RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
1622 }
1623 if (label_size.x > 0)
1624 RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
1625
1626 if (!popup_open)
1627 return false;
1628
1629 g.NextWindowData.Flags = backup_next_window_data_flags;
1630 return BeginComboPopup(popup_id, bb, flags);
1631 }
1632
BeginComboPopup(ImGuiID popup_id,const ImRect & bb,ImGuiComboFlags flags)1633 bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1634 {
1635 ImGuiContext& g = *GImGui;
1636 if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
1637 {
1638 g.NextWindowData.ClearFlags();
1639 return false;
1640 }
1641
1642 // Set popup size
1643 float w = bb.GetWidth();
1644 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1645 {
1646 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1647 }
1648 else
1649 {
1650 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1651 flags |= ImGuiComboFlags_HeightRegular;
1652 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1653 int popup_max_height_in_items = -1;
1654 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
1655 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
1656 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
1657 SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1658 }
1659
1660 // This is essentially a specialized version of BeginPopupEx()
1661 char name[16];
1662 ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
1663
1664 // Set position given a custom constraint (peak into expected window size so we can position it)
1665 // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1666 // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1667 if (ImGuiWindow* popup_window = FindWindowByName(name))
1668 if (popup_window->WasActive)
1669 {
1670 // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1671 ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
1672 popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1673 ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
1674 ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
1675 SetNextWindowPos(pos);
1676 }
1677
1678 // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1679 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1680 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text
1681 bool ret = Begin(name, NULL, window_flags);
1682 PopStyleVar();
1683 if (!ret)
1684 {
1685 EndPopup();
1686 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1687 return false;
1688 }
1689 return true;
1690 }
1691
EndCombo()1692 void ImGui::EndCombo()
1693 {
1694 EndPopup();
1695 }
1696
1697 // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1698 // (Experimental, see GitHub issues: #1658, #4168)
BeginComboPreview()1699 bool ImGui::BeginComboPreview()
1700 {
1701 ImGuiContext& g = *GImGui;
1702 ImGuiWindow* window = g.CurrentWindow;
1703 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1704
1705 if (window->SkipItems || !window->ClipRect.Overlaps(g.LastItemData.Rect)) // FIXME: Because we don't have a ImGuiItemStatusFlags_Visible flag to test last ItemAdd() result
1706 return false;
1707 IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag?
1708 if (!window->ClipRect.Contains(preview_data->PreviewRect)) // Narrower test (optional)
1709 return false;
1710
1711 // FIXME: This could be contained in a PushWorkRect() api
1712 preview_data->BackupCursorPos = window->DC.CursorPos;
1713 preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1714 preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1715 preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1716 preview_data->BackupLayout = window->DC.LayoutType;
1717 window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1718 window->DC.CursorMaxPos = window->DC.CursorPos;
1719 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1720 PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
1721
1722 return true;
1723 }
1724
EndComboPreview()1725 void ImGui::EndComboPreview()
1726 {
1727 ImGuiContext& g = *GImGui;
1728 ImGuiWindow* window = g.CurrentWindow;
1729 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1730
1731 // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
1732 ImDrawList* draw_list = window->DrawList;
1733 if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
1734 if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1735 {
1736 draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
1737 draw_list->_TryMergeDrawCmds();
1738 }
1739 PopClipRect();
1740 window->DC.CursorPos = preview_data->BackupCursorPos;
1741 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
1742 window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1743 window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1744 window->DC.LayoutType = preview_data->BackupLayout;
1745 preview_data->PreviewRect = ImRect();
1746 }
1747
1748 // Getter for the old Combo() API: const char*[]
Items_ArrayGetter(void * data,int idx,const char ** out_text)1749 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
1750 {
1751 const char* const* items = (const char* const*)data;
1752 if (out_text)
1753 *out_text = items[idx];
1754 return true;
1755 }
1756
1757 // Getter for the old Combo() API: "item1\0item2\0item3\0"
Items_SingleStringGetter(void * data,int idx,const char ** out_text)1758 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
1759 {
1760 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
1761 const char* items_separated_by_zeros = (const char*)data;
1762 int items_count = 0;
1763 const char* p = items_separated_by_zeros;
1764 while (*p)
1765 {
1766 if (idx == items_count)
1767 break;
1768 p += strlen(p) + 1;
1769 items_count++;
1770 }
1771 if (!*p)
1772 return false;
1773 if (out_text)
1774 *out_text = p;
1775 return true;
1776 }
1777
1778 // 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)1779 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)
1780 {
1781 ImGuiContext& g = *GImGui;
1782
1783 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1784 const char* preview_value = NULL;
1785 if (*current_item >= 0 && *current_item < items_count)
1786 items_getter(data, *current_item, &preview_value);
1787
1788 // 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.
1789 if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1790 SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1791
1792 if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1793 return false;
1794
1795 // Display items
1796 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1797 bool value_changed = false;
1798 for (int i = 0; i < items_count; i++)
1799 {
1800 PushID((void*)(intptr_t)i);
1801 const bool item_selected = (i == *current_item);
1802 const char* item_text;
1803 if (!items_getter(data, i, &item_text))
1804 item_text = "*Unknown item*";
1805 if (Selectable(item_text, item_selected))
1806 {
1807 value_changed = true;
1808 *current_item = i;
1809 }
1810 if (item_selected)
1811 SetItemDefaultFocus();
1812 PopID();
1813 }
1814
1815 EndCombo();
1816
1817 if (value_changed)
1818 MarkItemEdited(g.LastItemData.ID);
1819
1820 return value_changed;
1821 }
1822
1823 // 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)1824 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1825 {
1826 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
1827 return value_changed;
1828 }
1829
1830 // 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)1831 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1832 {
1833 int items_count = 0;
1834 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
1835 while (*p)
1836 {
1837 p += strlen(p) + 1;
1838 items_count++;
1839 }
1840 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
1841 return value_changed;
1842 }
1843
1844 //-------------------------------------------------------------------------
1845 // [SECTION] Data Type and Data Formatting Helpers [Internal]
1846 //-------------------------------------------------------------------------
1847 // - PatchFormatStringFloatToInt()
1848 // - DataTypeGetInfo()
1849 // - DataTypeFormatString()
1850 // - DataTypeApplyOp()
1851 // - DataTypeApplyOpFromText()
1852 // - DataTypeClamp()
1853 // - GetMinimumStepAtDecimalPrecision
1854 // - RoundScalarWithFormat<>()
1855 //-------------------------------------------------------------------------
1856
1857 static const ImGuiDataTypeInfo GDataTypeInfo[] =
1858 {
1859 { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8
1860 { sizeof(unsigned char), "U8", "%u", "%u" },
1861 { sizeof(short), "S16", "%d", "%d" }, // ImGuiDataType_S16
1862 { sizeof(unsigned short), "U16", "%u", "%u" },
1863 { sizeof(int), "S32", "%d", "%d" }, // ImGuiDataType_S32
1864 { sizeof(unsigned int), "U32", "%u", "%u" },
1865 #ifdef _MSC_VER
1866 { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64
1867 { sizeof(ImU64), "U64", "%I64u","%I64u" },
1868 #else
1869 { sizeof(ImS64), "S64", "%lld", "%lld" }, // ImGuiDataType_S64
1870 { sizeof(ImU64), "U64", "%llu", "%llu" },
1871 #endif
1872 { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg)
1873 { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double
1874 };
1875 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1876
1877 // 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".
1878 // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
1879 // 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)1880 static const char* PatchFormatStringFloatToInt(const char* fmt)
1881 {
1882 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.
1883 return "%d";
1884 const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)
1885 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).
1886 if (fmt_end > fmt_start && fmt_end[-1] == 'f')
1887 {
1888 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1889 if (fmt_start == fmt && fmt_end[0] == 0)
1890 return "%d";
1891 ImGuiContext& g = *GImGui;
1892 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.
1893 return g.TempBuffer;
1894 #else
1895 IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
1896 #endif
1897 }
1898 return fmt;
1899 }
1900
DataTypeGetInfo(ImGuiDataType data_type)1901 const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
1902 {
1903 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
1904 return &GDataTypeInfo[data_type];
1905 }
1906
DataTypeFormatString(char * buf,int buf_size,ImGuiDataType data_type,const void * p_data,const char * format)1907 int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
1908 {
1909 // Signedness doesn't matter when pushing integer arguments
1910 if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
1911 return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
1912 if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1913 return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
1914 if (data_type == ImGuiDataType_Float)
1915 return ImFormatString(buf, buf_size, format, *(const float*)p_data);
1916 if (data_type == ImGuiDataType_Double)
1917 return ImFormatString(buf, buf_size, format, *(const double*)p_data);
1918 if (data_type == ImGuiDataType_S8)
1919 return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
1920 if (data_type == ImGuiDataType_U8)
1921 return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
1922 if (data_type == ImGuiDataType_S16)
1923 return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
1924 if (data_type == ImGuiDataType_U16)
1925 return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
1926 IM_ASSERT(0);
1927 return 0;
1928 }
1929
DataTypeApplyOp(ImGuiDataType data_type,int op,void * output,const void * arg1,const void * arg2)1930 void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
1931 {
1932 IM_ASSERT(op == '+' || op == '-');
1933 switch (data_type)
1934 {
1935 case ImGuiDataType_S8:
1936 if (op == '+') { *(ImS8*)output = ImAddClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); }
1937 if (op == '-') { *(ImS8*)output = ImSubClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); }
1938 return;
1939 case ImGuiDataType_U8:
1940 if (op == '+') { *(ImU8*)output = ImAddClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); }
1941 if (op == '-') { *(ImU8*)output = ImSubClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); }
1942 return;
1943 case ImGuiDataType_S16:
1944 if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
1945 if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
1946 return;
1947 case ImGuiDataType_U16:
1948 if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
1949 if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
1950 return;
1951 case ImGuiDataType_S32:
1952 if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
1953 if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
1954 return;
1955 case ImGuiDataType_U32:
1956 if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
1957 if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
1958 return;
1959 case ImGuiDataType_S64:
1960 if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
1961 if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
1962 return;
1963 case ImGuiDataType_U64:
1964 if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
1965 if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
1966 return;
1967 case ImGuiDataType_Float:
1968 if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
1969 if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
1970 return;
1971 case ImGuiDataType_Double:
1972 if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
1973 if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
1974 return;
1975 case ImGuiDataType_COUNT: break;
1976 }
1977 IM_ASSERT(0);
1978 }
1979
1980 // User can input math operators (e.g. +100) to edit a numerical values.
1981 // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
DataTypeApplyOpFromText(const char * buf,const char * initial_value_buf,ImGuiDataType data_type,void * p_data,const char * format)1982 bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format)
1983 {
1984 while (ImCharIsBlankA(*buf))
1985 buf++;
1986
1987 // We don't support '-' op because it would conflict with inputing negative value.
1988 // Instead you can use +-100 to subtract from an existing value
1989 char op = buf[0];
1990 if (op == '+' || op == '*' || op == '/')
1991 {
1992 buf++;
1993 while (ImCharIsBlankA(*buf))
1994 buf++;
1995 }
1996 else
1997 {
1998 op = 0;
1999 }
2000 if (!buf[0])
2001 return false;
2002
2003 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2004 const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2005 ImGuiDataTypeTempStorage data_backup;
2006 memcpy(&data_backup, p_data, type_info->Size);
2007
2008 if (format == NULL)
2009 format = type_info->ScanFmt;
2010
2011 // FIXME-LEGACY: The aim is to remove those operators and write a proper expression evaluator at some point..
2012 int arg1i = 0;
2013 if (data_type == ImGuiDataType_S32)
2014 {
2015 int* v = (int*)p_data;
2016 int arg0i = *v;
2017 float arg1f = 0.0f;
2018 if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
2019 return false;
2020 // 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
2021 if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)
2022 else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply
2023 else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide
2024 else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant
2025 }
2026 else if (data_type == ImGuiDataType_Float)
2027 {
2028 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
2029 format = "%f";
2030 float* v = (float*)p_data;
2031 float arg0f = *v, arg1f = 0.0f;
2032 if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
2033 return false;
2034 if (sscanf(buf, format, &arg1f) < 1)
2035 return false;
2036 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
2037 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
2038 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
2039 else { *v = arg1f; } // Assign constant
2040 }
2041 else if (data_type == ImGuiDataType_Double)
2042 {
2043 format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
2044 double* v = (double*)p_data;
2045 double arg0f = *v, arg1f = 0.0;
2046 if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
2047 return false;
2048 if (sscanf(buf, format, &arg1f) < 1)
2049 return false;
2050 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
2051 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
2052 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
2053 else { *v = arg1f; } // Assign constant
2054 }
2055 else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2056 {
2057 // All other types assign constant
2058 // We don't bother handling support for legacy operators since they are a little too crappy. Instead we will later implement a proper expression evaluator in the future.
2059 if (sscanf(buf, format, p_data) < 1)
2060 return false;
2061 }
2062 else
2063 {
2064 // Small types need a 32-bit buffer to receive the result from scanf()
2065 int v32;
2066 if (sscanf(buf, format, &v32) < 1)
2067 return false;
2068 if (data_type == ImGuiDataType_S8)
2069 *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
2070 else if (data_type == ImGuiDataType_U8)
2071 *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
2072 else if (data_type == ImGuiDataType_S16)
2073 *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
2074 else if (data_type == ImGuiDataType_U16)
2075 *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
2076 else
2077 IM_ASSERT(0);
2078 }
2079
2080 return memcmp(&data_backup, p_data, type_info->Size) != 0;
2081 }
2082
2083 template<typename T>
DataTypeCompareT(const T * lhs,const T * rhs)2084 static int DataTypeCompareT(const T* lhs, const T* rhs)
2085 {
2086 if (*lhs < *rhs) return -1;
2087 if (*lhs > *rhs) return +1;
2088 return 0;
2089 }
2090
DataTypeCompare(ImGuiDataType data_type,const void * arg_1,const void * arg_2)2091 int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2092 {
2093 switch (data_type)
2094 {
2095 case ImGuiDataType_S8: return DataTypeCompareT<ImS8 >((const ImS8* )arg_1, (const ImS8* )arg_2);
2096 case ImGuiDataType_U8: return DataTypeCompareT<ImU8 >((const ImU8* )arg_1, (const ImU8* )arg_2);
2097 case ImGuiDataType_S16: return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
2098 case ImGuiDataType_U16: return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
2099 case ImGuiDataType_S32: return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
2100 case ImGuiDataType_U32: return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
2101 case ImGuiDataType_S64: return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
2102 case ImGuiDataType_U64: return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
2103 case ImGuiDataType_Float: return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
2104 case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
2105 case ImGuiDataType_COUNT: break;
2106 }
2107 IM_ASSERT(0);
2108 return 0;
2109 }
2110
2111 template<typename T>
DataTypeClampT(T * v,const T * v_min,const T * v_max)2112 static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2113 {
2114 // Clamp, both sides are optional, return true if modified
2115 if (v_min && *v < *v_min) { *v = *v_min; return true; }
2116 if (v_max && *v > *v_max) { *v = *v_max; return true; }
2117 return false;
2118 }
2119
DataTypeClamp(ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max)2120 bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2121 {
2122 switch (data_type)
2123 {
2124 case ImGuiDataType_S8: return DataTypeClampT<ImS8 >((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max);
2125 case ImGuiDataType_U8: return DataTypeClampT<ImU8 >((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max);
2126 case ImGuiDataType_S16: return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
2127 case ImGuiDataType_U16: return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
2128 case ImGuiDataType_S32: return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
2129 case ImGuiDataType_U32: return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
2130 case ImGuiDataType_S64: return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
2131 case ImGuiDataType_U64: return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
2132 case ImGuiDataType_Float: return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
2133 case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
2134 case ImGuiDataType_COUNT: break;
2135 }
2136 IM_ASSERT(0);
2137 return false;
2138 }
2139
GetMinimumStepAtDecimalPrecision(int decimal_precision)2140 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2141 {
2142 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 };
2143 if (decimal_precision < 0)
2144 return FLT_MIN;
2145 return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
2146 }
2147
2148 template<typename TYPE>
ImAtoi(const char * src,TYPE * output)2149 static const char* ImAtoi(const char* src, TYPE* output)
2150 {
2151 int negative = 0;
2152 if (*src == '-') { negative = 1; src++; }
2153 if (*src == '+') { src++; }
2154 TYPE v = 0;
2155 while (*src >= '0' && *src <= '9')
2156 v = (v * 10) + (*src++ - '0');
2157 *output = negative ? -v : v;
2158 return src;
2159 }
2160
2161 // Sanitize format
2162 // - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
2163 // - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
SanitizeFormatString(const char * fmt,char * fmt_out,size_t fmt_out_size)2164 static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size)
2165 {
2166 IM_UNUSED(fmt_out_size);
2167 const char* fmt_end = ImParseFormatFindEnd(fmt);
2168 IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
2169 while (fmt < fmt_end)
2170 {
2171 char c = *(fmt++);
2172 if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
2173 *(fmt_out++) = c;
2174 }
2175 *fmt_out = 0; // Zero-terminate
2176 }
2177
2178 template<typename TYPE, typename SIGNEDTYPE>
2179 TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2180 {
2181 const char* fmt_start = ImParseFormatFindStart(format);
2182 if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2183 return v;
2184
2185 // Sanitize format
2186 char fmt_sanitized[32];
2187 SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
2188 fmt_start = fmt_sanitized;
2189
2190 // Format value with our rounding, and read back
2191 char v_str[64];
2192 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
2193 const char* p = v_str;
2194 while (*p == ' ')
2195 p++;
2196 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2197 v = (TYPE)ImAtof(p);
2198 else
2199 ImAtoi(p, (SIGNEDTYPE*)&v);
2200 return v;
2201 }
2202
2203 //-------------------------------------------------------------------------
2204 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2205 //-------------------------------------------------------------------------
2206 // - DragBehaviorT<>() [Internal]
2207 // - DragBehavior() [Internal]
2208 // - DragScalar()
2209 // - DragScalarN()
2210 // - DragFloat()
2211 // - DragFloat2()
2212 // - DragFloat3()
2213 // - DragFloat4()
2214 // - DragFloatRange2()
2215 // - DragInt()
2216 // - DragInt2()
2217 // - DragInt3()
2218 // - DragInt4()
2219 // - DragIntRange2()
2220 //-------------------------------------------------------------------------
2221
2222 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2223 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
DragBehaviorT(ImGuiDataType data_type,TYPE * v,float v_speed,const TYPE v_min,const TYPE v_max,const char * format,ImGuiSliderFlags flags)2224 bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2225 {
2226 ImGuiContext& g = *GImGui;
2227 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2228 const bool is_clamped = (v_min < v_max);
2229 const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2230 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2231
2232 // Default tweak speed
2233 if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
2234 v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2235
2236 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2237 float adjust_delta = 0.0f;
2238 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2239 {
2240 adjust_delta = g.IO.MouseDelta[axis];
2241 if (g.IO.KeyAlt)
2242 adjust_delta *= 1.0f / 100.0f;
2243 if (g.IO.KeyShift)
2244 adjust_delta *= 10.0f;
2245 }
2246 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2247 {
2248 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2249 adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
2250 v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
2251 }
2252 adjust_delta *= v_speed;
2253
2254 // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2255 if (axis == ImGuiAxis_Y)
2256 adjust_delta = -adjust_delta;
2257
2258 // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2259 if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2260 adjust_delta /= (float)(v_max - v_min);
2261
2262 // Clear current value on activation
2263 // 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.
2264 bool is_just_activated = g.ActiveIdIsJustActivated;
2265 bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
2266 if (is_just_activated || is_already_past_limits_and_pushing_outward)
2267 {
2268 g.DragCurrentAccum = 0.0f;
2269 g.DragCurrentAccumDirty = false;
2270 }
2271 else if (adjust_delta != 0.0f)
2272 {
2273 g.DragCurrentAccum += adjust_delta;
2274 g.DragCurrentAccumDirty = true;
2275 }
2276
2277 if (!g.DragCurrentAccumDirty)
2278 return false;
2279
2280 TYPE v_cur = *v;
2281 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2282
2283 float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2284 const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2285 if (is_logarithmic)
2286 {
2287 // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
2288 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2289 logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2290
2291 // Convert to parametric space, apply delta, convert back
2292 float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2293 float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2294 v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2295 v_old_ref_for_accum_remainder = v_old_parametric;
2296 }
2297 else
2298 {
2299 v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2300 }
2301
2302 // Round to user desired precision based on format string
2303 if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
2304 v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
2305
2306 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2307 g.DragCurrentAccumDirty = false;
2308 if (is_logarithmic)
2309 {
2310 // Convert to parametric space, apply delta, convert back
2311 float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2312 g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2313 }
2314 else
2315 {
2316 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2317 }
2318
2319 // Lose zero sign for float/double
2320 if (v_cur == (TYPE)-0)
2321 v_cur = (TYPE)0;
2322
2323 // Clamp values (+ handle overflow/wrap-around for integer types)
2324 if (*v != v_cur && is_clamped)
2325 {
2326 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2327 v_cur = v_min;
2328 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2329 v_cur = v_max;
2330 }
2331
2332 // Apply result
2333 if (*v == v_cur)
2334 return false;
2335 *v = v_cur;
2336 return true;
2337 }
2338
DragBehavior(ImGuiID id,ImGuiDataType data_type,void * p_v,float v_speed,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2339 bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2340 {
2341 // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2342 IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
2343
2344 ImGuiContext& g = *GImGui;
2345 if (g.ActiveId == id)
2346 {
2347 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2348 ClearActiveID();
2349 else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2350 ClearActiveID();
2351 }
2352 if (g.ActiveId != id)
2353 return false;
2354 if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2355 return false;
2356
2357 switch (data_type)
2358 {
2359 case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN, p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
2360 case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN, p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
2361 case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
2362 case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
2363 case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)p_v, v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags);
2364 case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)p_v, v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags);
2365 case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)p_v, v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags);
2366 case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)p_v, v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags);
2367 case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, (float*)p_v, v_speed, p_min ? *(const float* )p_min : -FLT_MAX, p_max ? *(const float* )p_max : FLT_MAX, format, flags);
2368 case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX, p_max ? *(const double*)p_max : DBL_MAX, format, flags);
2369 case ImGuiDataType_COUNT: break;
2370 }
2371 IM_ASSERT(0);
2372 return false;
2373 }
2374
2375 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.
2376 // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
DragScalar(const char * label,ImGuiDataType data_type,void * p_data,float v_speed,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2377 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2378 {
2379 ImGuiWindow* window = GetCurrentWindow();
2380 if (window->SkipItems)
2381 return false;
2382
2383 ImGuiContext& g = *GImGui;
2384 const ImGuiStyle& style = g.Style;
2385 const ImGuiID id = window->GetID(label);
2386 const float w = CalcItemWidth();
2387
2388 const ImVec2 label_size = CalcTextSize(label, NULL, true);
2389 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2390 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));
2391
2392 const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2393 ItemSize(total_bb, style.FramePadding.y);
2394 if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0))
2395 return false;
2396
2397 // Default format string when passing NULL
2398 if (format == NULL)
2399 format = DataTypeGetInfo(data_type)->PrintFmt;
2400 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
2401 format = PatchFormatStringFloatToInt(format);
2402
2403 // Tabbing or CTRL-clicking on Drag turns it into an InputText
2404 const bool hovered = ItemHoverable(frame_bb, id);
2405 bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2406 if (!temp_input_is_active)
2407 {
2408 const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0;
2409 const bool clicked = (hovered && g.IO.MouseClicked[0]);
2410 const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]);
2411 if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id)
2412 {
2413 SetActiveID(id, window);
2414 SetFocusID(id, window);
2415 FocusWindow(window);
2416 g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2417 if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id))
2418 temp_input_is_active = true;
2419 }
2420 // Experimental: simple click (without moving) turns Drag into an InputText
2421 // FIXME: Currently polling ImGuiConfigFlags_IsTouchScreen, may either poll an hypothetical ImGuiBackendFlags_HasKeyboard and/or an explicit drag settings.
2422 if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2423 if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2424 {
2425 g.NavInputId = id;
2426 temp_input_is_active = true;
2427 }
2428 }
2429
2430 if (temp_input_is_active)
2431 {
2432 // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
2433 const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0);
2434 return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
2435 }
2436
2437 // Draw frame
2438 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2439 RenderNavHighlight(frame_bb, id);
2440 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
2441
2442 // Drag behavior
2443 const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
2444 if (value_changed)
2445 MarkItemEdited(id);
2446
2447 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2448 char value_buf[64];
2449 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
2450 if (g.LogEnabled)
2451 LogSetNextTextDecoration("{", "}");
2452 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
2453
2454 if (label_size.x > 0.0f)
2455 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
2456
2457 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
2458 return value_changed;
2459 }
2460
DragScalarN(const char * label,ImGuiDataType data_type,void * p_data,int components,float v_speed,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2461 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2462 {
2463 ImGuiWindow* window = GetCurrentWindow();
2464 if (window->SkipItems)
2465 return false;
2466
2467 ImGuiContext& g = *GImGui;
2468 bool value_changed = false;
2469 BeginGroup();
2470 PushID(label);
2471 PushMultiItemsWidths(components, CalcItemWidth());
2472 size_t type_size = GDataTypeInfo[data_type].Size;
2473 for (int i = 0; i < components; i++)
2474 {
2475 PushID(i);
2476 if (i > 0)
2477 SameLine(0, g.Style.ItemInnerSpacing.x);
2478 value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
2479 PopID();
2480 PopItemWidth();
2481 p_data = (void*)((char*)p_data + type_size);
2482 }
2483 PopID();
2484
2485 const char* label_end = FindRenderedTextEnd(label);
2486 if (label != label_end)
2487 {
2488 SameLine(0, g.Style.ItemInnerSpacing.x);
2489 TextEx(label, label_end);
2490 }
2491
2492 EndGroup();
2493 return value_changed;
2494 }
2495
DragFloat(const char * label,float * v,float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2496 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2497 {
2498 return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
2499 }
2500
DragFloat2(const char * label,float v[2],float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2501 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2502 {
2503 return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
2504 }
2505
DragFloat3(const char * label,float v[3],float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2506 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2507 {
2508 return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
2509 }
2510
DragFloat4(const char * label,float v[4],float v_speed,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)2511 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2512 {
2513 return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
2514 }
2515
2516 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
DragFloatRange2(const char * label,float * v_current_min,float * v_current_max,float v_speed,float v_min,float v_max,const char * format,const char * format_max,ImGuiSliderFlags flags)2517 bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags)
2518 {
2519 ImGuiWindow* window = GetCurrentWindow();
2520 if (window->SkipItems)
2521 return false;
2522
2523 ImGuiContext& g = *GImGui;
2524 PushID(label);
2525 BeginGroup();
2526 PushMultiItemsWidths(2, CalcItemWidth());
2527
2528 float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2529 float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2530 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2531 bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
2532 PopItemWidth();
2533 SameLine(0, g.Style.ItemInnerSpacing.x);
2534
2535 float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2536 float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2537 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2538 value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
2539 PopItemWidth();
2540 SameLine(0, g.Style.ItemInnerSpacing.x);
2541
2542 TextEx(label, FindRenderedTextEnd(label));
2543 EndGroup();
2544 PopID();
2545
2546 return value_changed;
2547 }
2548
2549 // NB: v_speed is float to allow adjusting the drag speed with more precision
DragInt(const char * label,int * v,float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2550 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2551 {
2552 return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
2553 }
2554
DragInt2(const char * label,int v[2],float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2555 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2556 {
2557 return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
2558 }
2559
DragInt3(const char * label,int v[3],float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2560 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2561 {
2562 return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
2563 }
2564
DragInt4(const char * label,int v[4],float v_speed,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)2565 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2566 {
2567 return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
2568 }
2569
2570 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
DragIntRange2(const char * label,int * v_current_min,int * v_current_max,float v_speed,int v_min,int v_max,const char * format,const char * format_max,ImGuiSliderFlags flags)2571 bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags)
2572 {
2573 ImGuiWindow* window = GetCurrentWindow();
2574 if (window->SkipItems)
2575 return false;
2576
2577 ImGuiContext& g = *GImGui;
2578 PushID(label);
2579 BeginGroup();
2580 PushMultiItemsWidths(2, CalcItemWidth());
2581
2582 int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2583 int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2584 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2585 bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
2586 PopItemWidth();
2587 SameLine(0, g.Style.ItemInnerSpacing.x);
2588
2589 int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2590 int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2591 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2592 value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
2593 PopItemWidth();
2594 SameLine(0, g.Style.ItemInnerSpacing.x);
2595
2596 TextEx(label, FindRenderedTextEnd(label));
2597 EndGroup();
2598 PopID();
2599
2600 return value_changed;
2601 }
2602
2603 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2604
2605 // Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
DragScalar(const char * label,ImGuiDataType data_type,void * p_data,float v_speed,const void * p_min,const void * p_max,const char * format,float power)2606 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
2607 {
2608 ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
2609 if (power != 1.0f)
2610 {
2611 IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
2612 IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds
2613 drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
2614 }
2615 return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags);
2616 }
2617
DragScalarN(const char * label,ImGuiDataType data_type,void * p_data,int components,float v_speed,const void * p_min,const void * p_max,const char * format,float power)2618 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
2619 {
2620 ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
2621 if (power != 1.0f)
2622 {
2623 IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
2624 IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds
2625 drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
2626 }
2627 return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags);
2628 }
2629
2630 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2631
2632 //-------------------------------------------------------------------------
2633 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2634 //-------------------------------------------------------------------------
2635 // - ScaleRatioFromValueT<> [Internal]
2636 // - ScaleValueFromRatioT<> [Internal]
2637 // - SliderBehaviorT<>() [Internal]
2638 // - SliderBehavior() [Internal]
2639 // - SliderScalar()
2640 // - SliderScalarN()
2641 // - SliderFloat()
2642 // - SliderFloat2()
2643 // - SliderFloat3()
2644 // - SliderFloat4()
2645 // - SliderAngle()
2646 // - SliderInt()
2647 // - SliderInt2()
2648 // - SliderInt3()
2649 // - SliderInt4()
2650 // - VSliderScalar()
2651 // - VSliderFloat()
2652 // - VSliderInt()
2653 //-------------------------------------------------------------------------
2654
2655 // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2656 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
ScaleRatioFromValueT(ImGuiDataType data_type,TYPE v,TYPE v_min,TYPE v_max,bool is_logarithmic,float logarithmic_zero_epsilon,float zero_deadzone_halfsize)2657 float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2658 {
2659 if (v_min == v_max)
2660 return 0.0f;
2661 IM_UNUSED(data_type);
2662
2663 const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2664 if (is_logarithmic)
2665 {
2666 bool flipped = v_max < v_min;
2667
2668 if (flipped) // Handle the case where the range is backwards
2669 ImSwap(v_min, v_max);
2670
2671 // Fudge min/max to avoid getting close to log(0)
2672 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2673 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2674
2675 // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2676 if ((v_min == 0.0f) && (v_max < 0.0f))
2677 v_min_fudged = -logarithmic_zero_epsilon;
2678 else if ((v_max == 0.0f) && (v_min < 0.0f))
2679 v_max_fudged = -logarithmic_zero_epsilon;
2680
2681 float result;
2682
2683 if (v_clamped <= v_min_fudged)
2684 result = 0.0f; // Workaround for values that are in-range but below our fudge
2685 else if (v_clamped >= v_max_fudged)
2686 result = 1.0f; // Workaround for values that are in-range but above our fudge
2687 else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
2688 {
2689 float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine)
2690 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2691 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2692 if (v == 0.0f)
2693 result = zero_point_center; // Special case for exactly zero
2694 else if (v < 0.0f)
2695 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
2696 else
2697 result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R));
2698 }
2699 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2700 result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
2701 else
2702 result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
2703
2704 return flipped ? (1.0f - result) : result;
2705 }
2706
2707 // Linear slider
2708 return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
2709 }
2710
2711 // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
2712 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2713 TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2714 {
2715 if (v_min == v_max)
2716 return v_min;
2717 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2718
2719 TYPE result;
2720 if (is_logarithmic)
2721 {
2722 // We special-case the extents because otherwise our fudging can lead to "mathematically correct" but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value
2723 if (t <= 0.0f)
2724 result = v_min;
2725 else if (t >= 1.0f)
2726 result = v_max;
2727 else
2728 {
2729 bool flipped = v_max < v_min; // Check if range is "backwards"
2730
2731 // Fudge min/max to avoid getting silly results close to zero
2732 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2733 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2734
2735 if (flipped)
2736 ImSwap(v_min_fudged, v_max_fudged);
2737
2738 // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2739 if ((v_max == 0.0f) && (v_min < 0.0f))
2740 v_max_fudged = -logarithmic_zero_epsilon;
2741
2742 float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
2743
2744 if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
2745 {
2746 float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
2747 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2748 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2749 if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
2750 result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
2751 else if (t_with_flip < zero_point_center)
2752 result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
2753 else
2754 result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
2755 }
2756 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2757 result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
2758 else
2759 result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
2760 }
2761 }
2762 else
2763 {
2764 // Linear slider
2765 if (is_floating_point)
2766 {
2767 result = ImLerp(v_min, v_max, t);
2768 }
2769 else
2770 {
2771 // - For integer values we want the clicking position to match the grab box so we round above
2772 // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2773 // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64
2774 // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
2775 if (t < 1.0)
2776 {
2777 FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
2778 result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
2779 }
2780 else
2781 {
2782 result = v_max;
2783 }
2784 }
2785 }
2786
2787 return result;
2788 }
2789
2790 // FIXME: Move more of the code into SliderBehavior()
2791 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
SliderBehaviorT(const ImRect & bb,ImGuiID id,ImGuiDataType data_type,TYPE * v,const TYPE v_min,const TYPE v_max,const char * format,ImGuiSliderFlags flags,ImRect * out_grab_bb)2792 bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2793 {
2794 ImGuiContext& g = *GImGui;
2795 const ImGuiStyle& style = g.Style;
2796
2797 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2798 const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2799 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2800
2801 const float grab_padding = 2.0f;
2802 const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
2803 float grab_sz = style.GrabMinSize;
2804 SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
2805 if (!is_floating_point && v_range >= 0) // v_range < 0 may happen on integer overflows
2806 grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
2807 grab_sz = ImMin(grab_sz, slider_sz);
2808 const float slider_usable_sz = slider_sz - grab_sz;
2809 const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
2810 const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
2811
2812 float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2813 float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
2814 if (is_logarithmic)
2815 {
2816 // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
2817 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2818 logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2819 zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
2820 }
2821
2822 // Process interacting with the slider
2823 bool value_changed = false;
2824 if (g.ActiveId == id)
2825 {
2826 bool set_new_value = false;
2827 float clicked_t = 0.0f;
2828 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
2829 {
2830 if (!g.IO.MouseDown[0])
2831 {
2832 ClearActiveID();
2833 }
2834 else
2835 {
2836 const float mouse_abs_pos = g.IO.MousePos[axis];
2837 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
2838 if (axis == ImGuiAxis_Y)
2839 clicked_t = 1.0f - clicked_t;
2840 set_new_value = true;
2841 }
2842 }
2843 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2844 {
2845 if (g.ActiveIdIsJustActivated)
2846 {
2847 g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
2848 g.SliderCurrentAccumDirty = false;
2849 }
2850
2851 const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
2852 float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y;
2853 if (input_delta != 0.0f)
2854 {
2855 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2856 if (decimal_precision > 0)
2857 {
2858 input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
2859 if (IsNavInputDown(ImGuiNavInput_TweakSlow))
2860 input_delta /= 10.0f;
2861 }
2862 else
2863 {
2864 if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
2865 input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
2866 else
2867 input_delta /= 100.0f;
2868 }
2869 if (IsNavInputDown(ImGuiNavInput_TweakFast))
2870 input_delta *= 10.0f;
2871
2872 g.SliderCurrentAccum += input_delta;
2873 g.SliderCurrentAccumDirty = true;
2874 }
2875
2876 float delta = g.SliderCurrentAccum;
2877 if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2878 {
2879 ClearActiveID();
2880 }
2881 else if (g.SliderCurrentAccumDirty)
2882 {
2883 clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2884
2885 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
2886 {
2887 set_new_value = false;
2888 g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
2889 }
2890 else
2891 {
2892 set_new_value = true;
2893 float old_clicked_t = clicked_t;
2894 clicked_t = ImSaturate(clicked_t + delta);
2895
2896 // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
2897 TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2898 if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
2899 v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
2900 float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2901
2902 if (delta > 0)
2903 g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
2904 else
2905 g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
2906 }
2907
2908 g.SliderCurrentAccumDirty = false;
2909 }
2910 }
2911
2912 if (set_new_value)
2913 {
2914 TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2915
2916 // Round to user desired precision based on format string
2917 if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
2918 v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
2919
2920 // Apply result
2921 if (*v != v_new)
2922 {
2923 *v = v_new;
2924 value_changed = true;
2925 }
2926 }
2927 }
2928
2929 if (slider_sz < 1.0f)
2930 {
2931 *out_grab_bb = ImRect(bb.Min, bb.Min);
2932 }
2933 else
2934 {
2935 // Output grab position so it can be displayed by the caller
2936 float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2937 if (axis == ImGuiAxis_Y)
2938 grab_t = 1.0f - grab_t;
2939 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
2940 if (axis == ImGuiAxis_X)
2941 *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);
2942 else
2943 *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);
2944 }
2945
2946 return value_changed;
2947 }
2948
2949 // For 32-bit and larger types, slider bounds are limited to half the natural type range.
2950 // 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.
2951 // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
SliderBehavior(const ImRect & bb,ImGuiID id,ImGuiDataType data_type,void * p_v,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags,ImRect * out_grab_bb)2952 bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2953 {
2954 // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2955 IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
2956
2957 ImGuiContext& g = *GImGui;
2958 if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2959 return false;
2960
2961 switch (data_type)
2962 {
2963 case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
2964 case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min, *(const ImU8*)p_max, format, flags, out_grab_bb); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
2965 case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
2966 case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
2967 case ImGuiDataType_S32:
2968 IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
2969 return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)p_v, *(const ImS32*)p_min, *(const ImS32*)p_max, format, flags, out_grab_bb);
2970 case ImGuiDataType_U32:
2971 IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
2972 return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)p_v, *(const ImU32*)p_min, *(const ImU32*)p_max, format, flags, out_grab_bb);
2973 case ImGuiDataType_S64:
2974 IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
2975 return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)p_v, *(const ImS64*)p_min, *(const ImS64*)p_max, format, flags, out_grab_bb);
2976 case ImGuiDataType_U64:
2977 IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
2978 return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)p_v, *(const ImU64*)p_min, *(const ImU64*)p_max, format, flags, out_grab_bb);
2979 case ImGuiDataType_Float:
2980 IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
2981 return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)p_v, *(const float*)p_min, *(const float*)p_max, format, flags, out_grab_bb);
2982 case ImGuiDataType_Double:
2983 IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
2984 return SliderBehaviorT<double, double, double>(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb);
2985 case ImGuiDataType_COUNT: break;
2986 }
2987 IM_ASSERT(0);
2988 return false;
2989 }
2990
2991 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
2992 // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
SliderScalar(const char * label,ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)2993 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2994 {
2995 ImGuiWindow* window = GetCurrentWindow();
2996 if (window->SkipItems)
2997 return false;
2998
2999 ImGuiContext& g = *GImGui;
3000 const ImGuiStyle& style = g.Style;
3001 const ImGuiID id = window->GetID(label);
3002 const float w = CalcItemWidth();
3003
3004 const ImVec2 label_size = CalcTextSize(label, NULL, true);
3005 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
3006 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));
3007
3008 const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
3009 ItemSize(total_bb, style.FramePadding.y);
3010 if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0))
3011 return false;
3012
3013 // Default format string when passing NULL
3014 if (format == NULL)
3015 format = DataTypeGetInfo(data_type)->PrintFmt;
3016 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
3017 format = PatchFormatStringFloatToInt(format);
3018
3019 // Tabbing or CTRL-clicking on Slider turns it into an input box
3020 const bool hovered = ItemHoverable(frame_bb, id);
3021 bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
3022 if (!temp_input_is_active)
3023 {
3024 const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0;
3025 const bool clicked = (hovered && g.IO.MouseClicked[0]);
3026 if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id)
3027 {
3028 SetActiveID(id, window);
3029 SetFocusID(id, window);
3030 FocusWindow(window);
3031 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
3032 if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id))
3033 temp_input_is_active = true;
3034 }
3035 }
3036
3037 if (temp_input_is_active)
3038 {
3039 // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
3040 const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
3041 return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
3042 }
3043
3044 // Draw frame
3045 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3046 RenderNavHighlight(frame_bb, id);
3047 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3048
3049 // Slider behavior
3050 ImRect grab_bb;
3051 const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
3052 if (value_changed)
3053 MarkItemEdited(id);
3054
3055 // Render grab
3056 if (grab_bb.Max.x > grab_bb.Min.x)
3057 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3058
3059 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3060 char value_buf[64];
3061 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3062 if (g.LogEnabled)
3063 LogSetNextTextDecoration("{", "}");
3064 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
3065
3066 if (label_size.x > 0.0f)
3067 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3068
3069 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
3070 return value_changed;
3071 }
3072
3073 // Add multiple sliders on 1 line for compact edition of multiple components
SliderScalarN(const char * label,ImGuiDataType data_type,void * v,int components,const void * v_min,const void * v_max,const char * format,ImGuiSliderFlags flags)3074 bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags)
3075 {
3076 ImGuiWindow* window = GetCurrentWindow();
3077 if (window->SkipItems)
3078 return false;
3079
3080 ImGuiContext& g = *GImGui;
3081 bool value_changed = false;
3082 BeginGroup();
3083 PushID(label);
3084 PushMultiItemsWidths(components, CalcItemWidth());
3085 size_t type_size = GDataTypeInfo[data_type].Size;
3086 for (int i = 0; i < components; i++)
3087 {
3088 PushID(i);
3089 if (i > 0)
3090 SameLine(0, g.Style.ItemInnerSpacing.x);
3091 value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
3092 PopID();
3093 PopItemWidth();
3094 v = (void*)((char*)v + type_size);
3095 }
3096 PopID();
3097
3098 const char* label_end = FindRenderedTextEnd(label);
3099 if (label != label_end)
3100 {
3101 SameLine(0, g.Style.ItemInnerSpacing.x);
3102 TextEx(label, label_end);
3103 }
3104
3105 EndGroup();
3106 return value_changed;
3107 }
3108
SliderFloat(const char * label,float * v,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3109 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3110 {
3111 return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3112 }
3113
SliderFloat2(const char * label,float v[2],float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3114 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3115 {
3116 return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
3117 }
3118
SliderFloat3(const char * label,float v[3],float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3119 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3120 {
3121 return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
3122 }
3123
SliderFloat4(const char * label,float v[4],float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3124 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3125 {
3126 return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
3127 }
3128
SliderAngle(const char * label,float * v_rad,float v_degrees_min,float v_degrees_max,const char * format,ImGuiSliderFlags flags)3129 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
3130 {
3131 if (format == NULL)
3132 format = "%.0f deg";
3133 float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
3134 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
3135 *v_rad = v_deg * (2 * IM_PI) / 360.0f;
3136 return value_changed;
3137 }
3138
SliderInt(const char * label,int * v,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3139 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3140 {
3141 return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3142 }
3143
SliderInt2(const char * label,int v[2],int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3144 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3145 {
3146 return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
3147 }
3148
SliderInt3(const char * label,int v[3],int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3149 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3150 {
3151 return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
3152 }
3153
SliderInt4(const char * label,int v[4],int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3154 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3155 {
3156 return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
3157 }
3158
VSliderScalar(const char * label,const ImVec2 & size,ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max,const char * format,ImGuiSliderFlags flags)3159 bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
3160 {
3161 ImGuiWindow* window = GetCurrentWindow();
3162 if (window->SkipItems)
3163 return false;
3164
3165 ImGuiContext& g = *GImGui;
3166 const ImGuiStyle& style = g.Style;
3167 const ImGuiID id = window->GetID(label);
3168
3169 const ImVec2 label_size = CalcTextSize(label, NULL, true);
3170 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3171 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));
3172
3173 ItemSize(bb, style.FramePadding.y);
3174 if (!ItemAdd(frame_bb, id))
3175 return false;
3176
3177 // Default format string when passing NULL
3178 if (format == NULL)
3179 format = DataTypeGetInfo(data_type)->PrintFmt;
3180 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
3181 format = PatchFormatStringFloatToInt(format);
3182
3183 const bool hovered = ItemHoverable(frame_bb, id);
3184 if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
3185 {
3186 SetActiveID(id, window);
3187 SetFocusID(id, window);
3188 FocusWindow(window);
3189 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
3190 }
3191
3192 // Draw frame
3193 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3194 RenderNavHighlight(frame_bb, id);
3195 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3196
3197 // Slider behavior
3198 ImRect grab_bb;
3199 const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
3200 if (value_changed)
3201 MarkItemEdited(id);
3202
3203 // Render grab
3204 if (grab_bb.Max.y > grab_bb.Min.y)
3205 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3206
3207 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3208 // For the vertical slider we allow centered text to overlap the frame padding
3209 char value_buf[64];
3210 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3211 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));
3212 if (label_size.x > 0.0f)
3213 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3214
3215 return value_changed;
3216 }
3217
VSliderFloat(const char * label,const ImVec2 & size,float * v,float v_min,float v_max,const char * format,ImGuiSliderFlags flags)3218 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3219 {
3220 return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3221 }
3222
VSliderInt(const char * label,const ImVec2 & size,int * v,int v_min,int v_max,const char * format,ImGuiSliderFlags flags)3223 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3224 {
3225 return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3226 }
3227
3228 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
3229
3230 // Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
SliderScalar(const char * label,ImGuiDataType data_type,void * p_data,const void * p_min,const void * p_max,const char * format,float power)3231 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power)
3232 {
3233 ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
3234 if (power != 1.0f)
3235 {
3236 IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
3237 slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
3238 }
3239 return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags);
3240 }
3241
SliderScalarN(const char * label,ImGuiDataType data_type,void * v,int components,const void * v_min,const void * v_max,const char * format,float power)3242 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)
3243 {
3244 ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
3245 if (power != 1.0f)
3246 {
3247 IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
3248 slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
3249 }
3250 return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags);
3251 }
3252
3253 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
3254
3255 //-------------------------------------------------------------------------
3256 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
3257 //-------------------------------------------------------------------------
3258 // - ImParseFormatFindStart() [Internal]
3259 // - ImParseFormatFindEnd() [Internal]
3260 // - ImParseFormatTrimDecorations() [Internal]
3261 // - ImParseFormatPrecision() [Internal]
3262 // - TempInputTextScalar() [Internal]
3263 // - InputScalar()
3264 // - InputScalarN()
3265 // - InputFloat()
3266 // - InputFloat2()
3267 // - InputFloat3()
3268 // - InputFloat4()
3269 // - InputInt()
3270 // - InputInt2()
3271 // - InputInt3()
3272 // - InputInt4()
3273 // - InputDouble()
3274 //-------------------------------------------------------------------------
3275
3276 // We don't use strchr() because our strings are usually very short and often start with '%'
ImParseFormatFindStart(const char * fmt)3277 const char* ImParseFormatFindStart(const char* fmt)
3278 {
3279 while (char c = fmt[0])
3280 {
3281 if (c == '%' && fmt[1] != '%')
3282 return fmt;
3283 else if (c == '%')
3284 fmt++;
3285 fmt++;
3286 }
3287 return fmt;
3288 }
3289
ImParseFormatFindEnd(const char * fmt)3290 const char* ImParseFormatFindEnd(const char* fmt)
3291 {
3292 // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
3293 if (fmt[0] != '%')
3294 return fmt;
3295 const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
3296 const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
3297 for (char c; (c = *fmt) != 0; fmt++)
3298 {
3299 if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
3300 return fmt + 1;
3301 if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
3302 return fmt + 1;
3303 }
3304 return fmt;
3305 }
3306
3307 // Extract the format out of a format string with leading or trailing decorations
3308 // fmt = "blah blah" -> return fmt
3309 // fmt = "%.3f" -> return fmt
3310 // fmt = "hello %.3f" -> return fmt + 6
3311 // fmt = "%.3f hello" -> return buf written with "%.3f"
ImParseFormatTrimDecorations(const char * fmt,char * buf,size_t buf_size)3312 const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
3313 {
3314 const char* fmt_start = ImParseFormatFindStart(fmt);
3315 if (fmt_start[0] != '%')
3316 return fmt;
3317 const char* fmt_end = ImParseFormatFindEnd(fmt_start);
3318 if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
3319 return fmt_start;
3320 ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
3321 return buf;
3322 }
3323
3324 // Parse display precision back from the display format string
3325 // 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)3326 int ImParseFormatPrecision(const char* fmt, int default_precision)
3327 {
3328 fmt = ImParseFormatFindStart(fmt);
3329 if (fmt[0] != '%')
3330 return default_precision;
3331 fmt++;
3332 while (*fmt >= '0' && *fmt <= '9')
3333 fmt++;
3334 int precision = INT_MAX;
3335 if (*fmt == '.')
3336 {
3337 fmt = ImAtoi<int>(fmt + 1, &precision);
3338 if (precision < 0 || precision > 99)
3339 precision = default_precision;
3340 }
3341 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
3342 precision = -1;
3343 if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
3344 precision = -1;
3345 return (precision == INT_MAX) ? default_precision : precision;
3346 }
3347
3348 // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
3349 // FIXME: Facilitate using this in variety of other situations.
TempInputText(const ImRect & bb,ImGuiID id,const char * label,char * buf,int buf_size,ImGuiInputTextFlags flags)3350 bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
3351 {
3352 // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
3353 // We clear ActiveID on the first frame to allow the InputText() taking it back.
3354 ImGuiContext& g = *GImGui;
3355 const bool init = (g.TempInputId != id);
3356 if (init)
3357 ClearActiveID();
3358
3359 g.CurrentWindow->DC.CursorPos = bb.Min;
3360 bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
3361 if (init)
3362 {
3363 // First frame we started displaying the InputText widget, we expect it to take the active id.
3364 IM_ASSERT(g.ActiveId == id);
3365 g.TempInputId = g.ActiveId;
3366 }
3367 return value_changed;
3368 }
3369
3370 // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
3371 // This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
3372 // However this may not be ideal for all uses, as some user code may break on out of bound values.
TempInputScalar(const ImRect & bb,ImGuiID id,const char * label,ImGuiDataType data_type,void * p_data,const char * format,const void * p_clamp_min,const void * p_clamp_max)3373 bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)
3374 {
3375 ImGuiContext& g = *GImGui;
3376
3377 char fmt_buf[32];
3378 char data_buf[32];
3379 format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
3380 DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
3381 ImStrTrimBlanks(data_buf);
3382
3383 ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
3384 flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
3385 bool value_changed = false;
3386 if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
3387 {
3388 // Backup old value
3389 size_t data_type_size = DataTypeGetInfo(data_type)->Size;
3390 ImGuiDataTypeTempStorage data_backup;
3391 memcpy(&data_backup, p_data, data_type_size);
3392
3393 // Apply new value (or operations) then clamp
3394 DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL);
3395 if (p_clamp_min || p_clamp_max)
3396 {
3397 if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
3398 ImSwap(p_clamp_min, p_clamp_max);
3399 DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
3400 }
3401
3402 // Only mark as edited if new value is different
3403 value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
3404 if (value_changed)
3405 MarkItemEdited(id);
3406 }
3407 return value_changed;
3408 }
3409
3410 // Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional.
3411 // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
InputScalar(const char * label,ImGuiDataType data_type,void * p_data,const void * p_step,const void * p_step_fast,const char * format,ImGuiInputTextFlags flags)3412 bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
3413 {
3414 ImGuiWindow* window = GetCurrentWindow();
3415 if (window->SkipItems)
3416 return false;
3417
3418 ImGuiContext& g = *GImGui;
3419 ImGuiStyle& style = g.Style;
3420
3421 if (format == NULL)
3422 format = DataTypeGetInfo(data_type)->PrintFmt;
3423
3424 char buf[64];
3425 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
3426
3427 bool value_changed = false;
3428 if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
3429 flags |= ImGuiInputTextFlags_CharsDecimal;
3430 flags |= ImGuiInputTextFlags_AutoSelectAll;
3431 flags |= ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
3432
3433 if (p_step != NULL)
3434 {
3435 const float button_size = GetFrameHeight();
3436
3437 BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
3438 PushID(label);
3439 SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
3440 if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
3441 value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
3442
3443 // Step buttons
3444 const ImVec2 backup_frame_padding = style.FramePadding;
3445 style.FramePadding.x = style.FramePadding.y;
3446 ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
3447 if (flags & ImGuiInputTextFlags_ReadOnly)
3448 PushDisabled(true);
3449 SameLine(0, style.ItemInnerSpacing.x);
3450 if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
3451 {
3452 DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3453 value_changed = true;
3454 }
3455 SameLine(0, style.ItemInnerSpacing.x);
3456 if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
3457 {
3458 DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3459 value_changed = true;
3460 }
3461 if (flags & ImGuiInputTextFlags_ReadOnly)
3462 PopDisabled();
3463
3464 const char* label_end = FindRenderedTextEnd(label);
3465 if (label != label_end)
3466 {
3467 SameLine(0, style.ItemInnerSpacing.x);
3468 TextEx(label, label_end);
3469 }
3470 style.FramePadding = backup_frame_padding;
3471
3472 PopID();
3473 EndGroup();
3474 }
3475 else
3476 {
3477 if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
3478 value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
3479 }
3480 if (value_changed)
3481 MarkItemEdited(g.LastItemData.ID);
3482
3483 return value_changed;
3484 }
3485
InputScalarN(const char * label,ImGuiDataType data_type,void * p_data,int components,const void * p_step,const void * p_step_fast,const char * format,ImGuiInputTextFlags flags)3486 bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
3487 {
3488 ImGuiWindow* window = GetCurrentWindow();
3489 if (window->SkipItems)
3490 return false;
3491
3492 ImGuiContext& g = *GImGui;
3493 bool value_changed = false;
3494 BeginGroup();
3495 PushID(label);
3496 PushMultiItemsWidths(components, CalcItemWidth());
3497 size_t type_size = GDataTypeInfo[data_type].Size;
3498 for (int i = 0; i < components; i++)
3499 {
3500 PushID(i);
3501 if (i > 0)
3502 SameLine(0, g.Style.ItemInnerSpacing.x);
3503 value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
3504 PopID();
3505 PopItemWidth();
3506 p_data = (void*)((char*)p_data + type_size);
3507 }
3508 PopID();
3509
3510 const char* label_end = FindRenderedTextEnd(label);
3511 if (label != label_end)
3512 {
3513 SameLine(0.0f, g.Style.ItemInnerSpacing.x);
3514 TextEx(label, label_end);
3515 }
3516
3517 EndGroup();
3518 return value_changed;
3519 }
3520
InputFloat(const char * label,float * v,float step,float step_fast,const char * format,ImGuiInputTextFlags flags)3521 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
3522 {
3523 flags |= ImGuiInputTextFlags_CharsScientific;
3524 return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
3525 }
3526
InputFloat2(const char * label,float v[2],const char * format,ImGuiInputTextFlags flags)3527 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
3528 {
3529 return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
3530 }
3531
InputFloat3(const char * label,float v[3],const char * format,ImGuiInputTextFlags flags)3532 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
3533 {
3534 return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
3535 }
3536
InputFloat4(const char * label,float v[4],const char * format,ImGuiInputTextFlags flags)3537 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
3538 {
3539 return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
3540 }
3541
InputInt(const char * label,int * v,int step,int step_fast,ImGuiInputTextFlags flags)3542 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
3543 {
3544 // 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.
3545 const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
3546 return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
3547 }
3548
InputInt2(const char * label,int v[2],ImGuiInputTextFlags flags)3549 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
3550 {
3551 return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
3552 }
3553
InputInt3(const char * label,int v[3],ImGuiInputTextFlags flags)3554 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
3555 {
3556 return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
3557 }
3558
InputInt4(const char * label,int v[4],ImGuiInputTextFlags flags)3559 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
3560 {
3561 return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
3562 }
3563
InputDouble(const char * label,double * v,double step,double step_fast,const char * format,ImGuiInputTextFlags flags)3564 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
3565 {
3566 flags |= ImGuiInputTextFlags_CharsScientific;
3567 return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
3568 }
3569
3570 //-------------------------------------------------------------------------
3571 // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
3572 //-------------------------------------------------------------------------
3573 // - InputText()
3574 // - InputTextWithHint()
3575 // - InputTextMultiline()
3576 // - InputTextEx() [Internal]
3577 //-------------------------------------------------------------------------
3578
InputText(const char * label,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3579 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3580 {
3581 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
3582 return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3583 }
3584
InputTextMultiline(const char * label,char * buf,size_t buf_size,const ImVec2 & size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3585 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3586 {
3587 return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
3588 }
3589
InputTextWithHint(const char * label,const char * hint,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data)3590 bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3591 {
3592 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
3593 return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3594 }
3595
InputTextCalcTextLenAndLineCount(const char * text_begin,const char ** out_text_end)3596 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
3597 {
3598 int line_count = 0;
3599 const char* s = text_begin;
3600 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
3601 if (c == '\n')
3602 line_count++;
3603 s--;
3604 if (s[0] != '\n' && s[0] != '\r')
3605 line_count++;
3606 *out_text_end = s;
3607 return line_count;
3608 }
3609
InputTextCalcTextSizeW(const ImWchar * text_begin,const ImWchar * text_end,const ImWchar ** remaining,ImVec2 * out_offset,bool stop_on_new_line)3610 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
3611 {
3612 ImGuiContext& g = *GImGui;
3613 ImFont* font = g.Font;
3614 const float line_height = g.FontSize;
3615 const float scale = line_height / font->FontSize;
3616
3617 ImVec2 text_size = ImVec2(0, 0);
3618 float line_width = 0.0f;
3619
3620 const ImWchar* s = text_begin;
3621 while (s < text_end)
3622 {
3623 unsigned int c = (unsigned int)(*s++);
3624 if (c == '\n')
3625 {
3626 text_size.x = ImMax(text_size.x, line_width);
3627 text_size.y += line_height;
3628 line_width = 0.0f;
3629 if (stop_on_new_line)
3630 break;
3631 continue;
3632 }
3633 if (c == '\r')
3634 continue;
3635
3636 const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
3637 line_width += char_width;
3638 }
3639
3640 if (text_size.x < line_width)
3641 text_size.x = line_width;
3642
3643 if (out_offset)
3644 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
3645
3646 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
3647 text_size.y += line_height;
3648
3649 if (remaining)
3650 *remaining = s;
3651
3652 return text_size;
3653 }
3654
3655 // 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)
3656 namespace ImStb
3657 {
3658
STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState * obj)3659 static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; }
STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState * obj,int idx)3660 static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { return obj->TextW[idx]; }
STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState * obj,int line_start_idx,int char_idx)3661 static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); }
STB_TEXTEDIT_KEYTOTEXT(int key)3662 static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; }
3663 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
STB_TEXTEDIT_LAYOUTROW(StbTexteditRow * r,ImGuiInputTextState * obj,int line_start_idx)3664 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
3665 {
3666 const ImWchar* text = obj->TextW.Data;
3667 const ImWchar* text_remaining = NULL;
3668 const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
3669 r->x0 = 0.0f;
3670 r->x1 = size.x;
3671 r->baseline_y_delta = size.y;
3672 r->ymin = 0.0f;
3673 r->ymax = size.y;
3674 r->num_chars = (int)(text_remaining - (text + line_start_idx));
3675 }
3676
3677 // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
is_separator(unsigned int c)3678 static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
is_word_boundary_from_right(ImGuiInputTextState * obj,int idx)3679 static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; }
STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState * obj,int idx)3680 static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
3681 #ifdef __APPLE__ // FIXME: Move setting to IO structure
is_word_boundary_from_left(ImGuiInputTextState * obj,int idx)3682 static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; }
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState * obj,int idx)3683 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
3684 #else
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState * obj,int idx)3685 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
3686 #endif
3687 #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
3688 #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
3689
STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState * obj,int pos,int n)3690 static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
3691 {
3692 ImWchar* dst = obj->TextW.Data + pos;
3693
3694 // We maintain our buffer length in both UTF-8 and wchar formats
3695 obj->Edited = true;
3696 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
3697 obj->CurLenW -= n;
3698
3699 // Offset remaining text (FIXME-OPT: Use memmove)
3700 const ImWchar* src = obj->TextW.Data + pos + n;
3701 while (ImWchar c = *src++)
3702 *dst++ = c;
3703 *dst = '\0';
3704 }
3705
STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState * obj,int pos,const ImWchar * new_text,int new_text_len)3706 static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
3707 {
3708 const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3709 const int text_len = obj->CurLenW;
3710 IM_ASSERT(pos <= text_len);
3711
3712 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
3713 if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
3714 return false;
3715
3716 // Grow internal buffer if needed
3717 if (new_text_len + text_len + 1 > obj->TextW.Size)
3718 {
3719 if (!is_resizable)
3720 return false;
3721 IM_ASSERT(text_len < obj->TextW.Size);
3722 obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
3723 }
3724
3725 ImWchar* text = obj->TextW.Data;
3726 if (pos != text_len)
3727 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
3728 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
3729
3730 obj->Edited = true;
3731 obj->CurLenW += new_text_len;
3732 obj->CurLenA += new_text_len_utf8;
3733 obj->TextW[obj->CurLenW] = '\0';
3734
3735 return true;
3736 }
3737
3738 // 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)
3739 #define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left
3740 #define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right
3741 #define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up
3742 #define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down
3743 #define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line
3744 #define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line
3745 #define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text
3746 #define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text
3747 #define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor
3748 #define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor
3749 #define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo
3750 #define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo
3751 #define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word
3752 #define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word
3753 #define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page
3754 #define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page
3755 #define STB_TEXTEDIT_K_SHIFT 0x400000
3756
3757 #define STB_TEXTEDIT_IMPLEMENTATION
3758 #include "imstb_textedit.h"
3759
3760 // stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
3761 // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
stb_textedit_replace(ImGuiInputTextState * str,STB_TexteditState * state,const STB_TEXTEDIT_CHARTYPE * text,int text_len)3762 static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len)
3763 {
3764 stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
3765 ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
3766 if (text_len <= 0)
3767 return;
3768 if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
3769 {
3770 state->cursor = text_len;
3771 state->has_preferred_x = 0;
3772 return;
3773 }
3774 IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
3775 }
3776
3777 } // namespace ImStb
3778
OnKeyPressed(int key)3779 void ImGuiInputTextState::OnKeyPressed(int key)
3780 {
3781 stb_textedit_key(this, &Stb, key);
3782 CursorFollow = true;
3783 CursorAnimReset();
3784 }
3785
ImGuiInputTextCallbackData()3786 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
3787 {
3788 memset(this, 0, sizeof(*this));
3789 }
3790
3791 // Public API to manipulate UTF-8 text
3792 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
3793 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
DeleteChars(int pos,int bytes_count)3794 void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
3795 {
3796 IM_ASSERT(pos + bytes_count <= BufTextLen);
3797 char* dst = Buf + pos;
3798 const char* src = Buf + pos + bytes_count;
3799 while (char c = *src++)
3800 *dst++ = c;
3801 *dst = '\0';
3802
3803 if (CursorPos >= pos + bytes_count)
3804 CursorPos -= bytes_count;
3805 else if (CursorPos >= pos)
3806 CursorPos = pos;
3807 SelectionStart = SelectionEnd = CursorPos;
3808 BufDirty = true;
3809 BufTextLen -= bytes_count;
3810 }
3811
InsertChars(int pos,const char * new_text,const char * new_text_end)3812 void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
3813 {
3814 const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3815 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
3816 if (new_text_len + BufTextLen >= BufSize)
3817 {
3818 if (!is_resizable)
3819 return;
3820
3821 // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
3822 ImGuiContext& g = *GImGui;
3823 ImGuiInputTextState* edit_state = &g.InputTextState;
3824 IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
3825 IM_ASSERT(Buf == edit_state->TextA.Data);
3826 int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
3827 edit_state->TextA.reserve(new_buf_size + 1);
3828 Buf = edit_state->TextA.Data;
3829 BufSize = edit_state->BufCapacityA = new_buf_size;
3830 }
3831
3832 if (BufTextLen != pos)
3833 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
3834 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
3835 Buf[BufTextLen + new_text_len] = '\0';
3836
3837 if (CursorPos >= pos)
3838 CursorPos += new_text_len;
3839 SelectionStart = SelectionEnd = CursorPos;
3840 BufDirty = true;
3841 BufTextLen += new_text_len;
3842 }
3843
3844 // Return false to discard a character.
InputTextFilterCharacter(unsigned int * p_char,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * user_data,ImGuiInputSource input_source)3845 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source)
3846 {
3847 IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard);
3848 unsigned int c = *p_char;
3849
3850 // Filter non-printable (NB: isprint is unreliable! see #2467)
3851 if (c < 0x20)
3852 {
3853 bool pass = false;
3854 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
3855 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
3856 if (!pass)
3857 return false;
3858 }
3859
3860 if (input_source != ImGuiInputSource_Clipboard)
3861 {
3862 // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
3863 if (c == 127)
3864 return false;
3865
3866 // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
3867 if (c >= 0xE000 && c <= 0xF8FF)
3868 return false;
3869 }
3870
3871 // Filter Unicode ranges we are not handling in this build
3872 if (c > IM_UNICODE_CODEPOINT_MAX)
3873 return false;
3874
3875 // Generic named filters
3876 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
3877 {
3878 // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf.
3879 // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
3880 // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
3881 // Change the default decimal_point with:
3882 // ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
3883 ImGuiContext& g = *GImGui;
3884 const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint;
3885
3886 // Allow 0-9 . - + * /
3887 if (flags & ImGuiInputTextFlags_CharsDecimal)
3888 if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
3889 return false;
3890
3891 // Allow 0-9 . - + * / e E
3892 if (flags & ImGuiInputTextFlags_CharsScientific)
3893 if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
3894 return false;
3895
3896 // Allow 0-9 a-F A-F
3897 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
3898 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
3899 return false;
3900
3901 // Turn a-z into A-Z
3902 if (flags & ImGuiInputTextFlags_CharsUppercase)
3903 if (c >= 'a' && c <= 'z')
3904 *p_char = (c += (unsigned int)('A' - 'a'));
3905
3906 if (flags & ImGuiInputTextFlags_CharsNoBlank)
3907 if (ImCharIsBlankW(c))
3908 return false;
3909 }
3910
3911 // Custom callback filter
3912 if (flags & ImGuiInputTextFlags_CallbackCharFilter)
3913 {
3914 ImGuiInputTextCallbackData callback_data;
3915 memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
3916 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
3917 callback_data.EventChar = (ImWchar)c;
3918 callback_data.Flags = flags;
3919 callback_data.UserData = user_data;
3920 if (callback(&callback_data) != 0)
3921 return false;
3922 *p_char = callback_data.EventChar;
3923 if (!callback_data.EventChar)
3924 return false;
3925 }
3926
3927 return true;
3928 }
3929
3930 // Edit a string of text
3931 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
3932 // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
3933 // Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
3934 // - 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.
3935 // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
3936 // (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
3937 // doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
InputTextEx(const char * label,const char * hint,char * buf,int buf_size,const ImVec2 & size_arg,ImGuiInputTextFlags flags,ImGuiInputTextCallback callback,void * callback_user_data)3938 bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
3939 {
3940 ImGuiWindow* window = GetCurrentWindow();
3941 if (window->SkipItems)
3942 return false;
3943
3944 IM_ASSERT(buf != NULL && buf_size >= 0);
3945 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
3946 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
3947
3948 ImGuiContext& g = *GImGui;
3949 ImGuiIO& io = g.IO;
3950 const ImGuiStyle& style = g.Style;
3951
3952 const bool RENDER_SELECTION_WHEN_INACTIVE = false;
3953 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
3954 const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
3955 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
3956 const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
3957 const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
3958 if (is_resizable)
3959 IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
3960
3961 if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
3962 BeginGroup();
3963 const ImGuiID id = window->GetID(label);
3964 const ImVec2 label_size = CalcTextSize(label, NULL, true);
3965 const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line
3966 const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
3967
3968 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
3969 const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
3970
3971 ImGuiWindow* draw_window = window;
3972 ImVec2 inner_size = frame_size;
3973 if (is_multiline)
3974 {
3975 if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable))
3976 {
3977 ItemSize(total_bb, style.FramePadding.y);
3978 EndGroup();
3979 return false;
3980 }
3981
3982 // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
3983 PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
3984 PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
3985 PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
3986 bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove);
3987 PopStyleVar(2);
3988 PopStyleColor();
3989 if (!child_visible)
3990 {
3991 EndChild();
3992 EndGroup();
3993 return false;
3994 }
3995 draw_window = g.CurrentWindow; // Child window
3996 draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
3997 draw_window->DC.CursorPos += style.FramePadding;
3998 inner_size.x -= draw_window->ScrollbarSizes.x;
3999 }
4000 else
4001 {
4002 // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
4003 ItemSize(total_bb, style.FramePadding.y);
4004 if (!(flags & ImGuiInputTextFlags_MergedItem))
4005 if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable))
4006 return false;
4007 }
4008 const bool hovered = ItemHoverable(frame_bb, id);
4009 if (hovered)
4010 g.MouseCursor = ImGuiMouseCursor_TextInput;
4011
4012 // We are only allowed to access the state if we are already the active widget.
4013 ImGuiInputTextState* state = GetInputTextState(id);
4014
4015 const bool focus_requested_by_code = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByCode) != 0;
4016 const bool focus_requested_by_tabbing = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
4017
4018 const bool user_clicked = hovered && io.MouseClicked[0];
4019 const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard));
4020 const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4021 const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4022
4023 bool clear_active_id = false;
4024 bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
4025
4026 float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
4027
4028 const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline);
4029 const bool init_make_active = (user_clicked || user_scroll_finish || user_nav_input_start || focus_requested_by_code || focus_requested_by_tabbing);
4030 const bool init_state = (init_make_active || user_scroll_active);
4031 if ((init_state && g.ActiveId != id) || init_changed_specs)
4032 {
4033 // Access state even if we don't own it yet.
4034 state = &g.InputTextState;
4035 state->CursorAnimReset();
4036
4037 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
4038 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
4039 const int buf_len = (int)strlen(buf);
4040 state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
4041 memcpy(state->InitialTextA.Data, buf, buf_len + 1);
4042
4043 // Start edition
4044 const char* buf_end = NULL;
4045 state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
4046 state->TextA.resize(0);
4047 state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then)
4048 state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
4049 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.
4050
4051 // Preserve cursor position and undo/redo stack if we come back to same widget
4052 // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
4053 const bool recycle_state = (state->ID == id && !init_changed_specs);
4054 if (recycle_state)
4055 {
4056 // Recycle existing cursor/selection/undo stack but clamp position
4057 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
4058 state->CursorClamp();
4059 }
4060 else
4061 {
4062 state->ID = id;
4063 state->ScrollX = 0.0f;
4064 stb_textedit_initialize_state(&state->Stb, !is_multiline);
4065 if (!is_multiline && focus_requested_by_code)
4066 select_all = true;
4067 }
4068 if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
4069 state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863)
4070 if (!is_multiline && (focus_requested_by_tabbing || (user_clicked && io.KeyCtrl)))
4071 select_all = true;
4072 }
4073
4074 if (g.ActiveId != id && init_make_active)
4075 {
4076 IM_ASSERT(state && state->ID == id);
4077 SetActiveID(id, window);
4078 SetFocusID(id, window);
4079 FocusWindow(window);
4080
4081 // Declare our inputs
4082 IM_ASSERT(ImGuiNavInput_COUNT < 32);
4083 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
4084 if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
4085 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
4086 g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
4087 g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End);
4088 if (is_multiline)
4089 g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown);
4090 if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character.
4091 g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab);
4092 }
4093
4094 // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
4095 if (g.ActiveId == id && state == NULL)
4096 ClearActiveID();
4097
4098 // Release focus when we click outside
4099 if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
4100 clear_active_id = true;
4101
4102 // Lock the decision of whether we are going to take the path displaying the cursor or selection
4103 const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
4104 bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4105 bool value_changed = false;
4106 bool enter_pressed = false;
4107
4108 // When read-only we always use the live data passed to the function
4109 // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
4110 if (is_readonly && state != NULL && (render_cursor || render_selection))
4111 {
4112 const char* buf_end = NULL;
4113 state->TextW.resize(buf_size + 1);
4114 state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
4115 state->CurLenA = (int)(buf_end - buf);
4116 state->CursorClamp();
4117 render_selection &= state->HasSelection();
4118 }
4119
4120 // Select the buffer to render.
4121 const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
4122 const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
4123
4124 // Password pushes a temporary font with only a fallback glyph
4125 if (is_password && !is_displaying_hint)
4126 {
4127 const ImFontGlyph* glyph = g.Font->FindGlyph('*');
4128 ImFont* password_font = &g.InputTextPasswordFont;
4129 password_font->FontSize = g.Font->FontSize;
4130 password_font->Scale = g.Font->Scale;
4131 password_font->Ascent = g.Font->Ascent;
4132 password_font->Descent = g.Font->Descent;
4133 password_font->ContainerAtlas = g.Font->ContainerAtlas;
4134 password_font->FallbackGlyph = glyph;
4135 password_font->FallbackAdvanceX = glyph->AdvanceX;
4136 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
4137 PushFont(password_font);
4138 }
4139
4140 // Process mouse inputs and character inputs
4141 int backup_current_text_length = 0;
4142 if (g.ActiveId == id)
4143 {
4144 IM_ASSERT(state != NULL);
4145 backup_current_text_length = state->CurLenA;
4146 state->Edited = false;
4147 state->BufCapacityA = buf_size;
4148 state->Flags = flags;
4149 state->UserCallback = callback;
4150 state->UserCallbackData = callback_user_data;
4151
4152 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
4153 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
4154 g.ActiveIdAllowOverlap = !io.MouseDown[0];
4155 g.WantTextInputNextFrame = 1;
4156
4157 // Edit in progress
4158 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX;
4159 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
4160
4161 const bool is_osx = io.ConfigMacOSXBehaviors;
4162 if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
4163 {
4164 state->SelectAll();
4165 state->SelectedAllMouseLock = true;
4166 }
4167 else if (hovered && is_osx && io.MouseDoubleClicked[0])
4168 {
4169 // Double-click select a word only, OS X style (by simulating keystrokes)
4170 state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
4171 state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
4172 }
4173 else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
4174 {
4175 if (hovered)
4176 {
4177 stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
4178 state->CursorAnimReset();
4179 }
4180 }
4181 else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
4182 {
4183 stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
4184 state->CursorAnimReset();
4185 state->CursorFollow = true;
4186 }
4187 if (state->SelectedAllMouseLock && !io.MouseDown[0])
4188 state->SelectedAllMouseLock = false;
4189
4190 // It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys.
4191 // Win32 and GLFW naturally do it but not SDL.
4192 const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
4193 if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
4194 if (!io.InputQueueCharacters.contains('\t'))
4195 {
4196 unsigned int c = '\t'; // Insert TAB
4197 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4198 state->OnKeyPressed((int)c);
4199 }
4200
4201 // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
4202 // 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.
4203 if (io.InputQueueCharacters.Size > 0)
4204 {
4205 if (!ignore_char_inputs && !is_readonly && !user_nav_input_start)
4206 for (int n = 0; n < io.InputQueueCharacters.Size; n++)
4207 {
4208 // Insert character if they pass filtering
4209 unsigned int c = (unsigned int)io.InputQueueCharacters[n];
4210 if (c == '\t' && io.KeyShift)
4211 continue;
4212 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4213 state->OnKeyPressed((int)c);
4214 }
4215
4216 // Consume characters
4217 io.InputQueueCharacters.resize(0);
4218 }
4219 }
4220
4221 // Process other shortcuts/key-presses
4222 bool cancel_edit = false;
4223 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
4224 {
4225 IM_ASSERT(state != NULL);
4226 IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here.
4227
4228 const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
4229 state->Stb.row_count_per_page = row_count_per_page;
4230
4231 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
4232 const bool is_osx = io.ConfigMacOSXBehaviors;
4233 const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift));
4234 const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
4235 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
4236 const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
4237 const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift);
4238 const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
4239
4240 const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
4241 const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
4242 const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly;
4243 const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable);
4244 const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable;
4245
4246 if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
4247 else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
4248 else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
4249 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
4250 else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
4251 else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
4252 else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
4253 else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
4254 else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
4255 else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly)
4256 {
4257 if (!state->HasSelection())
4258 {
4259 if (is_wordmove_key_down)
4260 state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
4261 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl)
4262 state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
4263 }
4264 state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
4265 }
4266 else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter))
4267 {
4268 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
4269 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
4270 {
4271 enter_pressed = clear_active_id = true;
4272 }
4273 else if (!is_readonly)
4274 {
4275 unsigned int c = '\n'; // Insert new line
4276 if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4277 state->OnKeyPressed((int)c);
4278 }
4279 }
4280 else if (IsKeyPressedMap(ImGuiKey_Escape))
4281 {
4282 clear_active_id = cancel_edit = true;
4283 }
4284 else if (is_undo || is_redo)
4285 {
4286 state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
4287 state->ClearSelection();
4288 }
4289 else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
4290 {
4291 state->SelectAll();
4292 state->CursorFollow = true;
4293 }
4294 else if (is_cut || is_copy)
4295 {
4296 // Cut, Copy
4297 if (io.SetClipboardTextFn)
4298 {
4299 const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
4300 const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
4301 const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
4302 char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
4303 ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
4304 SetClipboardText(clipboard_data);
4305 MemFree(clipboard_data);
4306 }
4307 if (is_cut)
4308 {
4309 if (!state->HasSelection())
4310 state->SelectAll();
4311 state->CursorFollow = true;
4312 stb_textedit_cut(state, &state->Stb);
4313 }
4314 }
4315 else if (is_paste)
4316 {
4317 if (const char* clipboard = GetClipboardText())
4318 {
4319 // Filter pasted buffer
4320 const int clipboard_len = (int)strlen(clipboard);
4321 ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar));
4322 int clipboard_filtered_len = 0;
4323 for (const char* s = clipboard; *s; )
4324 {
4325 unsigned int c;
4326 s += ImTextCharFromUtf8(&c, s, NULL);
4327 if (c == 0)
4328 break;
4329 if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
4330 continue;
4331 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
4332 }
4333 clipboard_filtered[clipboard_filtered_len] = 0;
4334 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
4335 {
4336 stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
4337 state->CursorFollow = true;
4338 }
4339 MemFree(clipboard_filtered);
4340 }
4341 }
4342
4343 // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
4344 render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4345 }
4346
4347 // Process callbacks and apply result back to user's buffer.
4348 if (g.ActiveId == id)
4349 {
4350 IM_ASSERT(state != NULL);
4351 const char* apply_new_text = NULL;
4352 int apply_new_text_length = 0;
4353 if (cancel_edit)
4354 {
4355 // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
4356 if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0)
4357 {
4358 // Push records into the undo stack so we can CTRL+Z the revert operation itself
4359 apply_new_text = state->InitialTextA.Data;
4360 apply_new_text_length = state->InitialTextA.Size - 1;
4361 ImVector<ImWchar> w_text;
4362 if (apply_new_text_length > 0)
4363 {
4364 w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1);
4365 ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length);
4366 }
4367 stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0);
4368 }
4369 }
4370
4371 // 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.
4372 // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
4373 // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
4374 bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
4375 if (apply_edit_back_to_user_buffer)
4376 {
4377 // Apply new value immediately - copy modified buffer back
4378 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
4379 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
4380 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
4381 if (!is_readonly)
4382 {
4383 state->TextAIsValid = true;
4384 state->TextA.resize(state->TextW.Size * 4 + 1);
4385 ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
4386 }
4387
4388 // User callback
4389 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
4390 {
4391 IM_ASSERT(callback != NULL);
4392
4393 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
4394 ImGuiInputTextFlags event_flag = 0;
4395 ImGuiKey event_key = ImGuiKey_COUNT;
4396 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
4397 {
4398 event_flag = ImGuiInputTextFlags_CallbackCompletion;
4399 event_key = ImGuiKey_Tab;
4400 }
4401 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
4402 {
4403 event_flag = ImGuiInputTextFlags_CallbackHistory;
4404 event_key = ImGuiKey_UpArrow;
4405 }
4406 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
4407 {
4408 event_flag = ImGuiInputTextFlags_CallbackHistory;
4409 event_key = ImGuiKey_DownArrow;
4410 }
4411 else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
4412 {
4413 event_flag = ImGuiInputTextFlags_CallbackEdit;
4414 }
4415 else if (flags & ImGuiInputTextFlags_CallbackAlways)
4416 {
4417 event_flag = ImGuiInputTextFlags_CallbackAlways;
4418 }
4419
4420 if (event_flag)
4421 {
4422 ImGuiInputTextCallbackData callback_data;
4423 memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
4424 callback_data.EventFlag = event_flag;
4425 callback_data.Flags = flags;
4426 callback_data.UserData = callback_user_data;
4427
4428 callback_data.EventKey = event_key;
4429 callback_data.Buf = state->TextA.Data;
4430 callback_data.BufTextLen = state->CurLenA;
4431 callback_data.BufSize = state->BufCapacityA;
4432 callback_data.BufDirty = false;
4433
4434 // 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)
4435 ImWchar* text = state->TextW.Data;
4436 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
4437 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
4438 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
4439
4440 // Call user code
4441 callback(&callback_data);
4442
4443 // Read back what user may have modified
4444 IM_ASSERT(callback_data.Buf == state->TextA.Data); // Invalid to modify those fields
4445 IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
4446 IM_ASSERT(callback_data.Flags == flags);
4447 const bool buf_dirty = callback_data.BufDirty;
4448 if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; }
4449 if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
4450 if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
4451 if (buf_dirty)
4452 {
4453 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
4454 if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
4455 state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
4456 state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
4457 state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
4458 state->CursorAnimReset();
4459 }
4460 }
4461 }
4462
4463 // Will copy result string if modified
4464 if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
4465 {
4466 apply_new_text = state->TextA.Data;
4467 apply_new_text_length = state->CurLenA;
4468 }
4469 }
4470
4471 // Copy result to user buffer
4472 if (apply_new_text)
4473 {
4474 // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
4475 // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
4476 // without any storage on user's side.
4477 IM_ASSERT(apply_new_text_length >= 0);
4478 if (is_resizable)
4479 {
4480 ImGuiInputTextCallbackData callback_data;
4481 callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
4482 callback_data.Flags = flags;
4483 callback_data.Buf = buf;
4484 callback_data.BufTextLen = apply_new_text_length;
4485 callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
4486 callback_data.UserData = callback_user_data;
4487 callback(&callback_data);
4488 buf = callback_data.Buf;
4489 buf_size = callback_data.BufSize;
4490 apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
4491 IM_ASSERT(apply_new_text_length <= buf_size);
4492 }
4493 //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
4494
4495 // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
4496 ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
4497 value_changed = true;
4498 }
4499
4500 // Clear temporary user storage
4501 state->Flags = ImGuiInputTextFlags_None;
4502 state->UserCallback = NULL;
4503 state->UserCallbackData = NULL;
4504 }
4505
4506 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
4507 if (clear_active_id && g.ActiveId == id)
4508 ClearActiveID();
4509
4510 // Render frame
4511 if (!is_multiline)
4512 {
4513 RenderNavHighlight(frame_bb, id);
4514 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
4515 }
4516
4517 const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
4518 ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
4519 ImVec2 text_size(0.0f, 0.0f);
4520
4521 // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
4522 // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
4523 // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
4524 const int buf_display_max_length = 2 * 1024 * 1024;
4525 const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
4526 const char* buf_display_end = NULL; // We have specialized paths below for setting the length
4527 if (is_displaying_hint)
4528 {
4529 buf_display = hint;
4530 buf_display_end = hint + strlen(hint);
4531 }
4532
4533 // Render text. We currently only render selection when the widget is active or while scrolling.
4534 // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
4535 if (render_cursor || render_selection)
4536 {
4537 IM_ASSERT(state != NULL);
4538 if (!is_displaying_hint)
4539 buf_display_end = buf_display + state->CurLenA;
4540
4541 // Render text (with cursor and selection)
4542 // This is going to be messy. We need to:
4543 // - Display the text (this alone can be more easily clipped)
4544 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
4545 // - Measure text height (for scrollbar)
4546 // 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)
4547 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
4548 const ImWchar* text_begin = state->TextW.Data;
4549 ImVec2 cursor_offset, select_start_offset;
4550
4551 {
4552 // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
4553 const ImWchar* searches_input_ptr[2] = { NULL, NULL };
4554 int searches_result_line_no[2] = { -1000, -1000 };
4555 int searches_remaining = 0;
4556 if (render_cursor)
4557 {
4558 searches_input_ptr[0] = text_begin + state->Stb.cursor;
4559 searches_result_line_no[0] = -1;
4560 searches_remaining++;
4561 }
4562 if (render_selection)
4563 {
4564 searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
4565 searches_result_line_no[1] = -1;
4566 searches_remaining++;
4567 }
4568
4569 // Iterate all lines to find our line numbers
4570 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
4571 searches_remaining += is_multiline ? 1 : 0;
4572 int line_count = 0;
4573 //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bit
4574 for (const ImWchar* s = text_begin; *s != 0; s++)
4575 if (*s == '\n')
4576 {
4577 line_count++;
4578 if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
4579 if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
4580 }
4581 line_count++;
4582 if (searches_result_line_no[0] == -1)
4583 searches_result_line_no[0] = line_count;
4584 if (searches_result_line_no[1] == -1)
4585 searches_result_line_no[1] = line_count;
4586
4587 // Calculate 2d position by finding the beginning of the line and measuring distance
4588 cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
4589 cursor_offset.y = searches_result_line_no[0] * g.FontSize;
4590 if (searches_result_line_no[1] >= 0)
4591 {
4592 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
4593 select_start_offset.y = searches_result_line_no[1] * g.FontSize;
4594 }
4595
4596 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
4597 if (is_multiline)
4598 text_size = ImVec2(inner_size.x, line_count * g.FontSize);
4599 }
4600
4601 // Scroll
4602 if (render_cursor && state->CursorFollow)
4603 {
4604 // Horizontal scroll in chunks of quarter width
4605 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
4606 {
4607 const float scroll_increment_x = inner_size.x * 0.25f;
4608 const float visible_width = inner_size.x - style.FramePadding.x;
4609 if (cursor_offset.x < state->ScrollX)
4610 state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
4611 else if (cursor_offset.x - visible_width >= state->ScrollX)
4612 state->ScrollX = IM_FLOOR(cursor_offset.x - visible_width + scroll_increment_x);
4613 }
4614 else
4615 {
4616 state->ScrollX = 0.0f;
4617 }
4618
4619 // Vertical scroll
4620 if (is_multiline)
4621 {
4622 // Test if cursor is vertically visible
4623 if (cursor_offset.y - g.FontSize < scroll_y)
4624 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
4625 else if (cursor_offset.y - inner_size.y >= scroll_y)
4626 scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
4627 const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
4628 scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
4629 draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
4630 draw_window->Scroll.y = scroll_y;
4631 }
4632
4633 state->CursorFollow = false;
4634 }
4635
4636 // Draw selection
4637 const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
4638 if (render_selection)
4639 {
4640 const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
4641 const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
4642
4643 ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
4644 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.
4645 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
4646 ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
4647 for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
4648 {
4649 if (rect_pos.y > clip_rect.w + g.FontSize)
4650 break;
4651 if (rect_pos.y < clip_rect.y)
4652 {
4653 //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit
4654 //p = p ? p + 1 : text_selected_end;
4655 while (p < text_selected_end)
4656 if (*p++ == '\n')
4657 break;
4658 }
4659 else
4660 {
4661 ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
4662 if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
4663 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
4664 rect.ClipWith(clip_rect);
4665 if (rect.Overlaps(clip_rect))
4666 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
4667 }
4668 rect_pos.x = draw_pos.x - draw_scroll.x;
4669 rect_pos.y += g.FontSize;
4670 }
4671 }
4672
4673 // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
4674 if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
4675 {
4676 ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
4677 draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
4678 }
4679
4680 // Draw blinking cursor
4681 if (render_cursor)
4682 {
4683 state->CursorAnim += io.DeltaTime;
4684 bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
4685 ImVec2 cursor_screen_pos = ImFloor(draw_pos + cursor_offset - draw_scroll);
4686 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);
4687 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
4688 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
4689
4690 // 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.)
4691 if (!is_readonly)
4692 {
4693 g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
4694 g.PlatformImePosViewport = window->Viewport;
4695 }
4696 }
4697 }
4698 else
4699 {
4700 // Render text only (no selection, no cursor)
4701 if (is_multiline)
4702 text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
4703 else if (!is_displaying_hint && g.ActiveId == id)
4704 buf_display_end = buf_display + state->CurLenA;
4705 else if (!is_displaying_hint)
4706 buf_display_end = buf_display + strlen(buf_display);
4707
4708 if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
4709 {
4710 ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
4711 draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
4712 }
4713 }
4714
4715 if (is_password && !is_displaying_hint)
4716 PopFont();
4717
4718 if (is_multiline)
4719 {
4720 Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
4721 EndChild();
4722 EndGroup();
4723 }
4724
4725 // Log as text
4726 if (g.LogEnabled && (!is_password || is_displaying_hint))
4727 {
4728 LogSetNextTextDecoration("{", "}");
4729 LogRenderedText(&draw_pos, buf_display, buf_display_end);
4730 }
4731
4732 if (label_size.x > 0)
4733 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
4734
4735 if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited))
4736 MarkItemEdited(id);
4737
4738 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
4739 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
4740 return enter_pressed;
4741 else
4742 return value_changed;
4743 }
4744
4745 //-------------------------------------------------------------------------
4746 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
4747 //-------------------------------------------------------------------------
4748 // - ColorEdit3()
4749 // - ColorEdit4()
4750 // - ColorPicker3()
4751 // - RenderColorRectWithAlphaCheckerboard() [Internal]
4752 // - ColorPicker4()
4753 // - ColorButton()
4754 // - SetColorEditOptions()
4755 // - ColorTooltip() [Internal]
4756 // - ColorEditOptionsPopup() [Internal]
4757 // - ColorPickerOptionsPopup() [Internal]
4758 //-------------------------------------------------------------------------
4759
ColorEdit3(const char * label,float col[3],ImGuiColorEditFlags flags)4760 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
4761 {
4762 return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
4763 }
4764
4765 // Edit colors components (each component in 0.0f..1.0f range).
4766 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4767 // With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
ColorEdit4(const char * label,float col[4],ImGuiColorEditFlags flags)4768 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
4769 {
4770 ImGuiWindow* window = GetCurrentWindow();
4771 if (window->SkipItems)
4772 return false;
4773
4774 ImGuiContext& g = *GImGui;
4775 const ImGuiStyle& style = g.Style;
4776 const float square_sz = GetFrameHeight();
4777 const float w_full = CalcItemWidth();
4778 const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
4779 const float w_inputs = w_full - w_button;
4780 const char* label_display_end = FindRenderedTextEnd(label);
4781 g.NextItemData.ClearFlags();
4782
4783 BeginGroup();
4784 PushID(label);
4785
4786 // If we're not showing any slider there's no point in doing any HSV conversions
4787 const ImGuiColorEditFlags flags_untouched = flags;
4788 if (flags & ImGuiColorEditFlags_NoInputs)
4789 flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
4790
4791 // Context menu: display and modify options (before defaults are applied)
4792 if (!(flags & ImGuiColorEditFlags_NoOptions))
4793 ColorEditOptionsPopup(col, flags);
4794
4795 // Read stored options
4796 if (!(flags & ImGuiColorEditFlags_DisplayMask_))
4797 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
4798 if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
4799 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
4800 if (!(flags & ImGuiColorEditFlags_PickerMask_))
4801 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
4802 if (!(flags & ImGuiColorEditFlags_InputMask_))
4803 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
4804 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
4805 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
4806 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected
4807
4808 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
4809 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
4810 const int components = alpha ? 4 : 3;
4811
4812 // Convert to the formats we need
4813 float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
4814 if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
4815 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
4816 else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
4817 {
4818 // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
4819 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
4820 if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
4821 {
4822 if (f[1] == 0)
4823 f[0] = g.ColorEditLastHue;
4824 if (f[2] == 0)
4825 f[1] = g.ColorEditLastSat;
4826 }
4827 }
4828 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]) };
4829
4830 bool value_changed = false;
4831 bool value_changed_as_float = false;
4832
4833 const ImVec2 pos = window->DC.CursorPos;
4834 const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
4835 window->DC.CursorPos.x = pos.x + inputs_offset_x;
4836
4837 if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
4838 {
4839 // RGB/HSV 0..255 Sliders
4840 const float w_item_one = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
4841 const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
4842
4843 const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
4844 static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
4845 static const char* fmt_table_int[3][4] =
4846 {
4847 { "%3d", "%3d", "%3d", "%3d" }, // Short display
4848 { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
4849 { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
4850 };
4851 static const char* fmt_table_float[3][4] =
4852 {
4853 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
4854 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
4855 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
4856 };
4857 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
4858
4859 for (int n = 0; n < components; n++)
4860 {
4861 if (n > 0)
4862 SameLine(0, style.ItemInnerSpacing.x);
4863 SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last);
4864
4865 // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
4866 if (flags & ImGuiColorEditFlags_Float)
4867 {
4868 value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
4869 value_changed_as_float |= value_changed;
4870 }
4871 else
4872 {
4873 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
4874 }
4875 if (!(flags & ImGuiColorEditFlags_NoOptions))
4876 OpenPopupOnItemClick("context");
4877 }
4878 }
4879 else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
4880 {
4881 // RGB Hexadecimal Input
4882 char buf[64];
4883 if (alpha)
4884 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));
4885 else
4886 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
4887 SetNextItemWidth(w_inputs);
4888 if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
4889 {
4890 value_changed = true;
4891 char* p = buf;
4892 while (*p == '#' || ImCharIsBlankA(*p))
4893 p++;
4894 i[0] = i[1] = i[2] = 0;
4895 i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
4896 int r;
4897 if (alpha)
4898 r = sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
4899 else
4900 r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
4901 IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
4902 }
4903 if (!(flags & ImGuiColorEditFlags_NoOptions))
4904 OpenPopupOnItemClick("context");
4905 }
4906
4907 ImGuiWindow* picker_active_window = NULL;
4908 if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
4909 {
4910 const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
4911 window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
4912
4913 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
4914 if (ColorButton("##ColorButton", col_v4, flags))
4915 {
4916 if (!(flags & ImGuiColorEditFlags_NoPicker))
4917 {
4918 // Store current color and open a picker
4919 g.ColorPickerRef = col_v4;
4920 OpenPopup("picker");
4921 SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(-1, style.ItemSpacing.y));
4922 }
4923 }
4924 if (!(flags & ImGuiColorEditFlags_NoOptions))
4925 OpenPopupOnItemClick("context");
4926
4927 if (BeginPopup("picker"))
4928 {
4929 picker_active_window = g.CurrentWindow;
4930 if (label != label_display_end)
4931 {
4932 TextEx(label, label_display_end);
4933 Spacing();
4934 }
4935 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
4936 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
4937 SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
4938 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
4939 EndPopup();
4940 }
4941 }
4942
4943 if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
4944 {
4945 const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x;
4946 window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y);
4947 TextEx(label, label_display_end);
4948 }
4949
4950 // Convert back
4951 if (value_changed && picker_active_window == NULL)
4952 {
4953 if (!value_changed_as_float)
4954 for (int n = 0; n < 4; n++)
4955 f[n] = i[n] / 255.0f;
4956 if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
4957 {
4958 g.ColorEditLastHue = f[0];
4959 g.ColorEditLastSat = f[1];
4960 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
4961 memcpy(g.ColorEditLastColor, f, sizeof(float) * 3);
4962 }
4963 if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
4964 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
4965
4966 col[0] = f[0];
4967 col[1] = f[1];
4968 col[2] = f[2];
4969 if (alpha)
4970 col[3] = f[3];
4971 }
4972
4973 PopID();
4974 EndGroup();
4975
4976 // Drag and Drop Target
4977 // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
4978 if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
4979 {
4980 bool accepted_drag_drop = false;
4981 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
4982 {
4983 memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
4984 value_changed = accepted_drag_drop = true;
4985 }
4986 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
4987 {
4988 memcpy((float*)col, payload->Data, sizeof(float) * components);
4989 value_changed = accepted_drag_drop = true;
4990 }
4991
4992 // Drag-drop payloads are always RGB
4993 if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
4994 ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
4995 EndDragDropTarget();
4996 }
4997
4998 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
4999 if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
5000 g.LastItemData.ID = g.ActiveId;
5001
5002 if (value_changed)
5003 MarkItemEdited(g.LastItemData.ID);
5004
5005 return value_changed;
5006 }
5007
ColorPicker3(const char * label,float col[3],ImGuiColorEditFlags flags)5008 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
5009 {
5010 float col4[4] = { col[0], col[1], col[2], 1.0f };
5011 if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
5012 return false;
5013 col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
5014 return true;
5015 }
5016
5017 // Helper for ColorPicker4()
RenderArrowsForVerticalBar(ImDrawList * draw_list,ImVec2 pos,ImVec2 half_sz,float bar_w,float alpha)5018 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
5019 {
5020 ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
5021 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8));
5022 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
5023 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32(0,0,0,alpha8));
5024 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8));
5025 }
5026
5027 // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5028 // (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)
5029 // 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..)
5030 // FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)
ColorPicker4(const char * label,float col[4],ImGuiColorEditFlags flags,const float * ref_col)5031 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
5032 {
5033 ImGuiContext& g = *GImGui;
5034 ImGuiWindow* window = GetCurrentWindow();
5035 if (window->SkipItems)
5036 return false;
5037
5038 ImDrawList* draw_list = window->DrawList;
5039 ImGuiStyle& style = g.Style;
5040 ImGuiIO& io = g.IO;
5041
5042 const float width = CalcItemWidth();
5043 g.NextItemData.ClearFlags();
5044
5045 PushID(label);
5046 BeginGroup();
5047
5048 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5049 flags |= ImGuiColorEditFlags_NoSmallPreview;
5050
5051 // Context menu: display and store options.
5052 if (!(flags & ImGuiColorEditFlags_NoOptions))
5053 ColorPickerOptionsPopup(col, flags);
5054
5055 // Read stored options
5056 if (!(flags & ImGuiColorEditFlags_PickerMask_))
5057 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
5058 if (!(flags & ImGuiColorEditFlags_InputMask_))
5059 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
5060 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
5061 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected
5062 if (!(flags & ImGuiColorEditFlags_NoOptions))
5063 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
5064
5065 // Setup
5066 int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
5067 bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
5068 ImVec2 picker_pos = window->DC.CursorPos;
5069 float square_sz = GetFrameHeight();
5070 float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
5071 float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
5072 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
5073 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
5074 float bars_triangles_half_sz = IM_FLOOR(bars_width * 0.20f);
5075
5076 float backup_initial_col[4];
5077 memcpy(backup_initial_col, col, components * sizeof(float));
5078
5079 float wheel_thickness = sv_picker_size * 0.08f;
5080 float wheel_r_outer = sv_picker_size * 0.50f;
5081 float wheel_r_inner = wheel_r_outer - wheel_thickness;
5082 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
5083
5084 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
5085 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
5086 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
5087 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
5088 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
5089
5090 float H = col[0], S = col[1], V = col[2];
5091 float R = col[0], G = col[1], B = col[2];
5092 if (flags & ImGuiColorEditFlags_InputRGB)
5093 {
5094 // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
5095 ColorConvertRGBtoHSV(R, G, B, H, S, V);
5096 if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
5097 {
5098 if (S == 0)
5099 H = g.ColorEditLastHue;
5100 if (V == 0)
5101 S = g.ColorEditLastSat;
5102 }
5103 }
5104 else if (flags & ImGuiColorEditFlags_InputHSV)
5105 {
5106 ColorConvertHSVtoRGB(H, S, V, R, G, B);
5107 }
5108
5109 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
5110
5111 PushItemFlag(ImGuiItemFlags_NoNav, true);
5112 if (flags & ImGuiColorEditFlags_PickerHueWheel)
5113 {
5114 // Hue wheel + SV triangle logic
5115 InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
5116 if (IsItemActive())
5117 {
5118 ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
5119 ImVec2 current_off = g.IO.MousePos - wheel_center;
5120 float initial_dist2 = ImLengthSqr(initial_off);
5121 if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
5122 {
5123 // Interactive with Hue wheel
5124 H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
5125 if (H < 0.0f)
5126 H += 1.0f;
5127 value_changed = value_changed_h = true;
5128 }
5129 float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
5130 float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
5131 if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
5132 {
5133 // Interacting with SV triangle
5134 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
5135 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
5136 current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
5137 float uu, vv, ww;
5138 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
5139 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
5140 S = ImClamp(uu / V, 0.0001f, 1.0f);
5141 value_changed = value_changed_sv = true;
5142 }
5143 }
5144 if (!(flags & ImGuiColorEditFlags_NoOptions))
5145 OpenPopupOnItemClick("context");
5146 }
5147 else if (flags & ImGuiColorEditFlags_PickerHueBar)
5148 {
5149 // SV rectangle logic
5150 InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
5151 if (IsItemActive())
5152 {
5153 S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
5154 V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5155 value_changed = value_changed_sv = true;
5156 }
5157 if (!(flags & ImGuiColorEditFlags_NoOptions))
5158 OpenPopupOnItemClick("context");
5159
5160 // Hue bar logic
5161 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
5162 InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
5163 if (IsItemActive())
5164 {
5165 H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5166 value_changed = value_changed_h = true;
5167 }
5168 }
5169
5170 // Alpha bar logic
5171 if (alpha_bar)
5172 {
5173 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
5174 InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
5175 if (IsItemActive())
5176 {
5177 col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5178 value_changed = true;
5179 }
5180 }
5181 PopItemFlag(); // ImGuiItemFlags_NoNav
5182
5183 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5184 {
5185 SameLine(0, style.ItemInnerSpacing.x);
5186 BeginGroup();
5187 }
5188
5189 if (!(flags & ImGuiColorEditFlags_NoLabel))
5190 {
5191 const char* label_display_end = FindRenderedTextEnd(label);
5192 if (label != label_display_end)
5193 {
5194 if ((flags & ImGuiColorEditFlags_NoSidePreview))
5195 SameLine(0, style.ItemInnerSpacing.x);
5196 TextEx(label, label_display_end);
5197 }
5198 }
5199
5200 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5201 {
5202 PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
5203 ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5204 if ((flags & ImGuiColorEditFlags_NoLabel))
5205 Text("Current");
5206
5207 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
5208 ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
5209 if (ref_col != NULL)
5210 {
5211 Text("Original");
5212 ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
5213 if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
5214 {
5215 memcpy(col, ref_col, components * sizeof(float));
5216 value_changed = true;
5217 }
5218 }
5219 PopItemFlag();
5220 EndGroup();
5221 }
5222
5223 // Convert back color to RGB
5224 if (value_changed_h || value_changed_sv)
5225 {
5226 if (flags & ImGuiColorEditFlags_InputRGB)
5227 {
5228 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]);
5229 g.ColorEditLastHue = H;
5230 g.ColorEditLastSat = S;
5231 memcpy(g.ColorEditLastColor, col, sizeof(float) * 3);
5232 }
5233 else if (flags & ImGuiColorEditFlags_InputHSV)
5234 {
5235 col[0] = H;
5236 col[1] = S;
5237 col[2] = V;
5238 }
5239 }
5240
5241 // R,G,B and H,S,V slider color editor
5242 bool value_changed_fix_hue_wrap = false;
5243 if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
5244 {
5245 PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
5246 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
5247 ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
5248 if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5249 if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
5250 {
5251 // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
5252 // 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)
5253 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
5254 value_changed = true;
5255 }
5256 if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5257 value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
5258 if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5259 value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
5260 PopItemWidth();
5261 }
5262
5263 // Try to cancel hue wrap (after ColorEdit4 call), if any
5264 if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
5265 {
5266 float new_H, new_S, new_V;
5267 ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
5268 if (new_H <= 0 && H > 0)
5269 {
5270 if (new_V <= 0 && V != new_V)
5271 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
5272 else if (new_S <= 0)
5273 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
5274 }
5275 }
5276
5277 if (value_changed)
5278 {
5279 if (flags & ImGuiColorEditFlags_InputRGB)
5280 {
5281 R = col[0];
5282 G = col[1];
5283 B = col[2];
5284 ColorConvertRGBtoHSV(R, G, B, H, S, V);
5285 if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) // Fix local Hue as display below will use it immediately.
5286 {
5287 if (S == 0)
5288 H = g.ColorEditLastHue;
5289 if (V == 0)
5290 S = g.ColorEditLastSat;
5291 }
5292 }
5293 else if (flags & ImGuiColorEditFlags_InputHSV)
5294 {
5295 H = col[0];
5296 S = col[1];
5297 V = col[2];
5298 ColorConvertHSVtoRGB(H, S, V, R, G, B);
5299 }
5300 }
5301
5302 const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
5303 const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
5304 const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
5305 const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
5306 const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) };
5307
5308 ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
5309 ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
5310 ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
5311
5312 ImVec2 sv_cursor_pos;
5313
5314 if (flags & ImGuiColorEditFlags_PickerHueWheel)
5315 {
5316 // Render Hue Wheel
5317 const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
5318 const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
5319 for (int n = 0; n < 6; n++)
5320 {
5321 const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
5322 const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
5323 const int vert_start_idx = draw_list->VtxBuffer.Size;
5324 draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
5325 draw_list->PathStroke(col_white, 0, wheel_thickness);
5326 const int vert_end_idx = draw_list->VtxBuffer.Size;
5327
5328 // Paint colors over existing vertices
5329 ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
5330 ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
5331 ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
5332 }
5333
5334 // Render Cursor + preview on Hue Wheel
5335 float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
5336 float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
5337 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);
5338 float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
5339 int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
5340 draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
5341 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
5342 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
5343
5344 // Render SV triangle (rotated according to hue)
5345 ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
5346 ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
5347 ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
5348 ImVec2 uv_white = GetFontTexUvWhitePixel();
5349 draw_list->PrimReserve(6, 6);
5350 draw_list->PrimVtx(tra, uv_white, hue_color32);
5351 draw_list->PrimVtx(trb, uv_white, hue_color32);
5352 draw_list->PrimVtx(trc, uv_white, col_white);
5353 draw_list->PrimVtx(tra, uv_white, 0);
5354 draw_list->PrimVtx(trb, uv_white, col_black);
5355 draw_list->PrimVtx(trc, uv_white, 0);
5356 draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
5357 sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
5358 }
5359 else if (flags & ImGuiColorEditFlags_PickerHueBar)
5360 {
5361 // Render SV Square
5362 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
5363 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
5364 RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
5365 sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S) * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
5366 sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
5367
5368 // Render Hue Bar
5369 for (int i = 0; i < 6; ++i)
5370 draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]);
5371 float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
5372 RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
5373 RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);
5374 }
5375
5376 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
5377 float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
5378 draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, 12);
5379 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, 12);
5380 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12);
5381
5382 // Render alpha bar
5383 if (alpha_bar)
5384 {
5385 float alpha = ImSaturate(col[3]);
5386 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
5387 RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
5388 draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK);
5389 float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
5390 RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
5391 RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);
5392 }
5393
5394 EndGroup();
5395
5396 if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
5397 value_changed = false;
5398 if (value_changed)
5399 MarkItemEdited(g.LastItemData.ID);
5400
5401 PopID();
5402
5403 return value_changed;
5404 }
5405
5406 // A little color square. Return true when clicked.
5407 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
5408 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
5409 // Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
ColorButton(const char * desc_id,const ImVec4 & col,ImGuiColorEditFlags flags,ImVec2 size)5410 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
5411 {
5412 ImGuiWindow* window = GetCurrentWindow();
5413 if (window->SkipItems)
5414 return false;
5415
5416 ImGuiContext& g = *GImGui;
5417 const ImGuiID id = window->GetID(desc_id);
5418 float default_size = GetFrameHeight();
5419 if (size.x == 0.0f)
5420 size.x = default_size;
5421 if (size.y == 0.0f)
5422 size.y = default_size;
5423 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
5424 ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
5425 if (!ItemAdd(bb, id))
5426 return false;
5427
5428 bool hovered, held;
5429 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
5430
5431 if (flags & ImGuiColorEditFlags_NoAlpha)
5432 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
5433
5434 ImVec4 col_rgb = col;
5435 if (flags & ImGuiColorEditFlags_InputHSV)
5436 ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
5437
5438 ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
5439 float grid_step = ImMin(size.x, size.y) / 2.99f;
5440 float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
5441 ImRect bb_inner = bb;
5442 float off = 0.0f;
5443 if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
5444 {
5445 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.
5446 bb_inner.Expand(off);
5447 }
5448 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
5449 {
5450 float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
5451 RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight);
5452 window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
5453 }
5454 else
5455 {
5456 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
5457 ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
5458 if (col_source.w < 1.0f)
5459 RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
5460 else
5461 window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
5462 }
5463 RenderNavHighlight(bb, id);
5464 if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
5465 {
5466 if (g.Style.FrameBorderSize > 0.0f)
5467 RenderFrameBorder(bb.Min, bb.Max, rounding);
5468 else
5469 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
5470 }
5471
5472 // Drag and Drop Source
5473 // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
5474 if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
5475 {
5476 if (flags & ImGuiColorEditFlags_NoAlpha)
5477 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
5478 else
5479 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
5480 ColorButton(desc_id, col, flags);
5481 SameLine();
5482 TextEx("Color");
5483 EndDragDropSource();
5484 }
5485
5486 // Tooltip
5487 if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
5488 ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
5489
5490 return pressed;
5491 }
5492
5493 // Initialize/override default color options
SetColorEditOptions(ImGuiColorEditFlags flags)5494 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
5495 {
5496 ImGuiContext& g = *GImGui;
5497 if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5498 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
5499 if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
5500 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
5501 if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
5502 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
5503 if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
5504 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
5505 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected
5506 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected
5507 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected
5508 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected
5509 g.ColorEditOptions = flags;
5510 }
5511
5512 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
ColorTooltip(const char * text,const float * col,ImGuiColorEditFlags flags)5513 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
5514 {
5515 ImGuiContext& g = *GImGui;
5516
5517 BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip);
5518 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
5519 if (text_end > text)
5520 {
5521 TextEx(text, text_end);
5522 Separator();
5523 }
5524
5525 ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
5526 ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5527 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]);
5528 ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
5529 SameLine();
5530 if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
5531 {
5532 if (flags & ImGuiColorEditFlags_NoAlpha)
5533 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]);
5534 else
5535 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]);
5536 }
5537 else if (flags & ImGuiColorEditFlags_InputHSV)
5538 {
5539 if (flags & ImGuiColorEditFlags_NoAlpha)
5540 Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
5541 else
5542 Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
5543 }
5544 EndTooltip();
5545 }
5546
ColorEditOptionsPopup(const float * col,ImGuiColorEditFlags flags)5547 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
5548 {
5549 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
5550 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
5551 if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
5552 return;
5553 ImGuiContext& g = *GImGui;
5554 ImGuiColorEditFlags opts = g.ColorEditOptions;
5555 if (allow_opt_inputs)
5556 {
5557 if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
5558 if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
5559 if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
5560 }
5561 if (allow_opt_datatype)
5562 {
5563 if (allow_opt_inputs) Separator();
5564 if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
5565 if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
5566 }
5567
5568 if (allow_opt_inputs || allow_opt_datatype)
5569 Separator();
5570 if (Button("Copy as..", ImVec2(-1, 0)))
5571 OpenPopup("Copy");
5572 if (BeginPopup("Copy"))
5573 {
5574 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]);
5575 char buf[64];
5576 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5577 if (Selectable(buf))
5578 SetClipboardText(buf);
5579 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
5580 if (Selectable(buf))
5581 SetClipboardText(buf);
5582 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
5583 if (Selectable(buf))
5584 SetClipboardText(buf);
5585 if (!(flags & ImGuiColorEditFlags_NoAlpha))
5586 {
5587 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
5588 if (Selectable(buf))
5589 SetClipboardText(buf);
5590 }
5591 EndPopup();
5592 }
5593
5594 g.ColorEditOptions = opts;
5595 EndPopup();
5596 }
5597
ColorPickerOptionsPopup(const float * ref_col,ImGuiColorEditFlags flags)5598 void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
5599 {
5600 bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
5601 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
5602 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
5603 return;
5604 ImGuiContext& g = *GImGui;
5605 if (allow_opt_picker)
5606 {
5607 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
5608 PushItemWidth(picker_size.x);
5609 for (int picker_type = 0; picker_type < 2; picker_type++)
5610 {
5611 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
5612 if (picker_type > 0) Separator();
5613 PushID(picker_type);
5614 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
5615 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
5616 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
5617 ImVec2 backup_pos = GetCursorScreenPos();
5618 if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
5619 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
5620 SetCursorScreenPos(backup_pos);
5621 ImVec4 previewing_ref_col;
5622 memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
5623 ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
5624 PopID();
5625 }
5626 PopItemWidth();
5627 }
5628 if (allow_opt_alpha_bar)
5629 {
5630 if (allow_opt_picker) Separator();
5631 CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
5632 }
5633 EndPopup();
5634 }
5635
5636 //-------------------------------------------------------------------------
5637 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
5638 //-------------------------------------------------------------------------
5639 // - TreeNode()
5640 // - TreeNodeV()
5641 // - TreeNodeEx()
5642 // - TreeNodeExV()
5643 // - TreeNodeBehavior() [Internal]
5644 // - TreePush()
5645 // - TreePop()
5646 // - GetTreeNodeToLabelSpacing()
5647 // - SetNextItemOpen()
5648 // - CollapsingHeader()
5649 //-------------------------------------------------------------------------
5650
TreeNode(const char * str_id,const char * fmt,...)5651 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
5652 {
5653 va_list args;
5654 va_start(args, fmt);
5655 bool is_open = TreeNodeExV(str_id, 0, fmt, args);
5656 va_end(args);
5657 return is_open;
5658 }
5659
TreeNode(const void * ptr_id,const char * fmt,...)5660 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
5661 {
5662 va_list args;
5663 va_start(args, fmt);
5664 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
5665 va_end(args);
5666 return is_open;
5667 }
5668
TreeNode(const char * label)5669 bool ImGui::TreeNode(const char* label)
5670 {
5671 ImGuiWindow* window = GetCurrentWindow();
5672 if (window->SkipItems)
5673 return false;
5674 return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
5675 }
5676
TreeNodeV(const char * str_id,const char * fmt,va_list args)5677 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
5678 {
5679 return TreeNodeExV(str_id, 0, fmt, args);
5680 }
5681
TreeNodeV(const void * ptr_id,const char * fmt,va_list args)5682 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
5683 {
5684 return TreeNodeExV(ptr_id, 0, fmt, args);
5685 }
5686
TreeNodeEx(const char * label,ImGuiTreeNodeFlags flags)5687 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
5688 {
5689 ImGuiWindow* window = GetCurrentWindow();
5690 if (window->SkipItems)
5691 return false;
5692
5693 return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
5694 }
5695
TreeNodeEx(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,...)5696 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
5697 {
5698 va_list args;
5699 va_start(args, fmt);
5700 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
5701 va_end(args);
5702 return is_open;
5703 }
5704
TreeNodeEx(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,...)5705 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
5706 {
5707 va_list args;
5708 va_start(args, fmt);
5709 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
5710 va_end(args);
5711 return is_open;
5712 }
5713
TreeNodeExV(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)5714 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
5715 {
5716 ImGuiWindow* window = GetCurrentWindow();
5717 if (window->SkipItems)
5718 return false;
5719
5720 ImGuiContext& g = *GImGui;
5721 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5722 return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
5723 }
5724
TreeNodeExV(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)5725 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
5726 {
5727 ImGuiWindow* window = GetCurrentWindow();
5728 if (window->SkipItems)
5729 return false;
5730
5731 ImGuiContext& g = *GImGui;
5732 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
5733 return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
5734 }
5735
TreeNodeBehaviorIsOpen(ImGuiID id,ImGuiTreeNodeFlags flags)5736 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
5737 {
5738 if (flags & ImGuiTreeNodeFlags_Leaf)
5739 return true;
5740
5741 // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function)
5742 ImGuiContext& g = *GImGui;
5743 ImGuiWindow* window = g.CurrentWindow;
5744 ImGuiStorage* storage = window->DC.StateStorage;
5745
5746 bool is_open;
5747 if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen)
5748 {
5749 if (g.NextItemData.OpenCond & ImGuiCond_Always)
5750 {
5751 is_open = g.NextItemData.OpenVal;
5752 storage->SetInt(id, is_open);
5753 }
5754 else
5755 {
5756 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
5757 const int stored_value = storage->GetInt(id, -1);
5758 if (stored_value == -1)
5759 {
5760 is_open = g.NextItemData.OpenVal;
5761 storage->SetInt(id, is_open);
5762 }
5763 else
5764 {
5765 is_open = stored_value != 0;
5766 }
5767 }
5768 }
5769 else
5770 {
5771 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
5772 }
5773
5774 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
5775 // NB- If we are above max depth we still allow manually opened nodes to be logged.
5776 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
5777 is_open = true;
5778
5779 return is_open;
5780 }
5781
TreeNodeBehavior(ImGuiID id,ImGuiTreeNodeFlags flags,const char * label,const char * label_end)5782 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
5783 {
5784 ImGuiWindow* window = GetCurrentWindow();
5785 if (window->SkipItems)
5786 return false;
5787
5788 ImGuiContext& g = *GImGui;
5789 const ImGuiStyle& style = g.Style;
5790 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
5791 const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
5792
5793 if (!label_end)
5794 label_end = FindRenderedTextEnd(label);
5795 const ImVec2 label_size = CalcTextSize(label, label_end, false);
5796
5797 // We vertically grow up to current line height up the typical widget height.
5798 const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
5799 ImRect frame_bb;
5800 frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
5801 frame_bb.Min.y = window->DC.CursorPos.y;
5802 frame_bb.Max.x = window->WorkRect.Max.x;
5803 frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
5804 if (display_frame)
5805 {
5806 // Framed header expand a little outside the default padding, to the edge of InnerClipRect
5807 // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f)
5808 frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f);
5809 frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f);
5810 }
5811
5812 const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapser arrow width + Spacing
5813 const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it
5814 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapser
5815 ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
5816 ItemSize(ImVec2(text_width, frame_height), padding.y);
5817
5818 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
5819 ImRect interact_bb = frame_bb;
5820 if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0)
5821 interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f;
5822
5823 // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
5824 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
5825 // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
5826 const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
5827 bool is_open = TreeNodeBehaviorIsOpen(id, flags);
5828 if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5829 window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
5830
5831 bool item_add = ItemAdd(interact_bb, id);
5832 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
5833 g.LastItemData.DisplayRect = frame_bb;
5834
5835 if (!item_add)
5836 {
5837 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5838 TreePushOverrideID(id);
5839 IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
5840 return is_open;
5841 }
5842
5843 ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
5844 if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
5845 button_flags |= ImGuiButtonFlags_AllowItemOverlap;
5846 if (!is_leaf)
5847 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
5848
5849 // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
5850 // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
5851 // When clicking on the rest of the tree node we always disallow keyboard modifiers.
5852 const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
5853 const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
5854 const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
5855 if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
5856 button_flags |= ImGuiButtonFlags_NoKeyModifiers;
5857
5858 // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
5859 // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
5860 // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
5861 // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
5862 // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
5863 // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
5864 // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
5865 // It is rather standard that arrow click react on Down rather than Up.
5866 // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work.
5867 if (is_mouse_x_over_arrow)
5868 button_flags |= ImGuiButtonFlags_PressedOnClick;
5869 else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
5870 button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
5871 else
5872 button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
5873
5874 bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
5875 const bool was_selected = selected;
5876
5877 bool hovered, held;
5878 bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
5879 bool toggled = false;
5880 if (!is_leaf)
5881 {
5882 if (pressed && g.DragDropHoldJustPressedId != id)
5883 {
5884 if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
5885 toggled = true;
5886 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
5887 toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
5888 if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0])
5889 toggled = true;
5890 }
5891 else if (pressed && g.DragDropHoldJustPressedId == id)
5892 {
5893 IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
5894 if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
5895 toggled = true;
5896 }
5897
5898 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
5899 {
5900 toggled = true;
5901 NavMoveRequestCancel();
5902 }
5903 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?
5904 {
5905 toggled = true;
5906 NavMoveRequestCancel();
5907 }
5908
5909 if (toggled)
5910 {
5911 is_open = !is_open;
5912 window->DC.StateStorage->SetInt(id, is_open);
5913 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
5914 }
5915 }
5916 if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
5917 SetItemAllowOverlap();
5918
5919 // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
5920 if (selected != was_selected) //-V547
5921 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
5922
5923 // Render
5924 const ImU32 text_col = GetColorU32(ImGuiCol_Text);
5925 ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
5926 if (display_frame)
5927 {
5928 // Framed type
5929 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5930 RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
5931 RenderNavHighlight(frame_bb, id, nav_highlight_flags);
5932 if (flags & ImGuiTreeNodeFlags_Bullet)
5933 RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
5934 else if (!is_leaf)
5935 RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
5936 else // Leaf without bullet, left-adjusted text
5937 text_pos.x -= text_offset_x;
5938 if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
5939 frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
5940
5941 if (g.LogEnabled)
5942 LogSetNextTextDecoration("###", "###");
5943 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
5944 }
5945 else
5946 {
5947 // Unframed typed for tree nodes
5948 if (hovered || selected)
5949 {
5950 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5951 RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
5952 }
5953 RenderNavHighlight(frame_bb, id, nav_highlight_flags);
5954 if (flags & ImGuiTreeNodeFlags_Bullet)
5955 RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
5956 else if (!is_leaf)
5957 RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
5958 if (g.LogEnabled)
5959 LogSetNextTextDecoration(">", NULL);
5960 RenderText(text_pos, label, label_end, false);
5961 }
5962
5963 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
5964 TreePushOverrideID(id);
5965 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
5966 return is_open;
5967 }
5968
TreePush(const char * str_id)5969 void ImGui::TreePush(const char* str_id)
5970 {
5971 ImGuiWindow* window = GetCurrentWindow();
5972 Indent();
5973 window->DC.TreeDepth++;
5974 PushID(str_id ? str_id : "#TreePush");
5975 }
5976
TreePush(const void * ptr_id)5977 void ImGui::TreePush(const void* ptr_id)
5978 {
5979 ImGuiWindow* window = GetCurrentWindow();
5980 Indent();
5981 window->DC.TreeDepth++;
5982 PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
5983 }
5984
TreePushOverrideID(ImGuiID id)5985 void ImGui::TreePushOverrideID(ImGuiID id)
5986 {
5987 ImGuiContext& g = *GImGui;
5988 ImGuiWindow* window = g.CurrentWindow;
5989 Indent();
5990 window->DC.TreeDepth++;
5991 window->IDStack.push_back(id);
5992 }
5993
TreePop()5994 void ImGui::TreePop()
5995 {
5996 ImGuiContext& g = *GImGui;
5997 ImGuiWindow* window = g.CurrentWindow;
5998 Unindent();
5999
6000 window->DC.TreeDepth--;
6001 ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
6002
6003 // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
6004 if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6005 if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask))
6006 {
6007 SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect());
6008 NavMoveRequestCancel();
6009 }
6010 window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1;
6011
6012 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.
6013 PopID();
6014 }
6015
6016 // Horizontal distance preceding label when using TreeNode() or Bullet()
GetTreeNodeToLabelSpacing()6017 float ImGui::GetTreeNodeToLabelSpacing()
6018 {
6019 ImGuiContext& g = *GImGui;
6020 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
6021 }
6022
6023 // Set next TreeNode/CollapsingHeader open state.
SetNextItemOpen(bool is_open,ImGuiCond cond)6024 void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
6025 {
6026 ImGuiContext& g = *GImGui;
6027 if (g.CurrentWindow->SkipItems)
6028 return;
6029 g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen;
6030 g.NextItemData.OpenVal = is_open;
6031 g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always;
6032 }
6033
6034 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
6035 // 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)6036 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
6037 {
6038 ImGuiWindow* window = GetCurrentWindow();
6039 if (window->SkipItems)
6040 return false;
6041
6042 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
6043 }
6044
6045 // p_visible == NULL : regular collapsing header
6046 // p_visible != NULL && *p_visible == true : show a small close button on the corner of the header, clicking the button will set *p_visible = false
6047 // p_visible != NULL && *p_visible == false : do not show the header at all
6048 // Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
CollapsingHeader(const char * label,bool * p_visible,ImGuiTreeNodeFlags flags)6049 bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
6050 {
6051 ImGuiWindow* window = GetCurrentWindow();
6052 if (window->SkipItems)
6053 return false;
6054
6055 if (p_visible && !*p_visible)
6056 return false;
6057
6058 ImGuiID id = window->GetID(label);
6059 flags |= ImGuiTreeNodeFlags_CollapsingHeader;
6060 if (p_visible)
6061 flags |= ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
6062 bool is_open = TreeNodeBehavior(id, flags, label);
6063 if (p_visible != NULL)
6064 {
6065 // Create a small overlapping close button
6066 // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
6067 // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
6068 ImGuiContext& g = *GImGui;
6069 ImGuiLastItemData last_item_backup = g.LastItemData;
6070 float button_size = g.FontSize;
6071 float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
6072 float button_y = g.LastItemData.Rect.Min.y;
6073 ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
6074 if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
6075 *p_visible = false;
6076 g.LastItemData = last_item_backup;
6077 }
6078
6079 return is_open;
6080 }
6081
6082 //-------------------------------------------------------------------------
6083 // [SECTION] Widgets: Selectable
6084 //-------------------------------------------------------------------------
6085 // - Selectable()
6086 //-------------------------------------------------------------------------
6087
6088 // Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
6089 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
6090 // With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowItemOverlap are also frequently used flags.
6091 // FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
Selectable(const char * label,bool selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)6092 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6093 {
6094 ImGuiWindow* window = GetCurrentWindow();
6095 if (window->SkipItems)
6096 return false;
6097
6098 ImGuiContext& g = *GImGui;
6099 const ImGuiStyle& style = g.Style;
6100
6101 // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
6102 ImGuiID id = window->GetID(label);
6103 ImVec2 label_size = CalcTextSize(label, NULL, true);
6104 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
6105 ImVec2 pos = window->DC.CursorPos;
6106 pos.y += window->DC.CurrLineTextBaseOffset;
6107 ItemSize(size, 0.0f);
6108
6109 // Fill horizontal space
6110 // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
6111 const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
6112 const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
6113 const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
6114 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
6115 size.x = ImMax(label_size.x, max_x - min_x);
6116
6117 // Text stays at the submission position, but bounding box may be extended on both sides
6118 const ImVec2 text_min = pos;
6119 const ImVec2 text_max(min_x + size.x, pos.y + size.y);
6120
6121 // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
6122 ImRect bb(min_x, pos.y, text_max.x, text_max.y);
6123 if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
6124 {
6125 const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
6126 const float spacing_y = style.ItemSpacing.y;
6127 const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
6128 const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
6129 bb.Min.x -= spacing_L;
6130 bb.Min.y -= spacing_U;
6131 bb.Max.x += (spacing_x - spacing_L);
6132 bb.Max.y += (spacing_y - spacing_U);
6133 }
6134 //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
6135
6136 // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable..
6137 const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6138 const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6139 if (span_all_columns)
6140 {
6141 window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6142 window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6143 }
6144
6145 bool item_add;
6146 const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
6147 if (disabled_item)
6148 {
6149 ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
6150 g.CurrentItemFlags |= ImGuiItemFlags_Disabled;
6151 item_add = ItemAdd(bb, id);
6152 g.CurrentItemFlags = backup_item_flags;
6153 }
6154 else
6155 {
6156 item_add = ItemAdd(bb, id);
6157 }
6158
6159 if (span_all_columns)
6160 {
6161 window->ClipRect.Min.x = backup_clip_rect_min_x;
6162 window->ClipRect.Max.x = backup_clip_rect_max_x;
6163 }
6164
6165 if (!item_add)
6166 return false;
6167
6168 const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
6169 if (disabled_item && !disabled_global) // Only testing this as an optimization
6170 PushDisabled(true);
6171
6172 // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
6173 // which would be advantageous since most selectable are not selected.
6174 if (span_all_columns && window->DC.CurrentColumns)
6175 PushColumnsBackground();
6176 else if (span_all_columns && g.CurrentTable)
6177 TablePushBackgroundChannel();
6178
6179 // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
6180 ImGuiButtonFlags button_flags = 0;
6181 if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
6182 if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
6183 if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
6184 if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
6185 if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
6186
6187 const bool was_selected = selected;
6188 bool hovered, held;
6189 bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
6190
6191 // Auto-select when moved into
6192 // - This will be more fully fleshed in the range-select branch
6193 // - This is not exposed as it won't nicely work with some user side handling of shift/control
6194 // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
6195 // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
6196 // - (2) usage will fail with clipped items
6197 // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
6198 if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent)
6199 if (g.NavJustMovedToId == id)
6200 selected = pressed = true;
6201
6202 // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
6203 if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
6204 {
6205 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
6206 {
6207 SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos));
6208 g.NavDisableHighlight = true;
6209 }
6210 }
6211 if (pressed)
6212 MarkItemEdited(id);
6213
6214 if (flags & ImGuiSelectableFlags_AllowItemOverlap)
6215 SetItemAllowOverlap();
6216
6217 // In this branch, Selectable() cannot toggle the selection so this will never trigger.
6218 if (selected != was_selected) //-V547
6219 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
6220
6221 // Render
6222 if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld))
6223 hovered = true;
6224 if (hovered || selected)
6225 {
6226 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6227 RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
6228 }
6229 RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
6230
6231 if (span_all_columns && window->DC.CurrentColumns)
6232 PopColumnsBackground();
6233 else if (span_all_columns && g.CurrentTable)
6234 TablePopBackgroundChannel();
6235
6236 RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
6237
6238 // Automatically close popups
6239 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup))
6240 CloseCurrentPopup();
6241
6242 if (disabled_item && !disabled_global)
6243 PopDisabled();
6244
6245 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
6246 return pressed;
6247 }
6248
Selectable(const char * label,bool * p_selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)6249 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6250 {
6251 if (Selectable(label, *p_selected, flags, size_arg))
6252 {
6253 *p_selected = !*p_selected;
6254 return true;
6255 }
6256 return false;
6257 }
6258
6259 //-------------------------------------------------------------------------
6260 // [SECTION] Widgets: ListBox
6261 //-------------------------------------------------------------------------
6262 // - BeginListBox()
6263 // - EndListBox()
6264 // - ListBox()
6265 //-------------------------------------------------------------------------
6266
6267 // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
6268 // Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height).
BeginListBox(const char * label,const ImVec2 & size_arg)6269 bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
6270 {
6271 ImGuiContext& g = *GImGui;
6272 ImGuiWindow* window = GetCurrentWindow();
6273 if (window->SkipItems)
6274 return false;
6275
6276 const ImGuiStyle& style = g.Style;
6277 const ImGuiID id = GetID(label);
6278 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6279
6280 // Size default to hold ~7.25 items.
6281 // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
6282 ImVec2 size = ImFloor(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
6283 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
6284 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
6285 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
6286 g.NextItemData.ClearFlags();
6287
6288 if (!IsRectVisible(bb.Min, bb.Max))
6289 {
6290 ItemSize(bb.GetSize(), style.FramePadding.y);
6291 ItemAdd(bb, 0, &frame_bb);
6292 return false;
6293 }
6294
6295 // FIXME-OPT: We could omit the BeginGroup() if label_size.x but would need to omit the EndGroup() as well.
6296 BeginGroup();
6297 if (label_size.x > 0.0f)
6298 {
6299 ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
6300 RenderText(label_pos, label);
6301 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
6302 }
6303
6304 BeginChildFrame(id, frame_bb.GetSize());
6305 return true;
6306 }
6307
6308 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
6309 // OBSOLETED in 1.81 (from February 2021)
ListBoxHeader(const char * label,int items_count,int height_in_items)6310 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
6311 {
6312 // If height_in_items == -1, default height is maximum 7.
6313 ImGuiContext& g = *GImGui;
6314 float height_in_items_f = (height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f;
6315 ImVec2 size;
6316 size.x = 0.0f;
6317 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f;
6318 return BeginListBox(label, size);
6319 }
6320 #endif
6321
EndListBox()6322 void ImGui::EndListBox()
6323 {
6324 ImGuiContext& g = *GImGui;
6325 ImGuiWindow* window = g.CurrentWindow;
6326 IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
6327 IM_UNUSED(window);
6328
6329 EndChildFrame();
6330 EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
6331 }
6332
ListBox(const char * label,int * current_item,const char * const items[],int items_count,int height_items)6333 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
6334 {
6335 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
6336 return value_changed;
6337 }
6338
6339 // This is merely a helper around BeginListBox(), EndListBox().
6340 // Considering using those directly to submit custom data or store selection differently.
ListBox(const char * label,int * current_item,bool (* items_getter)(void *,int,const char **),void * data,int items_count,int height_in_items)6341 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)
6342 {
6343 ImGuiContext& g = *GImGui;
6344
6345 // Calculate size from "height_in_items"
6346 if (height_in_items < 0)
6347 height_in_items = ImMin(items_count, 7);
6348 float height_in_items_f = height_in_items + 0.25f;
6349 ImVec2 size(0.0f, ImFloor(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
6350
6351 if (!BeginListBox(label, size))
6352 return false;
6353
6354 // Assume all items have even height (= 1 line of text). If you need items of different height,
6355 // you can create a custom version of ListBox() in your code without using the clipper.
6356 bool value_changed = false;
6357 ImGuiListClipper clipper;
6358 clipper.Begin(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
6359 while (clipper.Step())
6360 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
6361 {
6362 const char* item_text;
6363 if (!items_getter(data, i, &item_text))
6364 item_text = "*Unknown item*";
6365
6366 PushID(i);
6367 const bool item_selected = (i == *current_item);
6368 if (Selectable(item_text, item_selected))
6369 {
6370 *current_item = i;
6371 value_changed = true;
6372 }
6373 if (item_selected)
6374 SetItemDefaultFocus();
6375 PopID();
6376 }
6377 EndListBox();
6378
6379 if (value_changed)
6380 MarkItemEdited(g.LastItemData.ID);
6381
6382 return value_changed;
6383 }
6384
6385 //-------------------------------------------------------------------------
6386 // [SECTION] Widgets: PlotLines, PlotHistogram
6387 //-------------------------------------------------------------------------
6388 // - PlotEx() [Internal]
6389 // - PlotLines()
6390 // - PlotHistogram()
6391 //-------------------------------------------------------------------------
6392 // Plot/Graph widgets are not very good.
6393 // Consider writing your own, or using a third-party one, see:
6394 // - ImPlot https://github.com/epezent/implot
6395 // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
6396 //-------------------------------------------------------------------------
6397
PlotEx(ImGuiPlotType plot_type,const char * label,float (* values_getter)(void * data,int idx),void * data,int values_count,int values_offset,const char * overlay_text,float scale_min,float scale_max,ImVec2 frame_size)6398 int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size)
6399 {
6400 ImGuiContext& g = *GImGui;
6401 ImGuiWindow* window = GetCurrentWindow();
6402 if (window->SkipItems)
6403 return -1;
6404
6405 const ImGuiStyle& style = g.Style;
6406 const ImGuiID id = window->GetID(label);
6407
6408 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6409 if (frame_size.x == 0.0f)
6410 frame_size.x = CalcItemWidth();
6411 if (frame_size.y == 0.0f)
6412 frame_size.y = label_size.y + (style.FramePadding.y * 2);
6413
6414 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
6415 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
6416 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));
6417 ItemSize(total_bb, style.FramePadding.y);
6418 if (!ItemAdd(total_bb, 0, &frame_bb))
6419 return -1;
6420 const bool hovered = ItemHoverable(frame_bb, id);
6421
6422 // Determine scale from values if not specified
6423 if (scale_min == FLT_MAX || scale_max == FLT_MAX)
6424 {
6425 float v_min = FLT_MAX;
6426 float v_max = -FLT_MAX;
6427 for (int i = 0; i < values_count; i++)
6428 {
6429 const float v = values_getter(data, i);
6430 if (v != v) // Ignore NaN values
6431 continue;
6432 v_min = ImMin(v_min, v);
6433 v_max = ImMax(v_max, v);
6434 }
6435 if (scale_min == FLT_MAX)
6436 scale_min = v_min;
6437 if (scale_max == FLT_MAX)
6438 scale_max = v_max;
6439 }
6440
6441 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
6442
6443 const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
6444 int idx_hovered = -1;
6445 if (values_count >= values_count_min)
6446 {
6447 int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
6448 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
6449
6450 // Tooltip on hover
6451 if (hovered && inner_bb.Contains(g.IO.MousePos))
6452 {
6453 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
6454 const int v_idx = (int)(t * item_count);
6455 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
6456
6457 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
6458 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
6459 if (plot_type == ImGuiPlotType_Lines)
6460 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
6461 else if (plot_type == ImGuiPlotType_Histogram)
6462 SetTooltip("%d: %8.4g", v_idx, v0);
6463 idx_hovered = v_idx;
6464 }
6465
6466 const float t_step = 1.0f / (float)res_w;
6467 const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
6468
6469 float v0 = values_getter(data, (0 + values_offset) % values_count);
6470 float t0 = 0.0f;
6471 ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
6472 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
6473
6474 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
6475 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
6476
6477 for (int n = 0; n < res_w; n++)
6478 {
6479 const float t1 = t0 + t_step;
6480 const int v1_idx = (int)(t0 * item_count + 0.5f);
6481 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
6482 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
6483 const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
6484
6485 // 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.
6486 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
6487 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
6488 if (plot_type == ImGuiPlotType_Lines)
6489 {
6490 window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
6491 }
6492 else if (plot_type == ImGuiPlotType_Histogram)
6493 {
6494 if (pos1.x >= pos0.x + 2.0f)
6495 pos1.x -= 1.0f;
6496 window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
6497 }
6498
6499 t0 = t1;
6500 tp0 = tp1;
6501 }
6502 }
6503
6504 // Text overlay
6505 if (overlay_text)
6506 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));
6507
6508 if (label_size.x > 0.0f)
6509 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
6510
6511 // Return hovered index or -1 if none are hovered.
6512 // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().
6513 return idx_hovered;
6514 }
6515
6516 struct ImGuiPlotArrayGetterData
6517 {
6518 const float* Values;
6519 int Stride;
6520
ImGuiPlotArrayGetterDataImGuiPlotArrayGetterData6521 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
6522 };
6523
Plot_ArrayGetter(void * data,int idx)6524 static float Plot_ArrayGetter(void* data, int idx)
6525 {
6526 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
6527 const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
6528 return v;
6529 }
6530
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)6531 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)
6532 {
6533 ImGuiPlotArrayGetterData data(values, stride);
6534 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6535 }
6536
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)6537 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)
6538 {
6539 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6540 }
6541
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)6542 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)
6543 {
6544 ImGuiPlotArrayGetterData data(values, stride);
6545 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6546 }
6547
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)6548 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)
6549 {
6550 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
6551 }
6552
6553 //-------------------------------------------------------------------------
6554 // [SECTION] Widgets: Value helpers
6555 // Those is not very useful, legacy API.
6556 //-------------------------------------------------------------------------
6557 // - Value()
6558 //-------------------------------------------------------------------------
6559
Value(const char * prefix,bool b)6560 void ImGui::Value(const char* prefix, bool b)
6561 {
6562 Text("%s: %s", prefix, (b ? "true" : "false"));
6563 }
6564
Value(const char * prefix,int v)6565 void ImGui::Value(const char* prefix, int v)
6566 {
6567 Text("%s: %d", prefix, v);
6568 }
6569
Value(const char * prefix,unsigned int v)6570 void ImGui::Value(const char* prefix, unsigned int v)
6571 {
6572 Text("%s: %d", prefix, v);
6573 }
6574
Value(const char * prefix,float v,const char * float_format)6575 void ImGui::Value(const char* prefix, float v, const char* float_format)
6576 {
6577 if (float_format)
6578 {
6579 char fmt[64];
6580 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
6581 Text(fmt, prefix, v);
6582 }
6583 else
6584 {
6585 Text("%s: %.3f", prefix, v);
6586 }
6587 }
6588
6589 //-------------------------------------------------------------------------
6590 // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
6591 //-------------------------------------------------------------------------
6592 // - ImGuiMenuColumns [Internal]
6593 // - BeginMenuBar()
6594 // - EndMenuBar()
6595 // - BeginMainMenuBar()
6596 // - EndMainMenuBar()
6597 // - BeginMenu()
6598 // - EndMenu()
6599 // - MenuItemEx() [Internal]
6600 // - MenuItem()
6601 //-------------------------------------------------------------------------
6602
6603 // Helpers for internal use
Update(float spacing,bool window_reappearing)6604 void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
6605 {
6606 if (window_reappearing)
6607 memset(Widths, 0, sizeof(Widths));
6608 Spacing = (ImU16)spacing;
6609 CalcNextTotalWidth(true);
6610 memset(Widths, 0, sizeof(Widths));
6611 TotalWidth = NextTotalWidth;
6612 NextTotalWidth = 0;
6613 }
6614
CalcNextTotalWidth(bool update_offsets)6615 void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
6616 {
6617 ImU16 offset = 0;
6618 bool want_spacing = false;
6619 for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
6620 {
6621 ImU16 width = Widths[i];
6622 if (want_spacing && width > 0)
6623 offset += Spacing;
6624 want_spacing |= (width > 0);
6625 if (update_offsets)
6626 {
6627 if (i == 1) { OffsetLabel = offset; }
6628 if (i == 2) { OffsetShortcut = offset; }
6629 if (i == 3) { OffsetMark = offset; }
6630 }
6631 offset += width;
6632 }
6633 NextTotalWidth = offset;
6634 }
6635
DeclColumns(float w_icon,float w_label,float w_shortcut,float w_mark)6636 float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
6637 {
6638 Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
6639 Widths[1] = ImMax(Widths[1], (ImU16)w_label);
6640 Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
6641 Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
6642 CalcNextTotalWidth(false);
6643 return (float)ImMax(TotalWidth, NextTotalWidth);
6644 }
6645
6646 // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
6647 // Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
6648 // Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.
6649 // Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
BeginMenuBar()6650 bool ImGui::BeginMenuBar()
6651 {
6652 ImGuiWindow* window = GetCurrentWindow();
6653 if (window->SkipItems)
6654 return false;
6655 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
6656 return false;
6657
6658 IM_ASSERT(!window->DC.MenuBarAppending);
6659 BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
6660 PushID("##menubar");
6661
6662 // 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.
6663 // 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.
6664 ImRect bar_rect = window->MenuBarRect();
6665 ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y));
6666 clip_rect.ClipWith(window->OuterRectClipped);
6667 PushClipRect(clip_rect.Min, clip_rect.Max, false);
6668
6669 // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).
6670 window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
6671 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
6672 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
6673 window->DC.MenuBarAppending = true;
6674 AlignTextToFramePadding();
6675 return true;
6676 }
6677
EndMenuBar()6678 void ImGui::EndMenuBar()
6679 {
6680 ImGuiWindow* window = GetCurrentWindow();
6681 if (window->SkipItems)
6682 return;
6683 ImGuiContext& g = *GImGui;
6684
6685 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
6686 if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
6687 {
6688 ImGuiWindow* nav_earliest_child = g.NavWindow;
6689 while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
6690 nav_earliest_child = nav_earliest_child->ParentWindow;
6691 if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
6692 {
6693 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
6694 // 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)
6695 const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
6696 IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check
6697 FocusWindow(window);
6698 SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
6699 g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
6700 g.NavDisableMouseHover = g.NavMousePosDirty = true;
6701 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
6702 NavMoveRequestCancel();
6703 }
6704 }
6705
6706 IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
6707 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
6708 IM_ASSERT(window->DC.MenuBarAppending);
6709 PopClipRect();
6710 PopID();
6711 window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
6712 g.GroupStack.back().EmitItem = false;
6713 EndGroup(); // Restore position on layer 0
6714 window->DC.LayoutType = ImGuiLayoutType_Vertical;
6715 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
6716 window->DC.MenuBarAppending = false;
6717 }
6718
6719 // Important: calling order matters!
6720 // FIXME: Somehow overlapping with docking tech.
6721 // FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
BeginViewportSideBar(const char * name,ImGuiViewport * viewport_p,ImGuiDir dir,float axis_size,ImGuiWindowFlags window_flags)6722 bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
6723 {
6724 IM_ASSERT(dir != ImGuiDir_None);
6725
6726 ImGuiWindow* bar_window = FindWindowByName(name);
6727 ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
6728 if (bar_window == NULL || bar_window->BeginCount == 0)
6729 {
6730 // Calculate and set window size/position
6731 ImRect avail_rect = viewport->GetBuildWorkRect();
6732 ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
6733 ImVec2 pos = avail_rect.Min;
6734 if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
6735 pos[axis] = avail_rect.Max[axis] - axis_size;
6736 ImVec2 size = avail_rect.GetSize();
6737 size[axis] = axis_size;
6738 SetNextWindowPos(pos);
6739 SetNextWindowSize(size);
6740
6741 // Report our size into work area (for next frame) using actual window size
6742 if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
6743 viewport->BuildWorkOffsetMin[axis] += axis_size;
6744 else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
6745 viewport->BuildWorkOffsetMax[axis] -= axis_size;
6746 }
6747
6748 window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
6749 SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
6750 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
6751 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
6752 bool is_open = Begin(name, NULL, window_flags);
6753 PopStyleVar(2);
6754
6755 return is_open;
6756 }
6757
BeginMainMenuBar()6758 bool ImGui::BeginMainMenuBar()
6759 {
6760 ImGuiContext& g = *GImGui;
6761 ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
6762
6763 // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
6764 SetCurrentViewport(NULL, viewport);
6765
6766 // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
6767 // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
6768 // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
6769 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
6770 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
6771 float height = GetFrameHeight();
6772 bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
6773 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
6774
6775 if (is_open)
6776 BeginMenuBar();
6777 else
6778 End();
6779 return is_open;
6780 }
6781
EndMainMenuBar()6782 void ImGui::EndMainMenuBar()
6783 {
6784 EndMenuBar();
6785
6786 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
6787 // FIXME: With this strategy we won't be able to restore a NULL focus.
6788 ImGuiContext& g = *GImGui;
6789 if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
6790 FocusTopMostWindowUnderOne(g.NavWindow, NULL);
6791
6792 End();
6793 }
6794
BeginMenu(const char * label,bool enabled)6795 bool ImGui::BeginMenu(const char* label, bool enabled)
6796 {
6797 ImGuiWindow* window = GetCurrentWindow();
6798 if (window->SkipItems)
6799 return false;
6800
6801 ImGuiContext& g = *GImGui;
6802 const ImGuiStyle& style = g.Style;
6803 const ImGuiID id = window->GetID(label);
6804 bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
6805
6806 // 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)
6807 ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
6808 if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu))
6809 flags |= ImGuiWindowFlags_ChildWindow;
6810
6811 // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
6812 // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame.
6813 // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
6814 if (g.MenusIdSubmittedThisFrame.contains(id))
6815 {
6816 if (menu_is_open)
6817 menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
6818 else
6819 g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values
6820 return menu_is_open;
6821 }
6822
6823 // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
6824 g.MenusIdSubmittedThisFrame.push_back(id);
6825
6826 ImVec2 label_size = CalcTextSize(label, NULL, true);
6827 bool pressed;
6828 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
6829 ImGuiWindow* backed_nav_window = g.NavWindow;
6830 if (menuset_is_open)
6831 g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
6832
6833 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
6834 // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
6835 // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
6836 ImVec2 popup_pos, pos = window->DC.CursorPos;
6837 PushID(label);
6838 if (!enabled)
6839 PushDisabled();
6840 const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
6841 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
6842 {
6843 // Menu inside an horizontal menu bar
6844 // Selectable extend their highlight by half ItemSpacing in each direction.
6845 // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
6846 popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
6847 window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
6848 PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
6849 float w = label_size.x;
6850 ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
6851 pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups, ImVec2(w, 0.0f));
6852 RenderText(text_pos, label);
6853 PopStyleVar();
6854 window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
6855 }
6856 else
6857 {
6858 // Menu inside a menu
6859 // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
6860 // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
6861 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
6862 float icon_w = 0.0f; // FIXME: This not currently exposed for BeginMenu() however you can call window->DC.MenuColumns.DeclColumns(w, 0, 0, 0) yourself
6863 float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
6864 float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
6865 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
6866 ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
6867 pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
6868 RenderText(text_pos, label);
6869 RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
6870 }
6871 if (!enabled)
6872 PopDisabled();
6873
6874 const bool hovered = (g.HoveredId == id) && enabled;
6875 if (menuset_is_open)
6876 g.NavWindow = backed_nav_window;
6877
6878 bool want_open = false;
6879 bool want_close = false;
6880 if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
6881 {
6882 // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
6883 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
6884 bool moving_toward_other_child_menu = false;
6885
6886 ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL;
6887 if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar))
6888 {
6889 // FIXME-DPI: Values should be derived from a master "scale" factor.
6890 ImRect next_window_rect = child_menu_window->Rect();
6891 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
6892 ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
6893 ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
6894 float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
6895 ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
6896 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?
6897 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
6898 moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
6899 //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
6900 }
6901
6902 // FIXME: Hovering a disabled BeginMenu or MenuItem won't close us
6903 if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu)
6904 want_close = true;
6905
6906 if (!menu_is_open && hovered && pressed) // Click to open
6907 want_open = true;
6908 else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open
6909 want_open = true;
6910
6911 if (g.NavActivateId == id)
6912 {
6913 want_close = menu_is_open;
6914 want_open = !menu_is_open;
6915 }
6916 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
6917 {
6918 want_open = true;
6919 NavMoveRequestCancel();
6920 }
6921 }
6922 else
6923 {
6924 // Menu bar
6925 if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
6926 {
6927 want_close = true;
6928 want_open = menu_is_open = false;
6929 }
6930 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
6931 {
6932 want_open = true;
6933 }
6934 else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
6935 {
6936 want_open = true;
6937 NavMoveRequestCancel();
6938 }
6939 }
6940
6941 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.. }'
6942 want_close = true;
6943 if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
6944 ClosePopupToLevel(g.BeginPopupStack.Size, true);
6945
6946 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
6947 PopID();
6948
6949 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
6950 {
6951 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
6952 OpenPopup(label);
6953 return false;
6954 }
6955
6956 menu_is_open |= want_open;
6957 if (want_open)
6958 OpenPopup(label);
6959
6960 if (menu_is_open)
6961 {
6962 SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
6963 menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
6964 }
6965 else
6966 {
6967 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
6968 }
6969
6970 return menu_is_open;
6971 }
6972
EndMenu()6973 void ImGui::EndMenu()
6974 {
6975 // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
6976 // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
6977 // 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.
6978 ImGuiContext& g = *GImGui;
6979 ImGuiWindow* window = g.CurrentWindow;
6980 if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
6981 {
6982 ClosePopupToLevel(g.BeginPopupStack.Size, true);
6983 NavMoveRequestCancel();
6984 }
6985
6986 EndPopup();
6987 }
6988
MenuItemEx(const char * label,const char * icon,const char * shortcut,bool selected,bool enabled)6989 bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
6990 {
6991 ImGuiWindow* window = GetCurrentWindow();
6992 if (window->SkipItems)
6993 return false;
6994
6995 ImGuiContext& g = *GImGui;
6996 ImGuiStyle& style = g.Style;
6997 ImVec2 pos = window->DC.CursorPos;
6998 ImVec2 label_size = CalcTextSize(label, NULL, true);
6999
7000 // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
7001 // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
7002 bool pressed;
7003 PushID(label);
7004 if (!enabled)
7005 PushDisabled(true);
7006 const ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover;
7007 const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
7008 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
7009 {
7010 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
7011 // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
7012 float w = label_size.x;
7013 window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
7014 PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
7015 pressed = Selectable("", selected, flags, ImVec2(w, 0.0f));
7016 PopStyleVar();
7017 RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
7018 window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
7019 }
7020 else
7021 {
7022 // Menu item inside a vertical menu
7023 // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
7024 // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
7025 float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
7026 float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
7027 float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
7028 float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
7029 float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
7030 pressed = Selectable("", false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
7031 RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
7032 if (icon_w > 0.0f)
7033 RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
7034 if (shortcut_w > 0.0f)
7035 {
7036 PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
7037 RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
7038 PopStyleColor();
7039 }
7040 if (selected)
7041 RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
7042 }
7043 IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
7044 if (!enabled)
7045 PopDisabled();
7046 PopID();
7047
7048 return pressed;
7049 }
7050
MenuItem(const char * label,const char * shortcut,bool selected,bool enabled)7051 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
7052 {
7053 return MenuItemEx(label, NULL, shortcut, selected, enabled);
7054 }
7055
MenuItem(const char * label,const char * shortcut,bool * p_selected,bool enabled)7056 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
7057 {
7058 if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
7059 {
7060 if (p_selected)
7061 *p_selected = !*p_selected;
7062 return true;
7063 }
7064 return false;
7065 }
7066
7067 //-------------------------------------------------------------------------
7068 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
7069 //-------------------------------------------------------------------------
7070 // - BeginTabBar()
7071 // - BeginTabBarEx() [Internal]
7072 // - EndTabBar()
7073 // - TabBarLayout() [Internal]
7074 // - TabBarCalcTabID() [Internal]
7075 // - TabBarCalcMaxTabWidth() [Internal]
7076 // - TabBarFindTabById() [Internal]
7077 // - TabBarAddTab() [Internal]
7078 // - TabBarRemoveTab() [Internal]
7079 // - TabBarCloseTab() [Internal]
7080 // - TabBarScrollClamp() [Internal]
7081 // - TabBarScrollToTab() [Internal]
7082 // - TabBarQueueChangeTabOrder() [Internal]
7083 // - TabBarScrollingButtons() [Internal]
7084 // - TabBarTabListPopupButton() [Internal]
7085 //-------------------------------------------------------------------------
7086
7087 struct ImGuiTabBarSection
7088 {
7089 int TabCount; // Number of tabs in this section.
7090 float Width; // Sum of width of tabs in this section (after shrinking down)
7091 float Spacing; // Horizontal spacing at the end of the section.
7092
ImGuiTabBarSectionImGuiTabBarSection7093 ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
7094 };
7095
7096 namespace ImGui
7097 {
7098 static void TabBarLayout(ImGuiTabBar* tab_bar);
7099 static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
7100 static float TabBarCalcMaxTabWidth();
7101 static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
7102 static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
7103 static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar);
7104 static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
7105 }
7106
ImGuiTabBar()7107 ImGuiTabBar::ImGuiTabBar()
7108 {
7109 memset(this, 0, sizeof(*this));
7110 CurrFrameVisible = PrevFrameVisible = -1;
7111 LastTabItemIdx = -1;
7112 }
7113
TabItemGetSectionIdx(const ImGuiTabItem * tab)7114 static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
7115 {
7116 return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
7117 }
7118
TabItemComparerBySection(const void * lhs,const void * rhs)7119 static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
7120 {
7121 const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
7122 const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
7123 const int a_section = TabItemGetSectionIdx(a);
7124 const int b_section = TabItemGetSectionIdx(b);
7125 if (a_section != b_section)
7126 return a_section - b_section;
7127 return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
7128 }
7129
TabItemComparerByBeginOrder(const void * lhs,const void * rhs)7130 static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
7131 {
7132 const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
7133 const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
7134 return (int)(a->BeginOrder - b->BeginOrder);
7135 }
7136
GetTabBarFromTabBarRef(const ImGuiPtrOrIndex & ref)7137 static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
7138 {
7139 ImGuiContext& g = *GImGui;
7140 return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
7141 }
7142
GetTabBarRefFromTabBar(ImGuiTabBar * tab_bar)7143 static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
7144 {
7145 ImGuiContext& g = *GImGui;
7146 if (g.TabBars.Contains(tab_bar))
7147 return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
7148 return ImGuiPtrOrIndex(tab_bar);
7149 }
7150
BeginTabBar(const char * str_id,ImGuiTabBarFlags flags)7151 bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
7152 {
7153 ImGuiContext& g = *GImGui;
7154 ImGuiWindow* window = g.CurrentWindow;
7155 if (window->SkipItems)
7156 return false;
7157
7158 ImGuiID id = window->GetID(str_id);
7159 ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
7160 ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
7161 tab_bar->ID = id;
7162 return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused, NULL);
7163 }
7164
BeginTabBarEx(ImGuiTabBar * tab_bar,const ImRect & tab_bar_bb,ImGuiTabBarFlags flags,ImGuiDockNode * dock_node)7165 bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags, ImGuiDockNode* dock_node)
7166 {
7167 ImGuiContext& g = *GImGui;
7168 ImGuiWindow* window = g.CurrentWindow;
7169 if (window->SkipItems)
7170 return false;
7171
7172 if ((flags & ImGuiTabBarFlags_DockNode) == 0)
7173 PushOverrideID(tab_bar->ID);
7174
7175 // Add to stack
7176 g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
7177 g.CurrentTabBar = tab_bar;
7178
7179 // Append with multiple BeginTabBar()/EndTabBar() pairs.
7180 tab_bar->BackupCursorPos = window->DC.CursorPos;
7181 if (tab_bar->CurrFrameVisible == g.FrameCount)
7182 {
7183 window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
7184 tab_bar->BeginCount++;
7185 return true;
7186 }
7187
7188 // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
7189 if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
7190 if (tab_bar->Tabs.Size > 1 && (flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
7191 ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
7192 tab_bar->TabsAddedNew = false;
7193
7194 // Flags
7195 if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
7196 flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
7197
7198 tab_bar->Flags = flags;
7199 tab_bar->BarRect = tab_bar_bb;
7200 tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
7201 tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
7202 tab_bar->CurrFrameVisible = g.FrameCount;
7203 tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
7204 tab_bar->CurrTabsContentsHeight = 0.0f;
7205 tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
7206 tab_bar->FramePadding = g.Style.FramePadding;
7207 tab_bar->TabsActiveCount = 0;
7208 tab_bar->BeginCount = 1;
7209
7210 // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
7211 window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
7212
7213 // Draw separator
7214 const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
7215 const float y = tab_bar->BarRect.Max.y - 1.0f;
7216 if (dock_node != NULL)
7217 {
7218 const float separator_min_x = dock_node->Pos.x + window->WindowBorderSize;
7219 const float separator_max_x = dock_node->Pos.x + dock_node->Size.x - window->WindowBorderSize;
7220 window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
7221 }
7222 else
7223 {
7224 const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f);
7225 const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f);
7226 window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
7227 }
7228 return true;
7229 }
7230
EndTabBar()7231 void ImGui::EndTabBar()
7232 {
7233 ImGuiContext& g = *GImGui;
7234 ImGuiWindow* window = g.CurrentWindow;
7235 if (window->SkipItems)
7236 return;
7237
7238 ImGuiTabBar* tab_bar = g.CurrentTabBar;
7239 if (tab_bar == NULL)
7240 {
7241 IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
7242 return;
7243 }
7244
7245 // Fallback in case no TabItem have been submitted
7246 if (tab_bar->WantLayout)
7247 TabBarLayout(tab_bar);
7248
7249 // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
7250 const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
7251 if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
7252 {
7253 tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
7254 window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
7255 }
7256 else
7257 {
7258 window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
7259 }
7260 if (tab_bar->BeginCount > 1)
7261 window->DC.CursorPos = tab_bar->BackupCursorPos;
7262
7263 if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
7264 PopID();
7265
7266 g.CurrentTabBarStack.pop_back();
7267 g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
7268 }
7269
7270 // This is called only once a frame before by the first call to ItemTab()
7271 // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
TabBarLayout(ImGuiTabBar * tab_bar)7272 static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
7273 {
7274 ImGuiContext& g = *GImGui;
7275 tab_bar->WantLayout = false;
7276
7277 // Garbage collect by compacting list
7278 // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
7279 int tab_dst_n = 0;
7280 bool need_sort_by_section = false;
7281 ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
7282 for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
7283 {
7284 ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
7285 if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
7286 {
7287 // Remove tab
7288 if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
7289 if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
7290 if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
7291 continue;
7292 }
7293 if (tab_dst_n != tab_src_n)
7294 tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
7295
7296 tab = &tab_bar->Tabs[tab_dst_n];
7297 tab->IndexDuringLayout = (ImS16)tab_dst_n;
7298
7299 // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
7300 int curr_tab_section_n = TabItemGetSectionIdx(tab);
7301 if (tab_dst_n > 0)
7302 {
7303 ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
7304 int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
7305 if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
7306 need_sort_by_section = true;
7307 if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
7308 need_sort_by_section = true;
7309 }
7310
7311 sections[curr_tab_section_n].TabCount++;
7312 tab_dst_n++;
7313 }
7314 if (tab_bar->Tabs.Size != tab_dst_n)
7315 tab_bar->Tabs.resize(tab_dst_n);
7316
7317 if (need_sort_by_section)
7318 ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
7319
7320 // Calculate spacing between sections
7321 sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
7322 sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
7323
7324 // Setup next selected tab
7325 ImGuiID scroll_to_tab_id = 0;
7326 if (tab_bar->NextSelectedTabId)
7327 {
7328 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
7329 tab_bar->NextSelectedTabId = 0;
7330 scroll_to_tab_id = tab_bar->SelectedTabId;
7331 }
7332
7333 // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
7334 if (tab_bar->ReorderRequestTabId != 0)
7335 {
7336 if (TabBarProcessReorder(tab_bar))
7337 if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
7338 scroll_to_tab_id = tab_bar->ReorderRequestTabId;
7339 tab_bar->ReorderRequestTabId = 0;
7340 }
7341
7342 // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
7343 const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
7344 if (tab_list_popup_button)
7345 if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
7346 scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
7347
7348 // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
7349 // (whereas our tabs are stored as: leading, central, trailing)
7350 int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
7351 g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
7352
7353 // Compute ideal tabs widths + store them into shrink buffer
7354 ImGuiTabItem* most_recently_selected_tab = NULL;
7355 int curr_section_n = -1;
7356 bool found_selected_tab_id = false;
7357 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
7358 {
7359 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
7360 IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
7361
7362 if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
7363 most_recently_selected_tab = tab;
7364 if (tab->ID == tab_bar->SelectedTabId)
7365 found_selected_tab_id = true;
7366 if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
7367 scroll_to_tab_id = tab->ID;
7368
7369 // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
7370 // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
7371 // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
7372 const char* tab_name = tab_bar->GetTabName(tab);
7373 const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0;
7374 tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
7375
7376 int section_n = TabItemGetSectionIdx(tab);
7377 ImGuiTabBarSection* section = §ions[section_n];
7378 section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
7379 curr_section_n = section_n;
7380
7381 // Store data so we can build an array sorted by width if we need to shrink tabs down
7382 IM_MSVC_WARNING_SUPPRESS(6385);
7383 int shrink_buffer_index = shrink_buffer_indexes[section_n]++;
7384 g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n;
7385 g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth;
7386
7387 IM_ASSERT(tab->ContentWidth > 0.0f);
7388 tab->Width = tab->ContentWidth;
7389 }
7390
7391 // Compute total ideal width (used for e.g. auto-resizing a window)
7392 tab_bar->WidthAllTabsIdeal = 0.0f;
7393 for (int section_n = 0; section_n < 3; section_n++)
7394 tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
7395
7396 // Horizontal scrolling buttons
7397 // (note that TabBarScrollButtons() will alter BarRect.Max.x)
7398 if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
7399 if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
7400 {
7401 scroll_to_tab_id = scroll_and_select_tab->ID;
7402 if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
7403 tab_bar->SelectedTabId = scroll_to_tab_id;
7404 }
7405
7406 // Shrink widths if full tabs don't fit in their allocated space
7407 float section_0_w = sections[0].Width + sections[0].Spacing;
7408 float section_1_w = sections[1].Width + sections[1].Spacing;
7409 float section_2_w = sections[2].Width + sections[2].Spacing;
7410 bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
7411 float width_excess;
7412 if (central_section_is_visible)
7413 width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
7414 else
7415 width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
7416
7417 // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
7418 if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
7419 {
7420 int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
7421 int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
7422 ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
7423
7424 // Apply shrunk values into tabs and sections
7425 for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
7426 {
7427 ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
7428 float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width);
7429 if (shrinked_width < 0.0f)
7430 continue;
7431
7432 int section_n = TabItemGetSectionIdx(tab);
7433 sections[section_n].Width -= (tab->Width - shrinked_width);
7434 tab->Width = shrinked_width;
7435 }
7436 }
7437
7438 // Layout all active tabs
7439 int section_tab_index = 0;
7440 float tab_offset = 0.0f;
7441 tab_bar->WidthAllTabs = 0.0f;
7442 for (int section_n = 0; section_n < 3; section_n++)
7443 {
7444 ImGuiTabBarSection* section = §ions[section_n];
7445 if (section_n == 2)
7446 tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
7447
7448 for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
7449 {
7450 ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
7451 tab->Offset = tab_offset;
7452 tab->NameOffset = -1;
7453 tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
7454 }
7455 tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
7456 tab_offset += section->Spacing;
7457 section_tab_index += section->TabCount;
7458 }
7459
7460 // Clear name buffers
7461 tab_bar->TabsNames.Buf.resize(0);
7462
7463 // If we have lost the selected tab, select the next most recently active one
7464 if (found_selected_tab_id == false)
7465 tab_bar->SelectedTabId = 0;
7466 if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
7467 scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
7468
7469 // Lock in visible tab
7470 tab_bar->VisibleTabId = tab_bar->SelectedTabId;
7471 tab_bar->VisibleTabWasSubmitted = false;
7472
7473 // CTRL+TAB can override visible tab temporarily
7474 if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
7475 tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->ID;
7476
7477 // Update scrolling
7478 if (scroll_to_tab_id != 0)
7479 TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
7480 tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
7481 tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
7482 if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
7483 {
7484 // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
7485 // Teleport if we are aiming far off the visible line
7486 tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
7487 tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
7488 const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
7489 tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
7490 }
7491 else
7492 {
7493 tab_bar->ScrollingSpeed = 0.0f;
7494 }
7495 tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
7496 tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
7497
7498 // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
7499 ImGuiWindow* window = g.CurrentWindow;
7500 window->DC.CursorPos = tab_bar->BarRect.Min;
7501 ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
7502 window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
7503 }
7504
7505 // Dockable uses Name/ID in the global namespace. Non-dockable items use the ID stack.
TabBarCalcTabID(ImGuiTabBar * tab_bar,const char * label,ImGuiWindow * docked_window)7506 static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
7507 {
7508 if (docked_window != NULL)
7509 {
7510 IM_UNUSED(tab_bar);
7511 IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
7512 ImGuiID id = ImHashStr(label);
7513 KeepAliveID(id);
7514 return id;
7515 }
7516 else
7517 {
7518 ImGuiWindow* window = GImGui->CurrentWindow;
7519 return window->GetID(label);
7520 }
7521 }
7522
TabBarCalcMaxTabWidth()7523 static float ImGui::TabBarCalcMaxTabWidth()
7524 {
7525 ImGuiContext& g = *GImGui;
7526 return g.FontSize * 20.0f;
7527 }
7528
TabBarFindTabByID(ImGuiTabBar * tab_bar,ImGuiID tab_id)7529 ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
7530 {
7531 if (tab_id != 0)
7532 for (int n = 0; n < tab_bar->Tabs.Size; n++)
7533 if (tab_bar->Tabs[n].ID == tab_id)
7534 return &tab_bar->Tabs[n];
7535 return NULL;
7536 }
7537
7538 // FIXME: See references to #2304 in TODO.txt
TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar * tab_bar)7539 ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
7540 {
7541 ImGuiTabItem* most_recently_selected_tab = NULL;
7542 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
7543 {
7544 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
7545 if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
7546 if (tab->Window && tab->Window->WasActive)
7547 most_recently_selected_tab = tab;
7548 }
7549 return most_recently_selected_tab;
7550 }
7551
7552 // The purpose of this call is to register tab in advance so we can control their order at the time they appear.
7553 // Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
TabBarAddTab(ImGuiTabBar * tab_bar,ImGuiTabItemFlags tab_flags,ImGuiWindow * window)7554 void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
7555 {
7556 ImGuiContext& g = *GImGui;
7557 IM_ASSERT(TabBarFindTabByID(tab_bar, window->ID) == NULL);
7558 IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame)
7559
7560 if (!window->HasCloseButton)
7561 tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation.
7562
7563 ImGuiTabItem new_tab;
7564 new_tab.ID = window->ID;
7565 new_tab.Flags = tab_flags;
7566 new_tab.LastFrameVisible = tab_bar->CurrFrameVisible; // Required so BeginTabBar() doesn't ditch the tab
7567 if (new_tab.LastFrameVisible == -1)
7568 new_tab.LastFrameVisible = g.FrameCount - 1;
7569 new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission
7570 tab_bar->Tabs.push_back(new_tab);
7571 }
7572
7573 // The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
TabBarRemoveTab(ImGuiTabBar * tab_bar,ImGuiID tab_id)7574 void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
7575 {
7576 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
7577 tab_bar->Tabs.erase(tab);
7578 if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; }
7579 if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; }
7580 if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
7581 }
7582
7583 // Called on manual closure attempt
TabBarCloseTab(ImGuiTabBar * tab_bar,ImGuiTabItem * tab)7584 void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
7585 {
7586 IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button));
7587 if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
7588 {
7589 // This will remove a frame of lag for selecting another tab on closure.
7590 // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
7591 tab->WantClose = true;
7592 if (tab_bar->VisibleTabId == tab->ID)
7593 {
7594 tab->LastFrameVisible = -1;
7595 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
7596 }
7597 }
7598 else
7599 {
7600 // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
7601 if (tab_bar->VisibleTabId != tab->ID)
7602 tab_bar->NextSelectedTabId = tab->ID;
7603 }
7604 }
7605
TabBarScrollClamp(ImGuiTabBar * tab_bar,float scrolling)7606 static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
7607 {
7608 scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
7609 return ImMax(scrolling, 0.0f);
7610 }
7611
7612 // Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
TabBarScrollToTab(ImGuiTabBar * tab_bar,ImGuiID tab_id,ImGuiTabBarSection * sections)7613 static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
7614 {
7615 ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
7616 if (tab == NULL)
7617 return;
7618 if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
7619 return;
7620
7621 ImGuiContext& g = *GImGui;
7622 float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
7623 int order = tab_bar->GetTabOrder(tab);
7624
7625 // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
7626 // FIXME: This is all confusing.
7627 float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
7628
7629 // We make all tabs positions all relative Sections[0].Width to make code simpler
7630 float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
7631 float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
7632 tab_bar->ScrollingTargetDistToVisibility = 0.0f;
7633 if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
7634 {
7635 // Scroll to the left
7636 tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
7637 tab_bar->ScrollingTarget = tab_x1;
7638 }
7639 else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
7640 {
7641 // Scroll to the right
7642 tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
7643 tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
7644 }
7645 }
7646
TabBarQueueReorder(ImGuiTabBar * tab_bar,const ImGuiTabItem * tab,int offset)7647 void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset)
7648 {
7649 IM_ASSERT(offset != 0);
7650 IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
7651 tab_bar->ReorderRequestTabId = tab->ID;
7652 tab_bar->ReorderRequestOffset = (ImS16)offset;
7653 }
7654
TabBarQueueReorderFromMousePos(ImGuiTabBar * tab_bar,const ImGuiTabItem * src_tab,ImVec2 mouse_pos)7655 void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos)
7656 {
7657 ImGuiContext& g = *GImGui;
7658 IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
7659 if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
7660 return;
7661
7662 const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
7663 const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
7664
7665 // Count number of contiguous tabs we are crossing over
7666 const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
7667 const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
7668 int dst_idx = src_idx;
7669 for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
7670 {
7671 // Reordered tabs must share the same section
7672 const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
7673 if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
7674 break;
7675 if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
7676 break;
7677 dst_idx = i;
7678
7679 // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
7680 const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
7681 const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
7682 //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
7683 if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
7684 break;
7685 }
7686
7687 if (dst_idx != src_idx)
7688 TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
7689 }
7690
TabBarProcessReorder(ImGuiTabBar * tab_bar)7691 bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
7692 {
7693 ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
7694 if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
7695 return false;
7696
7697 //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
7698 int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset;
7699 if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
7700 return false;
7701
7702 // Reordered tabs must share the same section
7703 // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
7704 ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
7705 if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
7706 return false;
7707 if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
7708 return false;
7709
7710 ImGuiTabItem item_tmp = *tab1;
7711 ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
7712 ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
7713 const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
7714 memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
7715 *tab2 = item_tmp;
7716
7717 if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
7718 MarkIniSettingsDirty();
7719 return true;
7720 }
7721
TabBarScrollingButtons(ImGuiTabBar * tab_bar)7722 static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
7723 {
7724 ImGuiContext& g = *GImGui;
7725 ImGuiWindow* window = g.CurrentWindow;
7726
7727 const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
7728 const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
7729
7730 const ImVec2 backup_cursor_pos = window->DC.CursorPos;
7731 //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
7732
7733 int select_dir = 0;
7734 ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
7735 arrow_col.w *= 0.5f;
7736
7737 PushStyleColor(ImGuiCol_Text, arrow_col);
7738 PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
7739 const float backup_repeat_delay = g.IO.KeyRepeatDelay;
7740 const float backup_repeat_rate = g.IO.KeyRepeatRate;
7741 g.IO.KeyRepeatDelay = 0.250f;
7742 g.IO.KeyRepeatRate = 0.200f;
7743 float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
7744 window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
7745 if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
7746 select_dir = -1;
7747 window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
7748 if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
7749 select_dir = +1;
7750 PopStyleColor(2);
7751 g.IO.KeyRepeatRate = backup_repeat_rate;
7752 g.IO.KeyRepeatDelay = backup_repeat_delay;
7753
7754 ImGuiTabItem* tab_to_scroll_to = NULL;
7755 if (select_dir != 0)
7756 if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
7757 {
7758 int selected_order = tab_bar->GetTabOrder(tab_item);
7759 int target_order = selected_order + select_dir;
7760
7761 // Skip tab item buttons until another tab item is found or end is reached
7762 while (tab_to_scroll_to == NULL)
7763 {
7764 // If we are at the end of the list, still scroll to make our tab visible
7765 tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
7766
7767 // Cross through buttons
7768 // (even if first/last item is a button, return it so we can update the scroll)
7769 if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
7770 {
7771 target_order += select_dir;
7772 selected_order += select_dir;
7773 tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
7774 }
7775 }
7776 }
7777 window->DC.CursorPos = backup_cursor_pos;
7778 tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
7779
7780 return tab_to_scroll_to;
7781 }
7782
TabBarTabListPopupButton(ImGuiTabBar * tab_bar)7783 static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
7784 {
7785 ImGuiContext& g = *GImGui;
7786 ImGuiWindow* window = g.CurrentWindow;
7787
7788 // We use g.Style.FramePadding.y to match the square ArrowButton size
7789 const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
7790 const ImVec2 backup_cursor_pos = window->DC.CursorPos;
7791 window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
7792 tab_bar->BarRect.Min.x += tab_list_popup_button_width;
7793
7794 ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
7795 arrow_col.w *= 0.5f;
7796 PushStyleColor(ImGuiCol_Text, arrow_col);
7797 PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
7798 bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
7799 PopStyleColor(2);
7800
7801 ImGuiTabItem* tab_to_select = NULL;
7802 if (open)
7803 {
7804 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
7805 {
7806 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
7807 if (tab->Flags & ImGuiTabItemFlags_Button)
7808 continue;
7809
7810 const char* tab_name = tab_bar->GetTabName(tab);
7811 if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
7812 tab_to_select = tab;
7813 }
7814 EndCombo();
7815 }
7816
7817 window->DC.CursorPos = backup_cursor_pos;
7818 return tab_to_select;
7819 }
7820
7821 //-------------------------------------------------------------------------
7822 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
7823 //-------------------------------------------------------------------------
7824 // - BeginTabItem()
7825 // - EndTabItem()
7826 // - TabItemButton()
7827 // - TabItemEx() [Internal]
7828 // - SetTabItemClosed()
7829 // - TabItemCalcSize() [Internal]
7830 // - TabItemBackground() [Internal]
7831 // - TabItemLabelAndCloseButton() [Internal]
7832 //-------------------------------------------------------------------------
7833
BeginTabItem(const char * label,bool * p_open,ImGuiTabItemFlags flags)7834 bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
7835 {
7836 ImGuiContext& g = *GImGui;
7837 ImGuiWindow* window = g.CurrentWindow;
7838 if (window->SkipItems)
7839 return false;
7840
7841 ImGuiTabBar* tab_bar = g.CurrentTabBar;
7842 if (tab_bar == NULL)
7843 {
7844 IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
7845 return false;
7846 }
7847 IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
7848
7849 bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
7850 if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
7851 {
7852 ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
7853 PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
7854 }
7855 return ret;
7856 }
7857
EndTabItem()7858 void ImGui::EndTabItem()
7859 {
7860 ImGuiContext& g = *GImGui;
7861 ImGuiWindow* window = g.CurrentWindow;
7862 if (window->SkipItems)
7863 return;
7864
7865 ImGuiTabBar* tab_bar = g.CurrentTabBar;
7866 if (tab_bar == NULL)
7867 {
7868 IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
7869 return;
7870 }
7871 IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
7872 ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
7873 if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
7874 PopID();
7875 }
7876
TabItemButton(const char * label,ImGuiTabItemFlags flags)7877 bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
7878 {
7879 ImGuiContext& g = *GImGui;
7880 ImGuiWindow* window = g.CurrentWindow;
7881 if (window->SkipItems)
7882 return false;
7883
7884 ImGuiTabBar* tab_bar = g.CurrentTabBar;
7885 if (tab_bar == NULL)
7886 {
7887 IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
7888 return false;
7889 }
7890 return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
7891 }
7892
TabItemEx(ImGuiTabBar * tab_bar,const char * label,bool * p_open,ImGuiTabItemFlags flags,ImGuiWindow * docked_window)7893 bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
7894 {
7895 // Layout whole tab bar if not already done
7896 if (tab_bar->WantLayout)
7897 TabBarLayout(tab_bar);
7898
7899 ImGuiContext& g = *GImGui;
7900 ImGuiWindow* window = g.CurrentWindow;
7901 if (window->SkipItems)
7902 return false;
7903
7904 const ImGuiStyle& style = g.Style;
7905 const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
7906
7907 // If the user called us with *p_open == false, we early out and don't render.
7908 // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
7909 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
7910 if (p_open && !*p_open)
7911 {
7912 PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
7913 ItemAdd(ImRect(), id);
7914 PopItemFlag();
7915 return false;
7916 }
7917
7918 IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
7919 IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
7920
7921 // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
7922 if (flags & ImGuiTabItemFlags_NoCloseButton)
7923 p_open = NULL;
7924 else if (p_open == NULL)
7925 flags |= ImGuiTabItemFlags_NoCloseButton;
7926
7927 // Calculate tab contents size
7928 ImVec2 size = TabItemCalcSize(label, p_open != NULL);
7929
7930 // Acquire tab data
7931 ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
7932 bool tab_is_new = false;
7933 if (tab == NULL)
7934 {
7935 tab_bar->Tabs.push_back(ImGuiTabItem());
7936 tab = &tab_bar->Tabs.back();
7937 tab->ID = id;
7938 tab->Width = size.x;
7939 tab_bar->TabsAddedNew = true;
7940 tab_is_new = true;
7941 }
7942 tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
7943 tab->ContentWidth = size.x;
7944 tab->BeginOrder = tab_bar->TabsActiveCount++;
7945
7946 const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
7947 const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
7948 const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
7949 const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
7950 tab->LastFrameVisible = g.FrameCount;
7951 tab->Flags = flags;
7952 tab->Window = docked_window;
7953
7954 // Append name with zero-terminator
7955 // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar)
7956 if (tab->Window != NULL)
7957 {
7958 IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
7959 tab->NameOffset = -1;
7960 }
7961 else
7962 {
7963 IM_ASSERT(tab->Window == NULL);
7964 tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
7965 tab_bar->TabsNames.append(label, label + strlen(label) + 1); // Append name _with_ the zero-terminator.
7966 }
7967
7968 // Update selected tab
7969 if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
7970 if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
7971 if (!is_tab_button)
7972 tab_bar->NextSelectedTabId = id; // New tabs gets activated
7973 if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar
7974 if (!is_tab_button)
7975 tab_bar->NextSelectedTabId = id;
7976
7977 // Lock visibility
7978 // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
7979 bool tab_contents_visible = (tab_bar->VisibleTabId == id);
7980 if (tab_contents_visible)
7981 tab_bar->VisibleTabWasSubmitted = true;
7982
7983 // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
7984 if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
7985 if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
7986 tab_contents_visible = true;
7987
7988 // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
7989 // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
7990 if (tab_appearing && (!tab_bar_appearing || tab_is_new))
7991 {
7992 PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
7993 ItemAdd(ImRect(), id);
7994 PopItemFlag();
7995 if (is_tab_button)
7996 return false;
7997 return tab_contents_visible;
7998 }
7999
8000 if (tab_bar->SelectedTabId == id)
8001 tab->LastFrameSelected = g.FrameCount;
8002
8003 // Backup current layout position
8004 const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
8005
8006 // Layout
8007 const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
8008 size.x = tab->Width;
8009 if (is_central_section)
8010 window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
8011 else
8012 window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
8013 ImVec2 pos = window->DC.CursorPos;
8014 ImRect bb(pos, pos + size);
8015
8016 // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
8017 const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
8018 if (want_clip_rect)
8019 PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
8020
8021 ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
8022 ItemSize(bb.GetSize(), style.FramePadding.y);
8023 window->DC.CursorMaxPos = backup_cursor_max_pos;
8024
8025 if (!ItemAdd(bb, id))
8026 {
8027 if (want_clip_rect)
8028 PopClipRect();
8029 window->DC.CursorPos = backup_main_cursor_pos;
8030 return tab_contents_visible;
8031 }
8032
8033 // Click to Select a tab
8034 ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap);
8035 if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
8036 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
8037 bool hovered, held;
8038 bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
8039 if (pressed && !is_tab_button)
8040 tab_bar->NextSelectedTabId = id;
8041
8042 // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow()
8043 // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id.
8044 if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
8045 g.ActiveIdWindow = docked_window;
8046
8047 // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
8048 if (g.ActiveId != id)
8049 SetItemAllowOverlap();
8050
8051 // Drag and drop a single floating window node moves it
8052 ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
8053 const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
8054 if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
8055 {
8056 // Move
8057 StartMouseMovingWindow(docked_window);
8058 }
8059 else if (held && !tab_appearing && IsMouseDragging(0))
8060 {
8061 // Drag and drop: re-order tabs
8062 int drag_dir = 0;
8063 float drag_distance_from_edge_x = 0.0f;
8064 if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
8065 {
8066 // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
8067 if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
8068 {
8069 drag_dir = -1;
8070 drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
8071 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
8072 }
8073 else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
8074 {
8075 drag_dir = +1;
8076 drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
8077 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
8078 }
8079 }
8080
8081 // Extract a Dockable window out of it's tab bar
8082 if (docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove))
8083 {
8084 // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar
8085 bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
8086 if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
8087 {
8088 float threshold_base = g.FontSize;
8089 float threshold_x = (threshold_base * 2.2f);
8090 float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f);
8091 //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG]
8092
8093 float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y);
8094 if (distance_from_edge_y >= threshold_y)
8095 undocking_tab = true;
8096 if (drag_distance_from_edge_x > threshold_x)
8097 if ((drag_dir < 0 && tab_bar->GetTabOrder(tab) == 0) || (drag_dir > 0 && tab_bar->GetTabOrder(tab) == tab_bar->Tabs.Size - 1))
8098 undocking_tab = true;
8099 }
8100
8101 if (undocking_tab)
8102 {
8103 // Undock
8104 // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
8105 DockContextQueueUndockWindow(&g, docked_window);
8106 g.MovingWindow = docked_window;
8107 SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
8108 g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
8109 g.ActiveIdNoClearOnFocusLoss = true;
8110 SetActiveIdUsingNavAndKeys();
8111 }
8112 }
8113 }
8114
8115 #if 0
8116 if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
8117 {
8118 // Enlarge tab display when hovering
8119 bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
8120 display_draw_list = GetForegroundDrawList(window);
8121 TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
8122 }
8123 #endif
8124
8125 // Render tab shape
8126 ImDrawList* display_draw_list = window->DrawList;
8127 const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused));
8128 TabItemBackground(display_draw_list, bb, flags, tab_col);
8129 RenderNavHighlight(bb, id);
8130
8131 // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
8132 const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
8133 if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
8134 if (!is_tab_button)
8135 tab_bar->NextSelectedTabId = id;
8136
8137 if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
8138 flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
8139
8140 // Render tab label, process close button
8141 const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0;
8142 bool just_closed;
8143 bool text_clipped;
8144 TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
8145 if (just_closed && p_open != NULL)
8146 {
8147 *p_open = false;
8148 TabBarCloseTab(tab_bar, tab);
8149 }
8150
8151 // Restore main window position so user can draw there
8152 if (want_clip_rect)
8153 PopClipRect();
8154 window->DC.CursorPos = backup_main_cursor_pos;
8155
8156 // Tooltip
8157 // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
8158 // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
8159 // FIXME: This is a mess.
8160 // FIXME: We may want disabled tab to still display the tooltip?
8161 if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered())
8162 if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
8163 SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
8164
8165 IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
8166 if (is_tab_button)
8167 return pressed;
8168 return tab_contents_visible;
8169 }
8170
8171 // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
8172 // To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
8173 // Tabs closed by the close button will automatically be flagged to avoid this issue.
SetTabItemClosed(const char * label)8174 void ImGui::SetTabItemClosed(const char* label)
8175 {
8176 ImGuiContext& g = *GImGui;
8177 bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
8178 if (is_within_manual_tab_bar)
8179 {
8180 ImGuiTabBar* tab_bar = g.CurrentTabBar;
8181 ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
8182 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
8183 tab->WantClose = true; // Will be processed by next call to TabBarLayout()
8184 }
8185 else if (ImGuiWindow* window = FindWindowByName(label))
8186 {
8187 if (window->DockIsActive)
8188 if (ImGuiDockNode* node = window->DockNode)
8189 {
8190 ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
8191 TabBarRemoveTab(node->TabBar, tab_id);
8192 window->DockTabWantClose = true;
8193 }
8194 }
8195 }
8196
TabItemCalcSize(const char * label,bool has_close_button)8197 ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
8198 {
8199 ImGuiContext& g = *GImGui;
8200 ImVec2 label_size = CalcTextSize(label, NULL, true);
8201 ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
8202 if (has_close_button)
8203 size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
8204 else
8205 size.x += g.Style.FramePadding.x + 1.0f;
8206 return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
8207 }
8208
TabItemBackground(ImDrawList * draw_list,const ImRect & bb,ImGuiTabItemFlags flags,ImU32 col)8209 void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
8210 {
8211 // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
8212 ImGuiContext& g = *GImGui;
8213 const float width = bb.GetWidth();
8214 IM_UNUSED(flags);
8215 IM_ASSERT(width > 0.0f);
8216 const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
8217 const float y1 = bb.Min.y + 1.0f;
8218 const float y2 = bb.Max.y + ((flags & ImGuiTabItemFlags_Preview) ? 0.0f : -1.0f);
8219 draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
8220 draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
8221 draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
8222 draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
8223 draw_list->PathFillConvex(col);
8224 if (g.Style.TabBorderSize > 0.0f)
8225 {
8226 draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
8227 draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
8228 draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
8229 draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
8230 draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
8231 }
8232 }
8233
8234 // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
8235 // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
TabItemLabelAndCloseButton(ImDrawList * draw_list,const ImRect & bb,ImGuiTabItemFlags flags,ImVec2 frame_padding,const char * label,ImGuiID tab_id,ImGuiID close_button_id,bool is_contents_visible,bool * out_just_closed,bool * out_text_clipped)8236 void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped)
8237 {
8238 ImGuiContext& g = *GImGui;
8239 ImVec2 label_size = CalcTextSize(label, NULL, true);
8240
8241 if (out_just_closed)
8242 *out_just_closed = false;
8243 if (out_text_clipped)
8244 *out_text_clipped = false;
8245
8246 if (bb.GetWidth() <= 1.0f)
8247 return;
8248
8249 // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
8250 // But right now if you want to alter text color of tabs this is what you need to do.
8251 #if 0
8252 const float backup_alpha = g.Style.Alpha;
8253 if (!is_contents_visible)
8254 g.Style.Alpha *= 0.7f;
8255 #endif
8256
8257 // Render text label (with clipping + alpha gradient) + unsaved marker
8258 ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
8259 ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
8260
8261 // Return clipped state ignoring the close button
8262 if (out_text_clipped)
8263 {
8264 *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
8265 //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
8266 }
8267
8268 const float button_sz = g.FontSize;
8269 const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x * 2.0f - button_sz), bb.Min.y);
8270
8271 // Close Button & Unsaved Marker
8272 // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
8273 // 'hovered' will be true when hovering the Tab but NOT when hovering the close button
8274 // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
8275 // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
8276 bool close_button_pressed = false;
8277 bool close_button_visible = false;
8278 if (close_button_id != 0)
8279 if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
8280 if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
8281 close_button_visible = true;
8282 bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
8283
8284 if (close_button_visible)
8285 {
8286 ImGuiLastItemData last_item_backup = g.LastItemData;
8287 PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding);
8288 if (CloseButton(close_button_id, button_pos))
8289 close_button_pressed = true;
8290 PopStyleVar();
8291 g.LastItemData = last_item_backup;
8292
8293 // Close with middle mouse button
8294 if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
8295 close_button_pressed = true;
8296 }
8297 else if (unsaved_marker_visible)
8298 {
8299 const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz) + g.Style.FramePadding * 2.0f);
8300 RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
8301 }
8302
8303 // This is all rather complicated
8304 // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
8305 // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..
8306 float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
8307 if (close_button_visible || unsaved_marker_visible)
8308 {
8309 text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
8310 text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
8311 ellipsis_max_x = text_pixel_clip_bb.Max.x;
8312 }
8313 RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
8314
8315 #if 0
8316 if (!is_contents_visible)
8317 g.Style.Alpha = backup_alpha;
8318 #endif
8319
8320 if (out_just_closed)
8321 *out_just_closed = close_button_pressed;
8322 }
8323
8324
8325 #endif // #ifndef IMGUI_DISABLE
8326