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