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