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