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