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