openMSX
imgui_widgets.cc
Go to the documentation of this file.
1// dear imgui, v1.90.5 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: Typing-Select support
22// [SECTION] Widgets: Multi-Select support
23// [SECTION] Widgets: ListBox
24// [SECTION] Widgets: PlotLines, PlotHistogram
25// [SECTION] Widgets: Value helpers
26// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
27// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
28// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
29// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
30
31*/
32
33#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
34#define _CRT_SECURE_NO_WARNINGS
35#endif
36
37#ifndef IMGUI_DEFINE_MATH_OPERATORS
38#define IMGUI_DEFINE_MATH_OPERATORS
39#endif
40
41#include "imgui.h"
42#ifndef IMGUI_DISABLE
43#include "imgui_internal.h"
44
45// System includes
46#include <stdint.h> // intptr_t
47
48//-------------------------------------------------------------------------
49// Warnings
50//-------------------------------------------------------------------------
51
52// Visual Studio warnings
53#ifdef _MSC_VER
54#pragma warning (disable: 4127) // condition expression is constant
55#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
56#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
57#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
58#endif
59#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).
60#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
61#endif
62
63// Clang/GCC warnings with -Weverything
64#if defined(__clang__)
65#if __has_warning("-Wunknown-warning-option")
66#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!
67#endif
68#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
69#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
70#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.
71#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.
72#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
73#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0
74#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.
75#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
76#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
77#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
78#elif defined(__GNUC__)
79#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
80#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
81#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
82#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
83#endif
84
85//-------------------------------------------------------------------------
86// Data
87//-------------------------------------------------------------------------
88
89// Widgets
90static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
91static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags.
92
93// Those MIN/MAX values are not define because we need to point to them
94static const signed char IM_S8_MIN = -128;
95static const signed char IM_S8_MAX = 127;
96static const unsigned char IM_U8_MIN = 0;
97static const unsigned char IM_U8_MAX = 0xFF;
98static const signed short IM_S16_MIN = -32768;
99static const signed short IM_S16_MAX = 32767;
100static const unsigned short IM_U16_MIN = 0;
101static const unsigned short IM_U16_MAX = 0xFFFF;
102static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
103static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
104static const ImU32 IM_U32_MIN = 0;
105static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
106#ifdef LLONG_MIN
107static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
108static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
109#else
110static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
111static const ImS64 IM_S64_MAX = 9223372036854775807LL;
112#endif
113static const ImU64 IM_U64_MIN = 0;
114#ifdef ULLONG_MAX
115static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
116#else
117static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
118#endif
119
120//-------------------------------------------------------------------------
121// [SECTION] Forward Declarations
122//-------------------------------------------------------------------------
123
124// For InputTextEx()
125static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
126static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
127static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
128
129//-------------------------------------------------------------------------
130// [SECTION] Widgets: Text, etc.
131//-------------------------------------------------------------------------
132// - TextEx() [Internal]
133// - TextUnformatted()
134// - Text()
135// - TextV()
136// - TextColored()
137// - TextColoredV()
138// - TextDisabled()
139// - TextDisabledV()
140// - TextWrapped()
141// - TextWrappedV()
142// - LabelText()
143// - LabelTextV()
144// - BulletText()
145// - BulletTextV()
146//-------------------------------------------------------------------------
147
148void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
149{
150 ImGuiWindow* window = GetCurrentWindow();
151 if (window->SkipItems)
152 return;
153 ImGuiContext& g = *GImGui;
154
155 // Accept null ranges
156 if (text == text_end)
157 text = text_end = "";
158
159 // Calculate length
160 const char* text_begin = text;
161 if (text_end == NULL)
162 text_end = text + strlen(text); // FIXME-OPT
163
164 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
165 const float wrap_pos_x = window->DC.TextWrapPos;
166 const bool wrap_enabled = (wrap_pos_x >= 0.0f);
167 if (text_end - text <= 2000 || wrap_enabled)
168 {
169 // Common case
170 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
171 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
172
173 ImRect bb(text_pos, text_pos + text_size);
174 ItemSize(text_size, 0.0f);
175 if (!ItemAdd(bb, 0))
176 return;
177
178 // Render (we don't hide text after ## in this end-user function)
179 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
180 }
181 else
182 {
183 // Long text!
184 // Perform manual coarse clipping to optimize for long multi-line text
185 // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
186 // - 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.
187 // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
188 const char* line = text;
189 const float line_height = GetTextLineHeight();
190 ImVec2 text_size(0, 0);
191
192 // Lines to skip (can't skip when logging text)
193 ImVec2 pos = text_pos;
194 if (!g.LogEnabled)
195 {
196 int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
197 if (lines_skippable > 0)
198 {
199 int lines_skipped = 0;
200 while (line < text_end && lines_skipped < lines_skippable)
201 {
202 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
203 if (!line_end)
204 line_end = text_end;
205 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
206 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
207 line = line_end + 1;
208 lines_skipped++;
209 }
210 pos.y += lines_skipped * line_height;
211 }
212 }
213
214 // Lines to render
215 if (line < text_end)
216 {
217 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
218 while (line < text_end)
219 {
220 if (IsClippedEx(line_rect, 0))
221 break;
222
223 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
224 if (!line_end)
225 line_end = text_end;
226 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
227 RenderText(pos, line, line_end, false);
228 line = line_end + 1;
229 line_rect.Min.y += line_height;
230 line_rect.Max.y += line_height;
231 pos.y += line_height;
232 }
233
234 // Count remaining lines
235 int lines_skipped = 0;
236 while (line < text_end)
237 {
238 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
239 if (!line_end)
240 line_end = text_end;
241 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
242 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
243 line = line_end + 1;
244 lines_skipped++;
245 }
246 pos.y += lines_skipped * line_height;
247 }
248 text_size.y = (pos - text_pos).y;
249
250 ImRect bb(text_pos, text_pos + text_size);
251 ItemSize(text_size, 0.0f);
252 ItemAdd(bb, 0);
253 }
254}
255
256void ImGui::TextUnformatted(const char* text, const char* text_end)
257{
258 TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
259}
260
261void ImGui::Text(const char* fmt, ...)
262{
263 va_list args;
264 va_start(args, fmt);
265 TextV(fmt, args);
266 va_end(args);
267}
268
269void ImGui::TextV(const char* fmt, va_list args)
270{
271 ImGuiWindow* window = GetCurrentWindow();
272 if (window->SkipItems)
273 return;
274
275 const char* text, *text_end;
276 ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
277 TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
278}
279
280void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
281{
282 va_list args;
283 va_start(args, fmt);
284 TextColoredV(col, fmt, args);
285 va_end(args);
286}
287
288void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
289{
290 PushStyleColor(ImGuiCol_Text, col);
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 TextV(fmt, args);
308 PopStyleColor();
309}
310
311void ImGui::TextWrapped(const char* fmt, ...)
312{
313 va_list args;
314 va_start(args, fmt);
315 TextWrappedV(fmt, args);
316 va_end(args);
317}
318
319void ImGui::TextWrappedV(const char* fmt, va_list args)
320{
321 ImGuiContext& g = *GImGui;
322 const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set
323 if (need_backup)
324 PushTextWrapPos(0.0f);
325 TextV(fmt, args);
326 if (need_backup)
327 PopTextWrapPos();
328}
329
330void ImGui::LabelText(const char* label, const char* fmt, ...)
331{
332 va_list args;
333 va_start(args, fmt);
334 LabelTextV(label, fmt, args);
335 va_end(args);
336}
337
338// Add a label+text combo aligned to other label+value widgets
339void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
340{
341 ImGuiWindow* window = GetCurrentWindow();
342 if (window->SkipItems)
343 return;
344
345 ImGuiContext& g = *GImGui;
346 const ImGuiStyle& style = g.Style;
347 const float w = CalcItemWidth();
348
349 const char* value_text_begin, *value_text_end;
350 ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
351 const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
352 const ImVec2 label_size = CalcTextSize(label, NULL, true);
353
354 const ImVec2 pos = window->DC.CursorPos;
355 const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
356 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));
357 ItemSize(total_bb, style.FramePadding.y);
358 if (!ItemAdd(total_bb, 0))
359 return;
360
361 // Render
362 RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
363 if (label_size.x > 0.0f)
364 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
365}
366
367void ImGui::BulletText(const char* fmt, ...)
368{
369 va_list args;
370 va_start(args, fmt);
371 BulletTextV(fmt, args);
372 va_end(args);
373}
374
375// Text with a little bullet aligned to the typical tree node.
376void ImGui::BulletTextV(const char* fmt, va_list args)
377{
378 ImGuiWindow* window = GetCurrentWindow();
379 if (window->SkipItems)
380 return;
381
382 ImGuiContext& g = *GImGui;
383 const ImGuiStyle& style = g.Style;
384
385 const char* text_begin, *text_end;
386 ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
387 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
388 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
389 ImVec2 pos = window->DC.CursorPos;
390 pos.y += window->DC.CurrLineTextBaseOffset;
391 ItemSize(total_size, 0.0f);
392 const ImRect bb(pos, pos + total_size);
393 if (!ItemAdd(bb, 0))
394 return;
395
396 // Render
397 ImU32 text_col = GetColorU32(ImGuiCol_Text);
398 RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
399 RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
400}
401
402//-------------------------------------------------------------------------
403// [SECTION] Widgets: Main
404//-------------------------------------------------------------------------
405// - ButtonBehavior() [Internal]
406// - Button()
407// - SmallButton()
408// - InvisibleButton()
409// - ArrowButton()
410// - CloseButton() [Internal]
411// - CollapseButton() [Internal]
412// - GetWindowScrollbarID() [Internal]
413// - GetWindowScrollbarRect() [Internal]
414// - Scrollbar() [Internal]
415// - ScrollbarEx() [Internal]
416// - Image()
417// - ImageButton()
418// - Checkbox()
419// - CheckboxFlagsT() [Internal]
420// - CheckboxFlags()
421// - RadioButton()
422// - ProgressBar()
423// - Bullet()
424//-------------------------------------------------------------------------
425
426// The ButtonBehavior() function is key to many interactions and used by many/most widgets.
427// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
428// this code is a little complex.
429// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
430// See the series of events below and the corresponding state reported by dear imgui:
431//------------------------------------------------------------------------------------------------------------------------------------------------
432// with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
433// Frame N+0 (mouse is outside bb) - - - - - -
434// Frame N+1 (mouse moves inside bb) - true - - - -
435// Frame N+2 (mouse button is down) - true true true - true
436// Frame N+3 (mouse button is down) - true true - - -
437// Frame N+4 (mouse moves outside bb) - - true - - -
438// Frame N+5 (mouse moves inside bb) - true true - - -
439// Frame N+6 (mouse button is released) true true - - true -
440// Frame N+7 (mouse button is released) - true - - - -
441// Frame N+8 (mouse moves outside bb) - - - - - -
442//------------------------------------------------------------------------------------------------------------------------------------------------
443// with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
444// Frame N+2 (mouse button is down) true true true true - true
445// Frame N+3 (mouse button is down) - true true - - -
446// Frame N+6 (mouse button is released) - true - - true -
447// Frame N+7 (mouse button is released) - true - - - -
448//------------------------------------------------------------------------------------------------------------------------------------------------
449// with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
450// Frame N+2 (mouse button is down) - true - - - true
451// Frame N+3 (mouse button is down) - true - - - -
452// Frame N+6 (mouse button is released) true true - - - -
453// Frame N+7 (mouse button is released) - true - - - -
454//------------------------------------------------------------------------------------------------------------------------------------------------
455// with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
456// Frame N+0 (mouse button is down) - true - - - true
457// Frame N+1 (mouse button is down) - true - - - -
458// Frame N+2 (mouse button is released) - true - - - -
459// Frame N+3 (mouse button is released) - true - - - -
460// Frame N+4 (mouse button is down) true true true true - true
461// Frame N+5 (mouse button is down) - true true - - -
462// Frame N+6 (mouse button is released) - true - - true -
463// Frame N+7 (mouse button is released) - true - - - -
464//------------------------------------------------------------------------------------------------------------------------------------------------
465// Note that some combinations are supported,
466// - PressedOnDragDropHold can generally be associated with any flag.
467// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
468//------------------------------------------------------------------------------------------------------------------------------------------------
469// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
470// Repeat+ Repeat+ Repeat+ Repeat+
471// PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick
472//-------------------------------------------------------------------------------------------------------------------------------------------------
473// Frame N+0 (mouse button is down) - true - true
474// ... - - - -
475// Frame N + RepeatDelay true true - true
476// ... - - - -
477// Frame N + RepeatDelay + RepeatRate*N true true - true
478//-------------------------------------------------------------------------------------------------------------------------------------------------
479
480// FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.
481// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
482// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
483bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
484{
485 ImGuiContext& g = *GImGui;
486 ImGuiWindow* window = GetCurrentWindow();
487
488 // Default only reacts to left mouse button
489 if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
490 flags |= ImGuiButtonFlags_MouseButtonDefault_;
491
492 // Default behavior requires click + release inside bounding box
493 if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
494 flags |= ImGuiButtonFlags_PressedOnDefault_;
495
496 // Default behavior inherited from item flags
497 // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
498 ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags);
499 if (flags & ImGuiButtonFlags_AllowOverlap)
500 item_flags |= ImGuiItemFlags_AllowOverlap;
501 if (flags & ImGuiButtonFlags_Repeat)
502 item_flags |= ImGuiItemFlags_ButtonRepeat;
503
504 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
505 const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
506 if (flatten_hovered_children)
507 g.HoveredWindow = window;
508
509#ifdef IMGUI_ENABLE_TEST_ENGINE
510 // Alternate registration spot, for when caller didn't use ItemAdd()
511 if (id != 0 && g.LastItemData.ID != id)
512 IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
513#endif
514
515 bool pressed = false;
516 bool hovered = ItemHoverable(bb, id, item_flags);
517
518 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
519 if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
520 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
521 {
522 hovered = true;
523 SetHoveredID(id);
524 if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
525 {
526 pressed = true;
527 g.DragDropHoldJustPressedId = id;
528 FocusWindow(window);
529 }
530 }
531
532 if (flatten_hovered_children)
533 g.HoveredWindow = backup_hovered_window;
534
535 // Mouse handling
536 const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
537 if (hovered)
538 {
539 // Poll mouse buttons
540 // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
541 // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
542 int mouse_button_clicked = -1;
543 int mouse_button_released = -1;
544 for (int button = 0; button < 3; button++)
545 if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
546 {
547 if (IsMouseClicked(button, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
548 if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
549 }
550
551 // Process initial action
552 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
553 {
554 if (mouse_button_clicked != -1 && g.ActiveId != id)
555 {
556 if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
557 SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
558 if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
559 {
560 SetActiveID(id, window);
561 g.ActiveIdMouseButton = mouse_button_clicked;
562 if (!(flags & ImGuiButtonFlags_NoNavFocus))
563 SetFocusID(id, window);
564 FocusWindow(window);
565 }
566 if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
567 {
568 pressed = true;
569 if (flags & ImGuiButtonFlags_NoHoldingActiveId)
570 ClearActiveID();
571 else
572 SetActiveID(id, window); // Hold on ID
573 if (!(flags & ImGuiButtonFlags_NoNavFocus))
574 SetFocusID(id, window);
575 g.ActiveIdMouseButton = mouse_button_clicked;
576 FocusWindow(window);
577 }
578 }
579 if (flags & ImGuiButtonFlags_PressedOnRelease)
580 {
581 if (mouse_button_released != -1)
582 {
583 const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior
584 if (!has_repeated_at_least_once)
585 pressed = true;
586 if (!(flags & ImGuiButtonFlags_NoNavFocus))
587 SetFocusID(id, window);
588 ClearActiveID();
589 }
590 }
591
592 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
593 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
594 if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
595 if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, test_owner_id, ImGuiInputFlags_Repeat))
596 pressed = true;
597 }
598
599 if (pressed)
600 g.NavDisableHighlight = true;
601 }
602
603 // Gamepad/Keyboard handling
604 // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
605 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover)
606 if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
607 hovered = true;
608 if (g.NavActivateDownId == id)
609 {
610 bool nav_activated_by_code = (g.NavActivateId == id);
611 bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
612 if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
613 {
614 // Avoid pressing multiple keys from triggering excessive amount of repeat events
615 const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
616 const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
617 const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
618 const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
619 nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
620 }
621 if (nav_activated_by_code || nav_activated_by_inputs)
622 {
623 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
624 pressed = true;
625 SetActiveID(id, window);
626 g.ActiveIdSource = g.NavInputSource;
627 if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
628 SetFocusID(id, window);
629 if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
630 g.ActiveIdFromShortcut = true;
631 }
632 }
633
634 // Process while held
635 bool held = false;
636 if (g.ActiveId == id)
637 {
638 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
639 {
640 if (g.ActiveIdIsJustActivated)
641 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
642
643 const int mouse_button = g.ActiveIdMouseButton;
644 if (mouse_button == -1)
645 {
646 // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
647 ClearActiveID();
648 }
649 else if (IsMouseDown(mouse_button, test_owner_id))
650 {
651 held = true;
652 }
653 else
654 {
655 bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
656 bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
657 if ((release_in || release_anywhere) && !g.DragDropActive)
658 {
659 // Report as pressed when releasing the mouse (this is the most common path)
660 bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
661 bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
662 bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
663 if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
664 pressed = true;
665 }
666 ClearActiveID();
667 }
668 if (!(flags & ImGuiButtonFlags_NoNavFocus))
669 g.NavDisableHighlight = true;
670 }
671 else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
672 {
673 // When activated using Nav, we hold on the ActiveID until activation button is released
674 if (g.NavActivateDownId == id)
675 held = true; // hovered == true not true as we are already likely hovered on direct activation.
676 else
677 ClearActiveID();
678 }
679 if (pressed)
680 g.ActiveIdHasBeenPressedBefore = true;
681 }
682
683 // Activation highlight (this may be a remote activation)
684 if (g.NavHighlightActivatedId == id)
685 hovered = true;
686
687 if (out_hovered) *out_hovered = hovered;
688 if (out_held) *out_held = held;
689
690 return pressed;
691}
692
693bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
694{
695 ImGuiWindow* window = GetCurrentWindow();
696 if (window->SkipItems)
697 return false;
698
699 ImGuiContext& g = *GImGui;
700 const ImGuiStyle& style = g.Style;
701 const ImGuiID id = window->GetID(label);
702 const ImVec2 label_size = CalcTextSize(label, NULL, true);
703
704 ImVec2 pos = window->DC.CursorPos;
705 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)
706 pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
707 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
708
709 const ImRect bb(pos, pos + size);
710 ItemSize(size, style.FramePadding.y);
711 if (!ItemAdd(bb, id))
712 return false;
713
714 bool hovered, held;
715 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
716
717 // Render
718 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
719 RenderNavHighlight(bb, id);
720 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
721
722 if (g.LogEnabled)
723 LogSetNextTextDecoration("[", "]");
724 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
725
726 // Automatically close popups
727 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
728 // CloseCurrentPopup();
729
730 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
731 return pressed;
732}
733
734bool ImGui::Button(const char* label, const ImVec2& size_arg)
735{
736 return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
737}
738
739// Small buttons fits within text without additional vertical spacing.
740bool ImGui::SmallButton(const char* label)
741{
742 ImGuiContext& g = *GImGui;
743 float backup_padding_y = g.Style.FramePadding.y;
744 g.Style.FramePadding.y = 0.0f;
745 bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
746 g.Style.FramePadding.y = backup_padding_y;
747 return pressed;
748}
749
750// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
751// 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)
752bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
753{
754 ImGuiContext& g = *GImGui;
755 ImGuiWindow* window = GetCurrentWindow();
756 if (window->SkipItems)
757 return false;
758
759 // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
760 IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
761
762 const ImGuiID id = window->GetID(str_id);
763 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
764 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
765 ItemSize(size);
766 if (!ItemAdd(bb, id))
767 return false;
768
769 bool hovered, held;
770 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
771
772 IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
773 return pressed;
774}
775
776bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
777{
778 ImGuiContext& g = *GImGui;
779 ImGuiWindow* window = GetCurrentWindow();
780 if (window->SkipItems)
781 return false;
782
783 const ImGuiID id = window->GetID(str_id);
784 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
785 const float default_size = GetFrameHeight();
786 ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
787 if (!ItemAdd(bb, id))
788 return false;
789
790 bool hovered, held;
791 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
792
793 // Render
794 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
795 const ImU32 text_col = GetColorU32(ImGuiCol_Text);
796 RenderNavHighlight(bb, id);
797 RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
798 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);
799
800 IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
801 return pressed;
802}
803
804bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
805{
806 float sz = GetFrameHeight();
807 return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
808}
809
810// Button to close a window
811bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
812{
813 ImGuiContext& g = *GImGui;
814 ImGuiWindow* window = g.CurrentWindow;
815
816 // 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)
817 // 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?
818 const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
819 ImRect bb_interact = bb;
820 const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
821 if (area_to_visible_ratio < 1.5f)
822 bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));
823
824 // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
825 // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer).
826 bool is_clipped = !ItemAdd(bb_interact, id);
827
828 bool hovered, held;
829 bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
830 if (is_clipped)
831 return pressed;
832
833 // Render
834 // FIXME: Clarify this mess
835 ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
836 ImVec2 center = bb.GetCenter();
837 if (hovered)
838 window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col);
839
840 float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
841 ImU32 cross_col = GetColorU32(ImGuiCol_Text);
842 center -= ImVec2(0.5f, 0.5f);
843 window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
844 window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
845
846 return pressed;
847}
848
849// The Collapse button also functions as a Dock Menu button.
850bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
851{
852 ImGuiContext& g = *GImGui;
853 ImGuiWindow* window = g.CurrentWindow;
854
855 ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
856 bool is_clipped = !ItemAdd(bb, id);
857 bool hovered, held;
858 bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
859 if (is_clipped)
860 return pressed;
861
862 // Render
863 //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
864 ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
865 ImU32 text_col = GetColorU32(ImGuiCol_Text);
866 if (hovered || held)
867 window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, bg_col);
868
869 if (dock_node)
870 RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col);
871 else
872 RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
873
874 // Switch to moving the window after mouse is moved beyond the initial drag threshold
875 if (IsItemActive() && IsMouseDragging(0))
876 StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button
877
878 return pressed;
879}
880
881ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
882{
883 return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
884}
885
886// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
887ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
888{
889 const ImRect outer_rect = window->Rect();
890 const ImRect inner_rect = window->InnerRect;
891 const float border_size = window->WindowBorderSize;
892 const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
893 IM_ASSERT(scrollbar_size > 0.0f);
894 if (axis == ImGuiAxis_X)
895 return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
896 else
897 return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
898}
899
900void ImGui::Scrollbar(ImGuiAxis axis)
901{
902 ImGuiContext& g = *GImGui;
903 ImGuiWindow* window = g.CurrentWindow;
904 const ImGuiID id = GetWindowScrollbarID(window, axis);
905
906 // Calculate scrollbar bounding box
907 ImRect bb = GetWindowScrollbarRect(window, axis);
908 ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
909 if (axis == ImGuiAxis_X)
910 {
911 rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
912 if (!window->ScrollbarY)
913 rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
914 }
915 else
916 {
917 if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
918 rounding_corners |= ImDrawFlags_RoundCornersTopRight;
919 if (!window->ScrollbarX)
920 rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
921 }
922 float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
923 float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
924 ImS64 scroll = (ImS64)window->Scroll[axis];
925 ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_avail, (ImS64)size_contents, rounding_corners);
926 window->Scroll[axis] = (float)scroll;
927}
928
929// Vertical/Horizontal scrollbar
930// The entire piece of code below is rather confusing because:
931// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
932// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
933// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
934// Still, the code should probably be made simpler..
935bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_avail_v, ImS64 size_contents_v, ImDrawFlags flags)
936{
937 ImGuiContext& g = *GImGui;
938 ImGuiWindow* window = g.CurrentWindow;
939 if (window->SkipItems)
940 return false;
941
942 const float bb_frame_width = bb_frame.GetWidth();
943 const float bb_frame_height = bb_frame.GetHeight();
944 if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
945 return false;
946
947 // 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)
948 float alpha = 1.0f;
949 if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
950 alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
951 if (alpha <= 0.0f)
952 return false;
953
954 const ImGuiStyle& style = g.Style;
955 const bool allow_interaction = (alpha >= 1.0f);
956
957 ImRect bb = bb_frame;
958 bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
959
960 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
961 const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
962
963 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
964 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
965 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.
966 const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), (ImS64)1);
967 const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_avail_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
968 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
969
970 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
971 bool held = false;
972 bool hovered = false;
973 ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
974 ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
975
976 const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_avail_v);
977 float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
978 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
979 if (held && allow_interaction && grab_h_norm < 1.0f)
980 {
981 const float scrollbar_pos_v = bb.Min[axis];
982 const float mouse_pos_v = g.IO.MousePos[axis];
983
984 // Click position in scrollbar normalized space (0.0f->1.0f)
985 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
986
987 bool seek_absolute = false;
988 if (g.ActiveIdIsJustActivated)
989 {
990 // On initial click calculate the distance between mouse and the center of the grab
991 seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
992 if (seek_absolute)
993 g.ScrollbarClickDeltaToGrabCenter = 0.0f;
994 else
995 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
996 }
997
998 // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
999 // 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
1000 const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
1001 *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
1002
1003 // Update values for rendering
1004 scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1005 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
1006
1007 // Update distance to grab now that we have seeked and saturated
1008 if (seek_absolute)
1009 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
1010 }
1011
1012 // Render
1013 const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
1014 const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
1015 window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
1016 ImRect grab_rect;
1017 if (axis == ImGuiAxis_X)
1018 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);
1019 else
1020 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);
1021 window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
1022
1023 return held;
1024}
1025
1026// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
1027// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
1028void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
1029{
1030 ImGuiWindow* window = GetCurrentWindow();
1031 if (window->SkipItems)
1032 return;
1033
1034 const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f;
1035 const ImVec2 padding(border_size, border_size);
1036 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1037 ItemSize(bb);
1038 if (!ItemAdd(bb, 0))
1039 return;
1040
1041 // Render
1042 if (border_size > 0.0f)
1043 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size);
1044 window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1045}
1046
1047// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
1048// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
1049bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
1050{
1051 ImGuiContext& g = *GImGui;
1052 ImGuiWindow* window = GetCurrentWindow();
1053 if (window->SkipItems)
1054 return false;
1055
1056 const ImVec2 padding = g.Style.FramePadding;
1057 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1058 ItemSize(bb);
1059 if (!ItemAdd(bb, id))
1060 return false;
1061
1062 bool hovered, held;
1063 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1064
1065 // Render
1066 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1067 RenderNavHighlight(bb, id);
1068 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
1069 if (bg_col.w > 0.0f)
1070 window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
1071 window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1072
1073 return pressed;
1074}
1075
1076// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
1077bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
1078{
1079 ImGuiContext& g = *GImGui;
1080 ImGuiWindow* window = g.CurrentWindow;
1081 if (window->SkipItems)
1082 return false;
1083
1084 return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col);
1085}
1086
1087#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1088// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
1089// - new ImageButton() requires an explicit 'const char* str_id' Old ImageButton() used opaque imTextureId (created issue with: multiple buttons with same image, transient texture id values, opaque computation of ID)
1090// - new ImageButton() always use style.FramePadding Old ImageButton() had an override argument.
1091// If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions.
1092bool 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)
1093{
1094 ImGuiContext& g = *GImGui;
1095 ImGuiWindow* window = g.CurrentWindow;
1096 if (window->SkipItems)
1097 return false;
1098
1099 // Default to using texture ID as ID. User can still push string/integer prefixes.
1100 PushID((void*)(intptr_t)user_texture_id);
1101 const ImGuiID id = window->GetID("#image");
1102 PopID();
1103
1104 if (frame_padding >= 0)
1105 PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
1106 bool ret = ImageButtonEx(id, user_texture_id, size, uv0, uv1, bg_col, tint_col);
1107 if (frame_padding >= 0)
1108 PopStyleVar();
1109 return ret;
1110}
1111#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1112
1113bool ImGui::Checkbox(const char* label, bool* v)
1114{
1115 ImGuiWindow* window = GetCurrentWindow();
1116 if (window->SkipItems)
1117 return false;
1118
1119 ImGuiContext& g = *GImGui;
1120 const ImGuiStyle& style = g.Style;
1121 const ImGuiID id = window->GetID(label);
1122 const ImVec2 label_size = CalcTextSize(label, NULL, true);
1123
1124 const float square_sz = GetFrameHeight();
1125 const ImVec2 pos = window->DC.CursorPos;
1126 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));
1127 ItemSize(total_bb, style.FramePadding.y);
1128 if (!ItemAdd(total_bb, id))
1129 {
1130 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1131 return false;
1132 }
1133
1134 bool hovered, held;
1135 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1136 if (pressed)
1137 {
1138 *v = !(*v);
1139 MarkItemEdited(id);
1140 }
1141
1142 const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1143 RenderNavHighlight(total_bb, id);
1144 RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
1145 ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1146 bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
1147 if (mixed_value)
1148 {
1149 // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1150 // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1151 ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));
1152 window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1153 }
1154 else if (*v)
1155 {
1156 const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1157 RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1158 }
1159
1160 ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1161 if (g.LogEnabled)
1162 LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1163 if (label_size.x > 0.0f)
1164 RenderText(label_pos, label);
1165
1166 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1167 return pressed;
1168}
1169
1170template<typename T>
1171bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1172{
1173 bool all_on = (*flags & flags_value) == flags_value;
1174 bool any_on = (*flags & flags_value) != 0;
1175 bool pressed;
1176 if (!all_on && any_on)
1177 {
1178 ImGuiContext& g = *GImGui;
1179 g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
1180 pressed = Checkbox(label, &all_on);
1181 }
1182 else
1183 {
1184 pressed = Checkbox(label, &all_on);
1185
1186 }
1187 if (pressed)
1188 {
1189 if (all_on)
1190 *flags |= flags_value;
1191 else
1192 *flags &= ~flags_value;
1193 }
1194 return pressed;
1195}
1196
1197bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1198{
1199 return CheckboxFlagsT(label, flags, flags_value);
1200}
1201
1202bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1203{
1204 return CheckboxFlagsT(label, flags, flags_value);
1205}
1206
1207bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1208{
1209 return CheckboxFlagsT(label, flags, flags_value);
1210}
1211
1212bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1213{
1214 return CheckboxFlagsT(label, flags, flags_value);
1215}
1216
1217bool ImGui::RadioButton(const char* label, bool active)
1218{
1219 ImGuiWindow* window = GetCurrentWindow();
1220 if (window->SkipItems)
1221 return false;
1222
1223 ImGuiContext& g = *GImGui;
1224 const ImGuiStyle& style = g.Style;
1225 const ImGuiID id = window->GetID(label);
1226 const ImVec2 label_size = CalcTextSize(label, NULL, true);
1227
1228 const float square_sz = GetFrameHeight();
1229 const ImVec2 pos = window->DC.CursorPos;
1230 const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1231 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));
1232 ItemSize(total_bb, style.FramePadding.y);
1233 if (!ItemAdd(total_bb, id))
1234 return false;
1235
1236 ImVec2 center = check_bb.GetCenter();
1237 center.x = IM_ROUND(center.x);
1238 center.y = IM_ROUND(center.y);
1239 const float radius = (square_sz - 1.0f) * 0.5f;
1240
1241 bool hovered, held;
1242 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1243 if (pressed)
1244 MarkItemEdited(id);
1245
1246 RenderNavHighlight(total_bb, id);
1247 const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1248 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);
1249 if (active)
1250 {
1251 const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1252 window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
1253 }
1254
1255 if (style.FrameBorderSize > 0.0f)
1256 {
1257 window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);
1258 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);
1259 }
1260
1261 ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1262 if (g.LogEnabled)
1263 LogRenderedText(&label_pos, active ? "(x)" : "( )");
1264 if (label_size.x > 0.0f)
1265 RenderText(label_pos, label);
1266
1267 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1268 return pressed;
1269}
1270
1271// 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..
1272bool ImGui::RadioButton(const char* label, int* v, int v_button)
1273{
1274 const bool pressed = RadioButton(label, *v == v_button);
1275 if (pressed)
1276 *v = v_button;
1277 return pressed;
1278}
1279
1280// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1281void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1282{
1283 ImGuiWindow* window = GetCurrentWindow();
1284 if (window->SkipItems)
1285 return;
1286
1287 ImGuiContext& g = *GImGui;
1288 const ImGuiStyle& style = g.Style;
1289
1290 ImVec2 pos = window->DC.CursorPos;
1291 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1292 ImRect bb(pos, pos + size);
1293 ItemSize(size, style.FramePadding.y);
1294 if (!ItemAdd(bb, 0))
1295 return;
1296
1297 // Render
1298 fraction = ImSaturate(fraction);
1299 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1300 bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1301 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
1302 RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
1303
1304 // Default displaying the fraction as percentage string, but user can override it
1305 char overlay_buf[32];
1306 if (!overlay)
1307 {
1308 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1309 overlay = overlay_buf;
1310 }
1311
1312 ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1313 if (overlay_size.x > 0.0f)
1314 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);
1315}
1316
1317void ImGui::Bullet()
1318{
1319 ImGuiWindow* window = GetCurrentWindow();
1320 if (window->SkipItems)
1321 return;
1322
1323 ImGuiContext& g = *GImGui;
1324 const ImGuiStyle& style = g.Style;
1325 const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
1326 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1327 ItemSize(bb);
1328 if (!ItemAdd(bb, 0))
1329 {
1330 SameLine(0, style.FramePadding.x * 2);
1331 return;
1332 }
1333
1334 // Render and stay on same line
1335 ImU32 text_col = GetColorU32(ImGuiCol_Text);
1336 RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
1337 SameLine(0, style.FramePadding.x * 2.0f);
1338}
1339
1340//-------------------------------------------------------------------------
1341// [SECTION] Widgets: Low-level Layout helpers
1342//-------------------------------------------------------------------------
1343// - Spacing()
1344// - Dummy()
1345// - NewLine()
1346// - AlignTextToFramePadding()
1347// - SeparatorEx() [Internal]
1348// - Separator()
1349// - SplitterBehavior() [Internal]
1350// - ShrinkWidths() [Internal]
1351//-------------------------------------------------------------------------
1352
1353void ImGui::Spacing()
1354{
1355 ImGuiWindow* window = GetCurrentWindow();
1356 if (window->SkipItems)
1357 return;
1358 ItemSize(ImVec2(0, 0));
1359}
1360
1361void ImGui::Dummy(const ImVec2& size)
1362{
1363 ImGuiWindow* window = GetCurrentWindow();
1364 if (window->SkipItems)
1365 return;
1366
1367 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1368 ItemSize(size);
1369 ItemAdd(bb, 0);
1370}
1371
1372void ImGui::NewLine()
1373{
1374 ImGuiWindow* window = GetCurrentWindow();
1375 if (window->SkipItems)
1376 return;
1377
1378 ImGuiContext& g = *GImGui;
1379 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1380 window->DC.LayoutType = ImGuiLayoutType_Vertical;
1381 window->DC.IsSameLine = false;
1382 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.
1383 ItemSize(ImVec2(0, 0));
1384 else
1385 ItemSize(ImVec2(0.0f, g.FontSize));
1386 window->DC.LayoutType = backup_layout_type;
1387}
1388
1389void ImGui::AlignTextToFramePadding()
1390{
1391 ImGuiWindow* window = GetCurrentWindow();
1392 if (window->SkipItems)
1393 return;
1394
1395 ImGuiContext& g = *GImGui;
1396 window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1397 window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1398}
1399
1400// Horizontal/vertical separating line
1401// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1402// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
1403void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1404{
1405 ImGuiWindow* window = GetCurrentWindow();
1406 if (window->SkipItems)
1407 return;
1408
1409 ImGuiContext& g = *GImGui;
1410 IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected
1411 IM_ASSERT(thickness > 0.0f);
1412
1413 if (flags & ImGuiSeparatorFlags_Vertical)
1414 {
1415 // Vertical separator, for menu bars (use current line height).
1416 float y1 = window->DC.CursorPos.y;
1417 float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1418 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1419 ItemSize(ImVec2(thickness, 0.0f));
1420 if (!ItemAdd(bb, 0))
1421 return;
1422
1423 // Draw
1424 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1425 if (g.LogEnabled)
1426 LogText(" |");
1427 }
1428 else if (flags & ImGuiSeparatorFlags_Horizontal)
1429 {
1430 // Horizontal Separator
1431 float x1 = window->DC.CursorPos.x;
1432 float x2 = window->WorkRect.Max.x;
1433
1434 // Preserve legacy behavior inside Columns()
1435 // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1436 // We currently don't need to provide the same feature for tables because tables naturally have border features.
1437 ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1438 if (columns)
1439 {
1440 x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
1441 x2 = window->Pos.x + window->Size.x;
1442 PushColumnsBackground();
1443 }
1444
1445 // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1446 // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1447 const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change.
1448 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1449 ItemSize(ImVec2(0.0f, thickness_for_layout));
1450
1451 if (ItemAdd(bb, 0))
1452 {
1453 // Draw
1454 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1455 if (g.LogEnabled)
1456 LogRenderedText(&bb.Min, "--------------------------------\n");
1457
1458 }
1459 if (columns)
1460 {
1461 PopColumnsBackground();
1462 columns->LineMinY = window->DC.CursorPos.y;
1463 }
1464 }
1465}
1466
1467void ImGui::Separator()
1468{
1469 ImGuiContext& g = *GImGui;
1470 ImGuiWindow* window = g.CurrentWindow;
1471 if (window->SkipItems)
1472 return;
1473
1474 // Those flags should eventually be configurable by the user
1475 // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
1476 ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1477
1478 // Only applies to legacy Columns() api as they relied on Separator() a lot.
1479 if (window->DC.CurrentColumns)
1480 flags |= ImGuiSeparatorFlags_SpanAllColumns;
1481
1482 SeparatorEx(flags, 1.0f);
1483}
1484
1485void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1486{
1487 ImGuiContext& g = *GImGui;
1488 ImGuiWindow* window = g.CurrentWindow;
1489 ImGuiStyle& style = g.Style;
1490
1491 const ImVec2 label_size = CalcTextSize(label, label_end, false);
1492 const ImVec2 pos = window->DC.CursorPos;
1493 const ImVec2 padding = style.SeparatorTextPadding;
1494
1495 const float separator_thickness = style.SeparatorTextBorderSize;
1496 const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
1497 const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1498 const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
1499 ItemSize(min_size, text_baseline_y);
1500 if (!ItemAdd(bb, id))
1501 return;
1502
1503 const float sep1_x1 = pos.x;
1504 const float sep2_x2 = bb.Max.x;
1505 const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
1506
1507 const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
1508 const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN
1509
1510 // This allows using SameLine() to position something in the 'extra_w'
1511 window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1512
1513 const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);
1514 if (label_size.x > 0.0f)
1515 {
1516 const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1517 const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1518 if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1519 window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness);
1520 if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1521 window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
1522 if (g.LogEnabled)
1523 LogSetNextTextDecoration("---", NULL);
1524 RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);
1525 }
1526 else
1527 {
1528 if (g.LogEnabled)
1529 LogText("---");
1530 if (separator_thickness > 0.0f)
1531 window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
1532 }
1533}
1534
1535void ImGui::SeparatorText(const char* label)
1536{
1537 ImGuiWindow* window = GetCurrentWindow();
1538 if (window->SkipItems)
1539 return;
1540
1541 // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1542 // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1543 // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
1544 // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1545 // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1546 // and then we can turn this into a format function.
1547 SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);
1548}
1549
1550// 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.
1551bool 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, ImU32 bg_col)
1552{
1553 ImGuiContext& g = *GImGui;
1554 ImGuiWindow* window = g.CurrentWindow;
1555
1556 if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))
1557 return false;
1558
1559 // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1560 // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1561 // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1562 ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1563#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1564 button_flags |= ImGuiButtonFlags_AllowOverlap;
1565#endif
1566
1567 bool hovered, held;
1568 ImRect bb_interact = bb;
1569 bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1570 ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);
1571 if (hovered)
1572 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1573
1574 if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1575 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1576
1577 ImRect bb_render = bb;
1578 if (held)
1579 {
1580 float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1581
1582 // Minimum pane size
1583 float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1584 float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1585 if (mouse_delta < -size_1_maximum_delta)
1586 mouse_delta = -size_1_maximum_delta;
1587 if (mouse_delta > size_2_maximum_delta)
1588 mouse_delta = size_2_maximum_delta;
1589
1590 // Apply resize
1591 if (mouse_delta != 0.0f)
1592 {
1593 *size1 = ImMax(*size1 + mouse_delta, min_size1);
1594 *size2 = ImMax(*size2 - mouse_delta, min_size2);
1595 bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1596 MarkItemEdited(id);
1597 }
1598 }
1599
1600 // Render at new position
1601 if (bg_col & IM_COL32_A_MASK)
1602 window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
1603 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1604 window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1605
1606 return held;
1607}
1608
1609static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1610{
1611 const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1612 const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1613 if (int d = (int)(b->Width - a->Width))
1614 return d;
1615 return (b->Index - a->Index);
1616}
1617
1618// Shrink excess width from a set of item, by removing width from the larger items first.
1619// Set items Width to -1.0f to disable shrinking this item.
1620void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1621{
1622 if (count == 1)
1623 {
1624 if (items[0].Width >= 0.0f)
1625 items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
1626 return;
1627 }
1628 ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
1629 int count_same_width = 1;
1630 while (width_excess > 0.0f && count_same_width < count)
1631 {
1632 while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1633 count_same_width++;
1634 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);
1635 if (max_width_to_remove_per_item <= 0.0f)
1636 break;
1637 float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
1638 for (int item_n = 0; item_n < count_same_width; item_n++)
1639 items[item_n].Width -= width_to_remove_per_item;
1640 width_excess -= width_to_remove_per_item * count_same_width;
1641 }
1642
1643 // Round width and redistribute remainder
1644 // 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.
1645 width_excess = 0.0f;
1646 for (int n = 0; n < count; n++)
1647 {
1648 float width_rounded = ImTrunc(items[n].Width);
1649 width_excess += items[n].Width - width_rounded;
1650 items[n].Width = width_rounded;
1651 }
1652 while (width_excess > 0.0f)
1653 for (int n = 0; n < count && width_excess > 0.0f; n++)
1654 {
1655 float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
1656 items[n].Width += width_to_add;
1657 width_excess -= width_to_add;
1658 }
1659}
1660
1661//-------------------------------------------------------------------------
1662// [SECTION] Widgets: ComboBox
1663//-------------------------------------------------------------------------
1664// - CalcMaxPopupHeightFromItemCount() [Internal]
1665// - BeginCombo()
1666// - BeginComboPopup() [Internal]
1667// - EndCombo()
1668// - BeginComboPreview() [Internal]
1669// - EndComboPreview() [Internal]
1670// - Combo()
1671//-------------------------------------------------------------------------
1672
1673static float CalcMaxPopupHeightFromItemCount(int items_count)
1674{
1675 ImGuiContext& g = *GImGui;
1676 if (items_count <= 0)
1677 return FLT_MAX;
1678 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1679}
1680
1681bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1682{
1683 ImGuiContext& g = *GImGui;
1684 ImGuiWindow* window = GetCurrentWindow();
1685
1686 ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1687 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1688 if (window->SkipItems)
1689 return false;
1690
1691 const ImGuiStyle& style = g.Style;
1692 const ImGuiID id = window->GetID(label);
1693 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1694 if (flags & ImGuiComboFlags_WidthFitPreview)
1695 IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
1696
1697 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1698 const ImVec2 label_size = CalcTextSize(label, NULL, true);
1699 const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;
1700 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
1701 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1702 const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1703 ItemSize(total_bb, style.FramePadding.y);
1704 if (!ItemAdd(total_bb, id, &bb))
1705 return false;
1706
1707 // Open on click
1708 bool hovered, held;
1709 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1710 const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1711 bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1712 if (pressed && !popup_open)
1713 {
1714 OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1715 popup_open = true;
1716 }
1717
1718 // Render shape
1719 const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1720 const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1721 RenderNavHighlight(bb, id);
1722 if (!(flags & ImGuiComboFlags_NoPreview))
1723 window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1724 if (!(flags & ImGuiComboFlags_NoArrowButton))
1725 {
1726 ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1727 ImU32 text_col = GetColorU32(ImGuiCol_Text);
1728 window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1729 if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1730 RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
1731 }
1732 RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1733
1734 // Custom preview
1735 if (flags & ImGuiComboFlags_CustomPreview)
1736 {
1737 g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1738 IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1739 preview_value = NULL;
1740 }
1741
1742 // Render preview and label
1743 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1744 {
1745 if (g.LogEnabled)
1746 LogSetNextTextDecoration("{", "}");
1747 RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
1748 }
1749 if (label_size.x > 0)
1750 RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
1751
1752 if (!popup_open)
1753 return false;
1754
1755 g.NextWindowData.Flags = backup_next_window_data_flags;
1756 return BeginComboPopup(popup_id, bb, flags);
1757}
1758
1759bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1760{
1761 ImGuiContext& g = *GImGui;
1762 if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
1763 {
1764 g.NextWindowData.ClearFlags();
1765 return false;
1766 }
1767
1768 // Set popup size
1769 float w = bb.GetWidth();
1770 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1771 {
1772 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1773 }
1774 else
1775 {
1776 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1777 flags |= ImGuiComboFlags_HeightRegular;
1778 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1779 int popup_max_height_in_items = -1;
1780 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
1781 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
1782 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
1783 ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
1784 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
1785 constraint_min.x = w;
1786 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
1787 constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
1788 SetNextWindowSizeConstraints(constraint_min, constraint_max);
1789 }
1790
1791 // This is essentially a specialized version of BeginPopupEx()
1792 char name[16];
1793 ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
1794
1795 // Set position given a custom constraint (peak into expected window size so we can position it)
1796 // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1797 // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1798 if (ImGuiWindow* popup_window = FindWindowByName(name))
1799 if (popup_window->WasActive)
1800 {
1801 // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1802 ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
1803 popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1804 ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
1805 ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
1806 SetNextWindowPos(pos);
1807 }
1808
1809 // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1810 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1811 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text
1812 bool ret = Begin(name, NULL, window_flags);
1813 PopStyleVar();
1814 if (!ret)
1815 {
1816 EndPopup();
1817 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1818 return false;
1819 }
1820 g.BeginComboDepth++;
1821 return true;
1822}
1823
1824void ImGui::EndCombo()
1825{
1826 ImGuiContext& g = *GImGui;
1827 EndPopup();
1828 g.BeginComboDepth--;
1829}
1830
1831// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1832// (Experimental, see GitHub issues: #1658, #4168)
1833bool ImGui::BeginComboPreview()
1834{
1835 ImGuiContext& g = *GImGui;
1836 ImGuiWindow* window = g.CurrentWindow;
1837 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1838
1839 if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
1840 return false;
1841 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?
1842 if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)
1843 return false;
1844
1845 // FIXME: This could be contained in a PushWorkRect() api
1846 preview_data->BackupCursorPos = window->DC.CursorPos;
1847 preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1848 preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1849 preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1850 preview_data->BackupLayout = window->DC.LayoutType;
1851 window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1852 window->DC.CursorMaxPos = window->DC.CursorPos;
1853 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1854 window->DC.IsSameLine = false;
1855 PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
1856
1857 return true;
1858}
1859
1860void ImGui::EndComboPreview()
1861{
1862 ImGuiContext& g = *GImGui;
1863 ImGuiWindow* window = g.CurrentWindow;
1864 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1865
1866 // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
1867 ImDrawList* draw_list = window->DrawList;
1868 if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
1869 if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1870 {
1871 draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
1872 draw_list->_TryMergeDrawCmds();
1873 }
1874 PopClipRect();
1875 window->DC.CursorPos = preview_data->BackupCursorPos;
1876 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
1877 window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1878 window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1879 window->DC.LayoutType = preview_data->BackupLayout;
1880 window->DC.IsSameLine = false;
1881 preview_data->PreviewRect = ImRect();
1882}
1883
1884// Getter for the old Combo() API: const char*[]
1885static const char* Items_ArrayGetter(void* data, int idx)
1886{
1887 const char* const* items = (const char* const*)data;
1888 return items[idx];
1889}
1890
1891// Getter for the old Combo() API: "item1\0item2\0item3\0"
1892static const char* Items_SingleStringGetter(void* data, int idx)
1893{
1894 const char* items_separated_by_zeros = (const char*)data;
1895 int items_count = 0;
1896 const char* p = items_separated_by_zeros;
1897 while (*p)
1898 {
1899 if (idx == items_count)
1900 break;
1901 p += strlen(p) + 1;
1902 items_count++;
1903 }
1904 return *p ? p : NULL;
1905}
1906
1907// Old API, prefer using BeginCombo() nowadays if you can.
1908bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items)
1909{
1910 ImGuiContext& g = *GImGui;
1911
1912 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1913 const char* preview_value = NULL;
1914 if (*current_item >= 0 && *current_item < items_count)
1915 preview_value = getter(user_data, *current_item);
1916
1917 // 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.
1918 if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1919 SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1920
1921 if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1922 return false;
1923
1924 // Display items
1925 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1926 bool value_changed = false;
1927 for (int i = 0; i < items_count; i++)
1928 {
1929 const char* item_text = getter(user_data, i);
1930 if (item_text == NULL)
1931 item_text = "*Unknown item*";
1932
1933 PushID(i);
1934 const bool item_selected = (i == *current_item);
1935 if (Selectable(item_text, item_selected) && *current_item != i)
1936 {
1937 value_changed = true;
1938 *current_item = i;
1939 }
1940 if (item_selected)
1941 SetItemDefaultFocus();
1942 PopID();
1943 }
1944
1945 EndCombo();
1946
1947 if (value_changed)
1948 MarkItemEdited(g.LastItemData.ID);
1949
1950 return value_changed;
1951}
1952
1953// Combo box helper allowing to pass an array of strings.
1954bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1955{
1956 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
1957 return value_changed;
1958}
1959
1960// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
1961bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1962{
1963 int items_count = 0;
1964 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
1965 while (*p)
1966 {
1967 p += strlen(p) + 1;
1968 items_count++;
1969 }
1970 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
1971 return value_changed;
1972}
1973
1974#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1975
1976struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
1977static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
1978{
1979 ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
1980 const char* s = NULL;
1981 data->OldCallback(data->UserData, idx, &s);
1982 return s;
1983}
1984
1985bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items)
1986{
1987 ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
1988 return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items);
1989}
1990bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items)
1991{
1992 ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
1993 return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items);
1994}
1995
1996#endif
1997
1998//-------------------------------------------------------------------------
1999// [SECTION] Data Type and Data Formatting Helpers [Internal]
2000//-------------------------------------------------------------------------
2001// - DataTypeGetInfo()
2002// - DataTypeFormatString()
2003// - DataTypeApplyOp()
2004// - DataTypeApplyFromText()
2005// - DataTypeCompare()
2006// - DataTypeClamp()
2007// - GetMinimumStepAtDecimalPrecision
2008// - RoundScalarWithFormat<>()
2009//-------------------------------------------------------------------------
2010
2011static const ImGuiDataTypeInfo GDataTypeInfo[] =
2012{
2013 { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8
2014 { sizeof(unsigned char), "U8", "%u", "%u" },
2015 { sizeof(short), "S16", "%d", "%d" }, // ImGuiDataType_S16
2016 { sizeof(unsigned short), "U16", "%u", "%u" },
2017 { sizeof(int), "S32", "%d", "%d" }, // ImGuiDataType_S32
2018 { sizeof(unsigned int), "U32", "%u", "%u" },
2019#ifdef _MSC_VER
2020 { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64
2021 { sizeof(ImU64), "U64", "%I64u","%I64u" },
2022#else
2023 { sizeof(ImS64), "S64", "%lld", "%lld" }, // ImGuiDataType_S64
2024 { sizeof(ImU64), "U64", "%llu", "%llu" },
2025#endif
2026 { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg)
2027 { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double
2028};
2029IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
2030
2031const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
2032{
2033 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2034 return &GDataTypeInfo[data_type];
2035}
2036
2037int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
2038{
2039 // Signedness doesn't matter when pushing integer arguments
2040 if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
2041 return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
2042 if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2043 return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
2044 if (data_type == ImGuiDataType_Float)
2045 return ImFormatString(buf, buf_size, format, *(const float*)p_data);
2046 if (data_type == ImGuiDataType_Double)
2047 return ImFormatString(buf, buf_size, format, *(const double*)p_data);
2048 if (data_type == ImGuiDataType_S8)
2049 return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
2050 if (data_type == ImGuiDataType_U8)
2051 return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
2052 if (data_type == ImGuiDataType_S16)
2053 return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
2054 if (data_type == ImGuiDataType_U16)
2055 return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
2056 IM_ASSERT(0);
2057 return 0;
2058}
2059
2060void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
2061{
2062 IM_ASSERT(op == '+' || op == '-');
2063 switch (data_type)
2064 {
2065 case ImGuiDataType_S8:
2066 if (op == '+') { *(ImS8*)output = ImAddClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); }
2067 if (op == '-') { *(ImS8*)output = ImSubClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); }
2068 return;
2069 case ImGuiDataType_U8:
2070 if (op == '+') { *(ImU8*)output = ImAddClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); }
2071 if (op == '-') { *(ImU8*)output = ImSubClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); }
2072 return;
2073 case ImGuiDataType_S16:
2074 if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
2075 if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
2076 return;
2077 case ImGuiDataType_U16:
2078 if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
2079 if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
2080 return;
2081 case ImGuiDataType_S32:
2082 if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
2083 if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
2084 return;
2085 case ImGuiDataType_U32:
2086 if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
2087 if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
2088 return;
2089 case ImGuiDataType_S64:
2090 if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
2091 if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
2092 return;
2093 case ImGuiDataType_U64:
2094 if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
2095 if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
2096 return;
2097 case ImGuiDataType_Float:
2098 if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
2099 if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
2100 return;
2101 case ImGuiDataType_Double:
2102 if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
2103 if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
2104 return;
2105 case ImGuiDataType_COUNT: break;
2106 }
2107 IM_ASSERT(0);
2108}
2109
2110// User can input math operators (e.g. +100) to edit a numerical values.
2111// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
2112bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format)
2113{
2114 while (ImCharIsBlankA(*buf))
2115 buf++;
2116 if (!buf[0])
2117 return false;
2118
2119 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2120 const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2121 ImGuiDataTypeTempStorage data_backup;
2122 memcpy(&data_backup, p_data, type_info->Size);
2123
2124 // Sanitize format
2125 // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
2126 // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
2127 char format_sanitized[32];
2128 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2129 format = type_info->ScanFmt;
2130 else
2131 format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
2132
2133 // Small types need a 32-bit buffer to receive the result from scanf()
2134 int v32 = 0;
2135 if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
2136 return false;
2137 if (type_info->Size < 4)
2138 {
2139 if (data_type == ImGuiDataType_S8)
2140 *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
2141 else if (data_type == ImGuiDataType_U8)
2142 *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
2143 else if (data_type == ImGuiDataType_S16)
2144 *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
2145 else if (data_type == ImGuiDataType_U16)
2146 *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
2147 else
2148 IM_ASSERT(0);
2149 }
2150
2151 return memcmp(&data_backup, p_data, type_info->Size) != 0;
2152}
2153
2154template<typename T>
2155static int DataTypeCompareT(const T* lhs, const T* rhs)
2156{
2157 if (*lhs < *rhs) return -1;
2158 if (*lhs > *rhs) return +1;
2159 return 0;
2160}
2161
2162int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2163{
2164 switch (data_type)
2165 {
2166 case ImGuiDataType_S8: return DataTypeCompareT<ImS8 >((const ImS8* )arg_1, (const ImS8* )arg_2);
2167 case ImGuiDataType_U8: return DataTypeCompareT<ImU8 >((const ImU8* )arg_1, (const ImU8* )arg_2);
2168 case ImGuiDataType_S16: return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
2169 case ImGuiDataType_U16: return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
2170 case ImGuiDataType_S32: return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
2171 case ImGuiDataType_U32: return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
2172 case ImGuiDataType_S64: return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
2173 case ImGuiDataType_U64: return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
2174 case ImGuiDataType_Float: return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
2175 case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
2176 case ImGuiDataType_COUNT: break;
2177 }
2178 IM_ASSERT(0);
2179 return 0;
2180}
2181
2182template<typename T>
2183static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2184{
2185 // Clamp, both sides are optional, return true if modified
2186 if (v_min && *v < *v_min) { *v = *v_min; return true; }
2187 if (v_max && *v > *v_max) { *v = *v_max; return true; }
2188 return false;
2189}
2190
2191bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2192{
2193 switch (data_type)
2194 {
2195 case ImGuiDataType_S8: return DataTypeClampT<ImS8 >((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max);
2196 case ImGuiDataType_U8: return DataTypeClampT<ImU8 >((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max);
2197 case ImGuiDataType_S16: return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
2198 case ImGuiDataType_U16: return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
2199 case ImGuiDataType_S32: return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
2200 case ImGuiDataType_U32: return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
2201 case ImGuiDataType_S64: return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
2202 case ImGuiDataType_U64: return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
2203 case ImGuiDataType_Float: return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
2204 case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
2205 case ImGuiDataType_COUNT: break;
2206 }
2207 IM_ASSERT(0);
2208 return false;
2209}
2210
2211static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2212{
2213 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 };
2214 if (decimal_precision < 0)
2215 return FLT_MIN;
2216 return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
2217}
2218
2219template<typename TYPE>
2220TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2221{
2222 IM_UNUSED(data_type);
2223 IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2224 const char* fmt_start = ImParseFormatFindStart(format);
2225 if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2226 return v;
2227
2228 // Sanitize format
2229 char fmt_sanitized[32];
2230 ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
2231 fmt_start = fmt_sanitized;
2232
2233 // Format value with our rounding, and read back
2234 char v_str[64];
2235 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
2236 const char* p = v_str;
2237 while (*p == ' ')
2238 p++;
2239 v = (TYPE)ImAtof(p);
2240
2241 return v;
2242}
2243
2244//-------------------------------------------------------------------------
2245// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2246//-------------------------------------------------------------------------
2247// - DragBehaviorT<>() [Internal]
2248// - DragBehavior() [Internal]
2249// - DragScalar()
2250// - DragScalarN()
2251// - DragFloat()
2252// - DragFloat2()
2253// - DragFloat3()
2254// - DragFloat4()
2255// - DragFloatRange2()
2256// - DragInt()
2257// - DragInt2()
2258// - DragInt3()
2259// - DragInt4()
2260// - DragIntRange2()
2261//-------------------------------------------------------------------------
2262
2263// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2264template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2265bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2266{
2267 ImGuiContext& g = *GImGui;
2268 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2269 const bool is_clamped = (v_min < v_max);
2270 const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2271 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2272
2273 // Default tweak speed
2274 if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
2275 v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2276
2277 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2278 float adjust_delta = 0.0f;
2279 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2280 {
2281 adjust_delta = g.IO.MouseDelta[axis];
2282 if (g.IO.KeyAlt)
2283 adjust_delta *= 1.0f / 100.0f;
2284 if (g.IO.KeyShift)
2285 adjust_delta *= 10.0f;
2286 }
2287 else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
2288 {
2289 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2290 const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
2291 const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
2292 const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f;
2293 adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
2294 v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
2295 }
2296 adjust_delta *= v_speed;
2297
2298 // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2299 if (axis == ImGuiAxis_Y)
2300 adjust_delta = -adjust_delta;
2301
2302 // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2303 if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2304 adjust_delta /= (float)(v_max - v_min);
2305
2306 // Clear current value on activation
2307 // 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.
2308 bool is_just_activated = g.ActiveIdIsJustActivated;
2309 bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
2310 if (is_just_activated || is_already_past_limits_and_pushing_outward)
2311 {
2312 g.DragCurrentAccum = 0.0f;
2313 g.DragCurrentAccumDirty = false;
2314 }
2315 else if (adjust_delta != 0.0f)
2316 {
2317 g.DragCurrentAccum += adjust_delta;
2318 g.DragCurrentAccumDirty = true;
2319 }
2320
2321 if (!g.DragCurrentAccumDirty)
2322 return false;
2323
2324 TYPE v_cur = *v;
2325 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2326
2327 float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2328 const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2329 if (is_logarithmic)
2330 {
2331 // 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.
2332 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2333 logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2334
2335 // Convert to parametric space, apply delta, convert back
2336 float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2337 float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2338 v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2339 v_old_ref_for_accum_remainder = v_old_parametric;
2340 }
2341 else
2342 {
2343 v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2344 }
2345
2346 // Round to user desired precision based on format string
2347 if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2348 v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
2349
2350 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2351 g.DragCurrentAccumDirty = false;
2352 if (is_logarithmic)
2353 {
2354 // Convert to parametric space, apply delta, convert back
2355 float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2356 g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2357 }
2358 else
2359 {
2360 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2361 }
2362
2363 // Lose zero sign for float/double
2364 if (v_cur == (TYPE)-0)
2365 v_cur = (TYPE)0;
2366
2367 // Clamp values (+ handle overflow/wrap-around for integer types)
2368 if (*v != v_cur && is_clamped)
2369 {
2370 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2371 v_cur = v_min;
2372 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2373 v_cur = v_max;
2374 }
2375
2376 // Apply result
2377 if (*v == v_cur)
2378 return false;
2379 *v = v_cur;
2380 return true;
2381}
2382
2383bool 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)
2384{
2385 // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2386 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.");
2387
2388 ImGuiContext& g = *GImGui;
2389 if (g.ActiveId == id)
2390 {
2391 // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
2392 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2393 ClearActiveID();
2394 else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2395 ClearActiveID();
2396 }
2397 if (g.ActiveId != id)
2398 return false;
2399 if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2400 return false;
2401
2402 switch (data_type)
2403 {
2404 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; }
2405 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; }
2406 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; }
2407 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; }
2408 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);
2409 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);
2410 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);
2411 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);
2412 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);
2413 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);
2414 case ImGuiDataType_COUNT: break;
2415 }
2416 IM_ASSERT(0);
2417 return false;
2418}
2419
2420// 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.
2421// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
2422bool 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)
2423{
2424 ImGuiWindow* window = GetCurrentWindow();
2425 if (window->SkipItems)
2426 return false;
2427
2428 ImGuiContext& g = *GImGui;
2429 const ImGuiStyle& style = g.Style;
2430 const ImGuiID id = window->GetID(label);
2431 const float w = CalcItemWidth();
2432
2433 const ImVec2 label_size = CalcTextSize(label, NULL, true);
2434 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2435 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));
2436
2437 const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2438 ItemSize(total_bb, style.FramePadding.y);
2439 if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
2440 return false;
2441
2442 // Default format string when passing NULL
2443 if (format == NULL)
2444 format = DataTypeGetInfo(data_type)->PrintFmt;
2445
2446 const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
2447 bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2448 if (!temp_input_is_active)
2449 {
2450 // Tabbing or CTRL-clicking on Drag turns it into an InputText
2451 const bool clicked = hovered && IsMouseClicked(0, id);
2452 const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
2453 const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
2454 if (make_active && (clicked || double_clicked))
2455 SetKeyOwner(ImGuiKey_MouseLeft, id);
2456 if (make_active && temp_input_allowed)
2457 if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
2458 temp_input_is_active = true;
2459
2460 // (Optional) simple click (without moving) turns Drag into an InputText
2461 if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2462 if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2463 {
2464 g.NavActivateId = id;
2465 g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
2466 temp_input_is_active = true;
2467 }
2468
2469 if (make_active && !temp_input_is_active)
2470 {
2471 SetActiveID(id, window);
2472 SetFocusID(id, window);
2473 FocusWindow(window);
2474 g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2475 }
2476 }
2477
2478 if (temp_input_is_active)
2479 {
2480 // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
2481 const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0);
2482 return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
2483 }
2484
2485 // Draw frame
2486 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2487 RenderNavHighlight(frame_bb, id);
2488 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
2489
2490 // Drag behavior
2491 const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
2492 if (value_changed)
2493 MarkItemEdited(id);
2494
2495 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2496 char value_buf[64];
2497 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
2498 if (g.LogEnabled)
2499 LogSetNextTextDecoration("{", "}");
2500 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
2501
2502 if (label_size.x > 0.0f)
2503 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
2504
2505 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
2506 return value_changed;
2507}
2508
2509bool 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)
2510{
2511 ImGuiWindow* window = GetCurrentWindow();
2512 if (window->SkipItems)
2513 return false;
2514
2515 ImGuiContext& g = *GImGui;
2516 bool value_changed = false;
2517 BeginGroup();
2518 PushID(label);
2519 PushMultiItemsWidths(components, CalcItemWidth());
2520 size_t type_size = GDataTypeInfo[data_type].Size;
2521 for (int i = 0; i < components; i++)
2522 {
2523 PushID(i);
2524 if (i > 0)
2525 SameLine(0, g.Style.ItemInnerSpacing.x);
2526 value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
2527 PopID();
2528 PopItemWidth();
2529 p_data = (void*)((char*)p_data + type_size);
2530 }
2531 PopID();
2532
2533 const char* label_end = FindRenderedTextEnd(label);
2534 if (label != label_end)
2535 {
2536 SameLine(0, g.Style.ItemInnerSpacing.x);
2537 TextEx(label, label_end);
2538 }
2539
2540 EndGroup();
2541 return value_changed;
2542}
2543
2544bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2545{
2546 return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
2547}
2548
2549bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2550{
2551 return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
2552}
2553
2554bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2555{
2556 return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
2557}
2558
2559bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2560{
2561 return DragScalarN(label, ImGuiDataType_Float, 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::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)
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 float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2577 float 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 = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
2580 PopItemWidth();
2581 SameLine(0, g.Style.ItemInnerSpacing.x);
2582
2583 float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2584 float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2585 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2586 value_changed |= DragScalar("##max", ImGuiDataType_Float, 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// NB: v_speed is float to allow adjusting the drag speed with more precision
2598bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2599{
2600 return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
2601}
2602
2603bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2604{
2605 return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
2606}
2607
2608bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2609{
2610 return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
2611}
2612
2613bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2614{
2615 return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
2616}
2617
2618// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2619bool 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)
2620{
2621 ImGuiWindow* window = GetCurrentWindow();
2622 if (window->SkipItems)
2623 return false;
2624
2625 ImGuiContext& g = *GImGui;
2626 PushID(label);
2627 BeginGroup();
2628 PushMultiItemsWidths(2, CalcItemWidth());
2629
2630 int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2631 int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2632 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2633 bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
2634 PopItemWidth();
2635 SameLine(0, g.Style.ItemInnerSpacing.x);
2636
2637 int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2638 int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2639 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2640 value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
2641 PopItemWidth();
2642 SameLine(0, g.Style.ItemInnerSpacing.x);
2643
2644 TextEx(label, FindRenderedTextEnd(label));
2645 EndGroup();
2646 PopID();
2647
2648 return value_changed;
2649}
2650
2651//-------------------------------------------------------------------------
2652// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2653//-------------------------------------------------------------------------
2654// - ScaleRatioFromValueT<> [Internal]
2655// - ScaleValueFromRatioT<> [Internal]
2656// - SliderBehaviorT<>() [Internal]
2657// - SliderBehavior() [Internal]
2658// - SliderScalar()
2659// - SliderScalarN()
2660// - SliderFloat()
2661// - SliderFloat2()
2662// - SliderFloat3()
2663// - SliderFloat4()
2664// - SliderAngle()
2665// - SliderInt()
2666// - SliderInt2()
2667// - SliderInt3()
2668// - SliderInt4()
2669// - VSliderScalar()
2670// - VSliderFloat()
2671// - VSliderInt()
2672//-------------------------------------------------------------------------
2673
2674// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2675template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2676float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2677{
2678 if (v_min == v_max)
2679 return 0.0f;
2680 IM_UNUSED(data_type);
2681
2682 const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2683 if (is_logarithmic)
2684 {
2685 bool flipped = v_max < v_min;
2686
2687 if (flipped) // Handle the case where the range is backwards
2688 ImSwap(v_min, v_max);
2689
2690 // Fudge min/max to avoid getting close to log(0)
2691 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2692 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2693
2694 // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2695 if ((v_min == 0.0f) && (v_max < 0.0f))
2696 v_min_fudged = -logarithmic_zero_epsilon;
2697 else if ((v_max == 0.0f) && (v_min < 0.0f))
2698 v_max_fudged = -logarithmic_zero_epsilon;
2699
2700 float result;
2701 if (v_clamped <= v_min_fudged)
2702 result = 0.0f; // Workaround for values that are in-range but below our fudge
2703 else if (v_clamped >= v_max_fudged)
2704 result = 1.0f; // Workaround for values that are in-range but above our fudge
2705 else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
2706 {
2707 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)
2708 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2709 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2710 if (v == 0.0f)
2711 result = zero_point_center; // Special case for exactly zero
2712 else if (v < 0.0f)
2713 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
2714 else
2715 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));
2716 }
2717 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2718 result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
2719 else
2720 result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
2721
2722 return flipped ? (1.0f - result) : result;
2723 }
2724 else
2725 {
2726 // Linear slider
2727 return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
2728 }
2729}
2730
2731// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
2732template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2733TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2734{
2735 // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
2736 // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
2737 if (t <= 0.0f || v_min == v_max)
2738 return v_min;
2739 if (t >= 1.0f)
2740 return v_max;
2741
2742 TYPE result = (TYPE)0;
2743 if (is_logarithmic)
2744 {
2745 // Fudge min/max to avoid getting silly results close to zero
2746 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2747 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2748
2749 const bool flipped = v_max < v_min; // Check if range is "backwards"
2750 if (flipped)
2751 ImSwap(v_min_fudged, v_max_fudged);
2752
2753 // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2754 if ((v_max == 0.0f) && (v_min < 0.0f))
2755 v_max_fudged = -logarithmic_zero_epsilon;
2756
2757 float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
2758
2759 if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
2760 {
2761 float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
2762 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2763 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2764 if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
2765 result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
2766 else if (t_with_flip < zero_point_center)
2767 result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
2768 else
2769 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))));
2770 }
2771 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2772 result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
2773 else
2774 result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
2775 }
2776 else
2777 {
2778 // Linear slider
2779 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2780 if (is_floating_point)
2781 {
2782 result = ImLerp(v_min, v_max, t);
2783 }
2784 else if (t < 1.0)
2785 {
2786 // - For integer values we want the clicking position to match the grab box so we round above
2787 // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2788 // - 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
2789 // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
2790 FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
2791 result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
2792 }
2793 }
2794
2795 return result;
2796}
2797
2798// FIXME: Try to move more of the code into shared SliderBehavior()
2799template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2800bool 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)
2801{
2802 ImGuiContext& g = *GImGui;
2803 const ImGuiStyle& style = g.Style;
2804
2805 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2806 const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2807 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2808 const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it.
2809
2810 // Calculate bounds
2811 const float grab_padding = 2.0f; // FIXME: Should be part of style.
2812 const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
2813 float grab_sz = style.GrabMinSize;
2814 if (!is_floating_point && v_range_f >= 0.0f) // v_range_f < 0 may happen on integer overflows
2815 grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
2816 grab_sz = ImMin(grab_sz, slider_sz);
2817 const float slider_usable_sz = slider_sz - grab_sz;
2818 const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
2819 const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
2820
2821 float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2822 float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
2823 if (is_logarithmic)
2824 {
2825 // 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.
2826 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2827 logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2828 zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
2829 }
2830
2831 // Process interacting with the slider
2832 bool value_changed = false;
2833 if (g.ActiveId == id)
2834 {
2835 bool set_new_value = false;
2836 float clicked_t = 0.0f;
2837 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
2838 {
2839 if (!g.IO.MouseDown[0])
2840 {
2841 ClearActiveID();
2842 }
2843 else
2844 {
2845 const float mouse_abs_pos = g.IO.MousePos[axis];
2846 if (g.ActiveIdIsJustActivated)
2847 {
2848 float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2849 if (axis == ImGuiAxis_Y)
2850 grab_t = 1.0f - grab_t;
2851 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
2852 const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here.
2853 g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
2854 }
2855 if (slider_usable_sz > 0.0f)
2856 clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
2857 if (axis == ImGuiAxis_Y)
2858 clicked_t = 1.0f - clicked_t;
2859 set_new_value = true;
2860 }
2861 }
2862 else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
2863 {
2864 if (g.ActiveIdIsJustActivated)
2865 {
2866 g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
2867 g.SliderCurrentAccumDirty = false;
2868 }
2869
2870 float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
2871 if (input_delta != 0.0f)
2872 {
2873 const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
2874 const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
2875 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2876 if (decimal_precision > 0)
2877 {
2878 input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
2879 if (tweak_slow)
2880 input_delta /= 10.0f;
2881 }
2882 else
2883 {
2884 if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)
2885 input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Gamepad/keyboard tweak speeds in integer steps
2886 else
2887 input_delta /= 100.0f;
2888 }
2889 if (tweak_fast)
2890 input_delta *= 10.0f;
2891
2892 g.SliderCurrentAccum += input_delta;
2893 g.SliderCurrentAccumDirty = true;
2894 }
2895
2896 float delta = g.SliderCurrentAccum;
2897 if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2898 {
2899 ClearActiveID();
2900 }
2901 else if (g.SliderCurrentAccumDirty)
2902 {
2903 clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2904
2905 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
2906 {
2907 set_new_value = false;
2908 g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
2909 }
2910 else
2911 {
2912 set_new_value = true;
2913 float old_clicked_t = clicked_t;
2914 clicked_t = ImSaturate(clicked_t + delta);
2915
2916 // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
2917 TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2918 if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2919 v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
2920 float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2921
2922 if (delta > 0)
2923 g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
2924 else
2925 g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
2926 }
2927
2928 g.SliderCurrentAccumDirty = false;
2929 }
2930 }
2931
2932 if (set_new_value)
2933 if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2934 set_new_value = false;
2935
2936 if (set_new_value)
2937 {
2938 TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2939
2940 // Round to user desired precision based on format string
2941 if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2942 v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
2943
2944 // Apply result
2945 if (*v != v_new)
2946 {
2947 *v = v_new;
2948 value_changed = true;
2949 }
2950 }
2951 }
2952
2953 if (slider_sz < 1.0f)
2954 {
2955 *out_grab_bb = ImRect(bb.Min, bb.Min);
2956 }
2957 else
2958 {
2959 // Output grab position so it can be displayed by the caller
2960 float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2961 if (axis == ImGuiAxis_Y)
2962 grab_t = 1.0f - grab_t;
2963 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
2964 if (axis == ImGuiAxis_X)
2965 *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);
2966 else
2967 *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);
2968 }
2969
2970 return value_changed;
2971}
2972
2973// For 32-bit and larger types, slider bounds are limited to half the natural type range.
2974// 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.
2975// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
2976bool 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)
2977{
2978 // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2979 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.");
2980
2981 switch (data_type)
2982 {
2983 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; }
2984 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; }
2985 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; }
2986 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; }
2987 case ImGuiDataType_S32:
2988 IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
2989 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);
2990 case ImGuiDataType_U32:
2991 IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
2992 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);
2993 case ImGuiDataType_S64:
2994 IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
2995 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);
2996 case ImGuiDataType_U64:
2997 IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
2998 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);
2999 case ImGuiDataType_Float:
3000 IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
3001 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);
3002 case ImGuiDataType_Double:
3003 IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
3004 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);
3005 case ImGuiDataType_COUNT: break;
3006 }
3007 IM_ASSERT(0);
3008 return false;
3009}
3010
3011// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
3012// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
3013bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
3014{
3015 ImGuiWindow* window = GetCurrentWindow();
3016 if (window->SkipItems)
3017 return false;
3018
3019 ImGuiContext& g = *GImGui;
3020 const ImGuiStyle& style = g.Style;
3021 const ImGuiID id = window->GetID(label);
3022 const float w = CalcItemWidth();
3023
3024 const ImVec2 label_size = CalcTextSize(label, NULL, true);
3025 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
3026 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));
3027
3028 const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
3029 ItemSize(total_bb, style.FramePadding.y);
3030 if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
3031 return false;
3032
3033 // Default format string when passing NULL
3034 if (format == NULL)
3035 format = DataTypeGetInfo(data_type)->PrintFmt;
3036
3037 const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
3038 bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
3039 if (!temp_input_is_active)
3040 {
3041 // Tabbing or CTRL-clicking on Slider turns it into an input box
3042 const bool clicked = hovered && IsMouseClicked(0, id);
3043 const bool make_active = (clicked || g.NavActivateId == id);
3044 if (make_active && clicked)
3045 SetKeyOwner(ImGuiKey_MouseLeft, id);
3046 if (make_active && temp_input_allowed)
3047 if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
3048 temp_input_is_active = true;
3049
3050 if (make_active && !temp_input_is_active)
3051 {
3052 SetActiveID(id, window);
3053 SetFocusID(id, window);
3054 FocusWindow(window);
3055 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
3056 }
3057 }
3058
3059 if (temp_input_is_active)
3060 {
3061 // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
3062 const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
3063 return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
3064 }
3065
3066 // Draw frame
3067 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3068 RenderNavHighlight(frame_bb, id);
3069 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3070
3071 // Slider behavior
3072 ImRect grab_bb;
3073 const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
3074 if (value_changed)
3075 MarkItemEdited(id);
3076
3077 // Render grab
3078 if (grab_bb.Max.x > grab_bb.Min.x)
3079 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3080
3081 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3082 char value_buf[64];
3083 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3084 if (g.LogEnabled)
3085 LogSetNextTextDecoration("{", "}");
3086 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
3087
3088 if (label_size.x > 0.0f)
3089 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3090
3091 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
3092 return value_changed;
3093}
3094
3095// Add multiple sliders on 1 line for compact edition of multiple components
3096bool 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)
3097{
3098 ImGuiWindow* window = GetCurrentWindow();
3099 if (window->SkipItems)
3100 return false;
3101
3102 ImGuiContext& g = *GImGui;
3103 bool value_changed = false;
3104 BeginGroup();
3105 PushID(label);
3106 PushMultiItemsWidths(components, CalcItemWidth());
3107 size_t type_size = GDataTypeInfo[data_type].Size;
3108 for (int i = 0; i < components; i++)
3109 {
3110 PushID(i);
3111 if (i > 0)
3112 SameLine(0, g.Style.ItemInnerSpacing.x);
3113 value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
3114 PopID();
3115 PopItemWidth();
3116 v = (void*)((char*)v + type_size);
3117 }
3118 PopID();
3119
3120 const char* label_end = FindRenderedTextEnd(label);
3121 if (label != label_end)
3122 {
3123 SameLine(0, g.Style.ItemInnerSpacing.x);
3124 TextEx(label, label_end);
3125 }
3126
3127 EndGroup();
3128 return value_changed;
3129}
3130
3131bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3132{
3133 return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3134}
3135
3136bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3137{
3138 return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
3139}
3140
3141bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3142{
3143 return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
3144}
3145
3146bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3147{
3148 return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
3149}
3150
3151bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
3152{
3153 if (format == NULL)
3154 format = "%.0f deg";
3155 float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
3156 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
3157 *v_rad = v_deg * (2 * IM_PI) / 360.0f;
3158 return value_changed;
3159}
3160
3161bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3162{
3163 return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3164}
3165
3166bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3167{
3168 return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
3169}
3170
3171bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3172{
3173 return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
3174}
3175
3176bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3177{
3178 return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
3179}
3180
3181bool 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)
3182{
3183 ImGuiWindow* window = GetCurrentWindow();
3184 if (window->SkipItems)
3185 return false;
3186
3187 ImGuiContext& g = *GImGui;
3188 const ImGuiStyle& style = g.Style;
3189 const ImGuiID id = window->GetID(label);
3190
3191 const ImVec2 label_size = CalcTextSize(label, NULL, true);
3192 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3193 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));
3194
3195 ItemSize(bb, style.FramePadding.y);
3196 if (!ItemAdd(frame_bb, id))
3197 return false;
3198
3199 // Default format string when passing NULL
3200 if (format == NULL)
3201 format = DataTypeGetInfo(data_type)->PrintFmt;
3202
3203 const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
3204 const bool clicked = hovered && IsMouseClicked(0, id);
3205 if (clicked || g.NavActivateId == id)
3206 {
3207 if (clicked)
3208 SetKeyOwner(ImGuiKey_MouseLeft, id);
3209 SetActiveID(id, window);
3210 SetFocusID(id, window);
3211 FocusWindow(window);
3212 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
3213 }
3214
3215 // Draw frame
3216 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3217 RenderNavHighlight(frame_bb, id);
3218 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3219
3220 // Slider behavior
3221 ImRect grab_bb;
3222 const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
3223 if (value_changed)
3224 MarkItemEdited(id);
3225
3226 // Render grab
3227 if (grab_bb.Max.y > grab_bb.Min.y)
3228 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3229
3230 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3231 // For the vertical slider we allow centered text to overlap the frame padding
3232 char value_buf[64];
3233 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3234 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));
3235 if (label_size.x > 0.0f)
3236 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3237
3238 return value_changed;
3239}
3240
3241bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3242{
3243 return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3244}
3245
3246bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3247{
3248 return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3249}
3250
3251//-------------------------------------------------------------------------
3252// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
3253//-------------------------------------------------------------------------
3254// - ImParseFormatFindStart() [Internal]
3255// - ImParseFormatFindEnd() [Internal]
3256// - ImParseFormatTrimDecorations() [Internal]
3257// - ImParseFormatSanitizeForPrinting() [Internal]
3258// - ImParseFormatSanitizeForScanning() [Internal]
3259// - ImParseFormatPrecision() [Internal]
3260// - TempInputTextScalar() [Internal]
3261// - InputScalar()
3262// - InputScalarN()
3263// - InputFloat()
3264// - InputFloat2()
3265// - InputFloat3()
3266// - InputFloat4()
3267// - InputInt()
3268// - InputInt2()
3269// - InputInt3()
3270// - InputInt4()
3271// - InputDouble()
3272//-------------------------------------------------------------------------
3273
3274// We don't use strchr() because our strings are usually very short and often start with '%'
3275const char* ImParseFormatFindStart(const char* fmt)
3276{
3277 while (char c = fmt[0])
3278 {
3279 if (c == '%' && fmt[1] != '%')
3280 return fmt;
3281 else if (c == '%')
3282 fmt++;
3283 fmt++;
3284 }
3285 return fmt;
3286}
3287
3288const char* ImParseFormatFindEnd(const char* fmt)
3289{
3290 // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
3291 if (fmt[0] != '%')
3292 return fmt;
3293 const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
3294 const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
3295 for (char c; (c = *fmt) != 0; fmt++)
3296 {
3297 if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
3298 return fmt + 1;
3299 if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
3300 return fmt + 1;
3301 }
3302 return fmt;
3303}
3304
3305// Extract the format out of a format string with leading or trailing decorations
3306// fmt = "blah blah" -> return ""
3307// fmt = "%.3f" -> return fmt
3308// fmt = "hello %.3f" -> return fmt + 6
3309// fmt = "%.3f hello" -> return buf written with "%.3f"
3310const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
3311{
3312 const char* fmt_start = ImParseFormatFindStart(fmt);
3313 if (fmt_start[0] != '%')
3314 return "";
3315 const char* fmt_end = ImParseFormatFindEnd(fmt_start);
3316 if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
3317 return fmt_start;
3318 ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
3319 return buf;
3320}
3321
3322// Sanitize format
3323// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
3324// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
3325void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
3326{
3327 const char* fmt_end = ImParseFormatFindEnd(fmt_in);
3328 IM_UNUSED(fmt_out_size);
3329 IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
3330 while (fmt_in < fmt_end)
3331 {
3332 char c = *fmt_in++;
3333 if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
3334 *(fmt_out++) = c;
3335 }
3336 *fmt_out = 0; // Zero-terminate
3337}
3338
3339// - For scanning we need to remove all width and precision fields and flags "%+3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d"
3340const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
3341{
3342 const char* fmt_end = ImParseFormatFindEnd(fmt_in);
3343 const char* fmt_out_begin = fmt_out;
3344 IM_UNUSED(fmt_out_size);
3345 IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
3346 bool has_type = false;
3347 while (fmt_in < fmt_end)
3348 {
3349 char c = *fmt_in++;
3350 if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))
3351 continue;
3352 has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
3353 if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
3354 *(fmt_out++) = c;
3355 }
3356 *fmt_out = 0; // Zero-terminate
3357 return fmt_out_begin;
3358}
3359
3360template<typename TYPE>
3361static const char* ImAtoi(const char* src, TYPE* output)
3362{
3363 int negative = 0;
3364 if (*src == '-') { negative = 1; src++; }
3365 if (*src == '+') { src++; }
3366 TYPE v = 0;
3367 while (*src >= '0' && *src <= '9')
3368 v = (v * 10) + (*src++ - '0');
3369 *output = negative ? -v : v;
3370 return src;
3371}
3372
3373// Parse display precision back from the display format string
3374// 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.
3375int ImParseFormatPrecision(const char* fmt, int default_precision)
3376{
3377 fmt = ImParseFormatFindStart(fmt);
3378 if (fmt[0] != '%')
3379 return default_precision;
3380 fmt++;
3381 while (*fmt >= '0' && *fmt <= '9')
3382 fmt++;
3383 int precision = INT_MAX;
3384 if (*fmt == '.')
3385 {
3386 fmt = ImAtoi<int>(fmt + 1, &precision);
3387 if (precision < 0 || precision > 99)
3388 precision = default_precision;
3389 }
3390 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
3391 precision = -1;
3392 if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
3393 precision = -1;
3394 return (precision == INT_MAX) ? default_precision : precision;
3395}
3396
3397// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
3398// FIXME: Facilitate using this in variety of other situations.
3399bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
3400{
3401 // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
3402 // We clear ActiveID on the first frame to allow the InputText() taking it back.
3403 ImGuiContext& g = *GImGui;
3404 const bool init = (g.TempInputId != id);
3405 if (init)
3406 ClearActiveID();
3407
3408 g.CurrentWindow->DC.CursorPos = bb.Min;
3409 bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
3410 if (init)
3411 {
3412 // First frame we started displaying the InputText widget, we expect it to take the active id.
3413 IM_ASSERT(g.ActiveId == id);
3414 g.TempInputId = g.ActiveId;
3415 }
3416 return value_changed;
3417}
3418
3419// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
3420// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
3421// However this may not be ideal for all uses, as some user code may break on out of bound values.
3422bool 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)
3423{
3424 // FIXME: May need to clarify display behavior if format doesn't contain %.
3425 // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405
3426 const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
3427 char fmt_buf[32];
3428 char data_buf[32];
3429 format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
3430 if (format[0] == 0)
3431 format = type_info->PrintFmt;
3432 DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
3433 ImStrTrimBlanks(data_buf);
3434
3435 ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited;
3436
3437 bool value_changed = false;
3438 if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
3439 {
3440 // Backup old value
3441 size_t data_type_size = type_info->Size;
3442 ImGuiDataTypeTempStorage data_backup;
3443 memcpy(&data_backup, p_data, data_type_size);
3444
3445 // Apply new value (or operations) then clamp
3446 DataTypeApplyFromText(data_buf, data_type, p_data, format);
3447 if (p_clamp_min || p_clamp_max)
3448 {
3449 if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
3450 ImSwap(p_clamp_min, p_clamp_max);
3451 DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
3452 }
3453
3454 // Only mark as edited if new value is different
3455 value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
3456 if (value_changed)
3457 MarkItemEdited(id);
3458 }
3459 return value_changed;
3460}
3461
3462// 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.
3463// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
3464bool 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)
3465{
3466 ImGuiWindow* window = GetCurrentWindow();
3467 if (window->SkipItems)
3468 return false;
3469
3470 ImGuiContext& g = *GImGui;
3471 ImGuiStyle& style = g.Style;
3472
3473 if (format == NULL)
3474 format = DataTypeGetInfo(data_type)->PrintFmt;
3475
3476 char buf[64];
3477 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
3478
3479 flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
3480
3481 bool value_changed = false;
3482 if (p_step == NULL)
3483 {
3484 if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
3485 value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
3486 }
3487 else
3488 {
3489 const float button_size = GetFrameHeight();
3490
3491 BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
3492 PushID(label);
3493 SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
3494 if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
3495 value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
3496 IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
3497
3498 // Step buttons
3499 const ImVec2 backup_frame_padding = style.FramePadding;
3500 style.FramePadding.x = style.FramePadding.y;
3501 ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
3502 if (flags & ImGuiInputTextFlags_ReadOnly)
3503 BeginDisabled();
3504 SameLine(0, style.ItemInnerSpacing.x);
3505 if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
3506 {
3507 DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3508 value_changed = true;
3509 }
3510 SameLine(0, style.ItemInnerSpacing.x);
3511 if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
3512 {
3513 DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3514 value_changed = true;
3515 }
3516 if (flags & ImGuiInputTextFlags_ReadOnly)
3517 EndDisabled();
3518
3519 const char* label_end = FindRenderedTextEnd(label);
3520 if (label != label_end)
3521 {
3522 SameLine(0, style.ItemInnerSpacing.x);
3523 TextEx(label, label_end);
3524 }
3525 style.FramePadding = backup_frame_padding;
3526
3527 PopID();
3528 EndGroup();
3529 }
3530 if (value_changed)
3531 MarkItemEdited(g.LastItemData.ID);
3532
3533 return value_changed;
3534}
3535
3536bool 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)
3537{
3538 ImGuiWindow* window = GetCurrentWindow();
3539 if (window->SkipItems)
3540 return false;
3541
3542 ImGuiContext& g = *GImGui;
3543 bool value_changed = false;
3544 BeginGroup();
3545 PushID(label);
3546 PushMultiItemsWidths(components, CalcItemWidth());
3547 size_t type_size = GDataTypeInfo[data_type].Size;
3548 for (int i = 0; i < components; i++)
3549 {
3550 PushID(i);
3551 if (i > 0)
3552 SameLine(0, g.Style.ItemInnerSpacing.x);
3553 value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
3554 PopID();
3555 PopItemWidth();
3556 p_data = (void*)((char*)p_data + type_size);
3557 }
3558 PopID();
3559
3560 const char* label_end = FindRenderedTextEnd(label);
3561 if (label != label_end)
3562 {
3563 SameLine(0.0f, g.Style.ItemInnerSpacing.x);
3564 TextEx(label, label_end);
3565 }
3566
3567 EndGroup();
3568 return value_changed;
3569}
3570
3571bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
3572{
3573 return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
3574}
3575
3576bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
3577{
3578 return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
3579}
3580
3581bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
3582{
3583 return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
3584}
3585
3586bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
3587{
3588 return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
3589}
3590
3591bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
3592{
3593 // 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.
3594 const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
3595 return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
3596}
3597
3598bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
3599{
3600 return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
3601}
3602
3603bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
3604{
3605 return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
3606}
3607
3608bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
3609{
3610 return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
3611}
3612
3613bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
3614{
3615 return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
3616}
3617
3618//-------------------------------------------------------------------------
3619// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
3620//-------------------------------------------------------------------------
3621// - InputText()
3622// - InputTextWithHint()
3623// - InputTextMultiline()
3624// - InputTextGetCharInfo() [Internal]
3625// - InputTextReindexLines() [Internal]
3626// - InputTextReindexLinesRange() [Internal]
3627// - InputTextEx() [Internal]
3628// - DebugNodeInputTextState() [Internal]
3629//-------------------------------------------------------------------------
3630
3631bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3632{
3633 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
3634 return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3635}
3636
3637bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3638{
3639 return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
3640}
3641
3642bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3643{
3644 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint.
3645 return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3646}
3647
3648static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
3649{
3650 int line_count = 0;
3651 const char* s = text_begin;
3652 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
3653 if (c == '\n')
3654 line_count++;
3655 s--;
3656 if (s[0] != '\n' && s[0] != '\r')
3657 line_count++;
3658 *out_text_end = s;
3659 return line_count;
3660}
3661
3662static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
3663{
3664 ImGuiContext& g = *ctx;
3665 ImFont* font = g.Font;
3666 const float line_height = g.FontSize;
3667 const float scale = line_height / font->FontSize;
3668
3669 ImVec2 text_size = ImVec2(0, 0);
3670 float line_width = 0.0f;
3671
3672 const ImWchar* s = text_begin;
3673 while (s < text_end)
3674 {
3675 unsigned int c = (unsigned int)(*s++);
3676 if (c == '\n')
3677 {
3678 text_size.x = ImMax(text_size.x, line_width);
3679 text_size.y += line_height;
3680 line_width = 0.0f;
3681 if (stop_on_new_line)
3682 break;
3683 continue;
3684 }
3685 if (c == '\r')
3686 continue;
3687
3688 const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
3689 line_width += char_width;
3690 }
3691
3692 if (text_size.x < line_width)
3693 text_size.x = line_width;
3694
3695 if (out_offset)
3696 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
3697
3698 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
3699 text_size.y += line_height;
3700
3701 if (remaining)
3702 *remaining = s;
3703
3704 return text_size;
3705}
3706
3707// 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)
3708namespace ImStb
3709{
3710
3711static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; }
3712static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->CurLenW); return obj->TextW[idx]; }
3713static 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 IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); }
3714static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; }
3715static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
3716static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
3717{
3718 const ImWchar* text = obj->TextW.Data;
3719 const ImWchar* text_remaining = NULL;
3720 const ImVec2 size = InputTextCalcTextSizeW(obj->Ctx, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
3721 r->x0 = 0.0f;
3722 r->x1 = size.x;
3723 r->baseline_y_delta = size.y;
3724 r->ymin = 0.0f;
3725 r->ymax = size.y;
3726 r->num_chars = (int)(text_remaining - (text + line_start_idx));
3727}
3728
3729static bool is_separator(unsigned int c)
3730{
3731 return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r' || c=='.' || c=='!';
3732}
3733
3734static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
3735{
3736 // 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.
3737 if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
3738 return 0;
3739
3740 bool prev_white = ImCharIsBlankW(obj->TextW[idx - 1]);
3741 bool prev_separ = is_separator(obj->TextW[idx - 1]);
3742 bool curr_white = ImCharIsBlankW(obj->TextW[idx]);
3743 bool curr_separ = is_separator(obj->TextW[idx]);
3744 return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
3745}
3746static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
3747{
3748 if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
3749 return 0;
3750
3751 bool prev_white = ImCharIsBlankW(obj->TextW[idx]);
3752 bool prev_separ = is_separator(obj->TextW[idx]);
3753 bool curr_white = ImCharIsBlankW(obj->TextW[idx - 1]);
3754 bool curr_separ = is_separator(obj->TextW[idx - 1]);
3755 return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
3756}
3757static 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; }
3758static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(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; }
3759static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(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; }
3760static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
3761#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
3762#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
3763
3764static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
3765{
3766 ImWchar* dst = obj->TextW.Data + pos;
3767
3768 // We maintain our buffer length in both UTF-8 and wchar formats
3769 obj->Edited = true;
3770 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
3771 obj->CurLenW -= n;
3772
3773 // Offset remaining text (FIXME-OPT: Use memmove)
3774 const ImWchar* src = obj->TextW.Data + pos + n;
3775 while (ImWchar c = *src++)
3776 *dst++ = c;
3777 *dst = '\0';
3778}
3779
3780static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
3781{
3782 const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3783 const int text_len = obj->CurLenW;
3784 IM_ASSERT(pos <= text_len);
3785
3786 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
3787 if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
3788 return false;
3789
3790 // Grow internal buffer if needed
3791 if (new_text_len + text_len + 1 > obj->TextW.Size)
3792 {
3793 if (!is_resizable)
3794 return false;
3795 IM_ASSERT(text_len < obj->TextW.Size);
3796 obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
3797 }
3798
3799 ImWchar* text = obj->TextW.Data;
3800 if (pos != text_len)
3801 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
3802 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
3803
3804 obj->Edited = true;
3805 obj->CurLenW += new_text_len;
3806 obj->CurLenA += new_text_len_utf8;
3807 obj->TextW[obj->CurLenW] = '\0';
3808
3809 return true;
3810}
3811
3812// 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)
3813#define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left
3814#define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right
3815#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up
3816#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down
3817#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line
3818#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line
3819#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text
3820#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text
3821#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor
3822#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor
3823#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo
3824#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo
3825#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word
3826#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word
3827#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page
3828#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page
3829#define STB_TEXTEDIT_K_SHIFT 0x400000
3830
3831#define IMSTB_TEXTEDIT_IMPLEMENTATION
3832#define IMSTB_TEXTEDIT_memmove memmove
3833#include "imstb_textedit.h"
3834
3835// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
3836// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
3837static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
3838{
3839 stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
3840 ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
3841 state->cursor = state->select_start = state->select_end = 0;
3842 if (text_len <= 0)
3843 return;
3844 if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
3845 {
3846 state->cursor = state->select_start = state->select_end = text_len;
3847 state->has_preferred_x = 0;
3848 return;
3849 }
3850 IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
3851}
3852
3853} // namespace ImStb
3854
3855void ImGuiInputTextState::OnKeyPressed(int key)
3856{
3857 stb_textedit_key(this, &Stb, key);
3858 CursorFollow = true;
3859 CursorAnimReset();
3860}
3861
3862ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
3863{
3864 memset(this, 0, sizeof(*this));
3865}
3866
3867// Public API to manipulate UTF-8 text
3868// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
3869// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
3870void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
3871{
3872 IM_ASSERT(pos + bytes_count <= BufTextLen);
3873 char* dst = Buf + pos;
3874 const char* src = Buf + pos + bytes_count;
3875 while (char c = *src++)
3876 *dst++ = c;
3877 *dst = '\0';
3878
3879 if (CursorPos >= pos + bytes_count)
3880 CursorPos -= bytes_count;
3881 else if (CursorPos >= pos)
3882 CursorPos = pos;
3883 SelectionStart = SelectionEnd = CursorPos;
3884 BufDirty = true;
3885 BufTextLen -= bytes_count;
3886}
3887
3888void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
3889{
3890 // Accept null ranges
3891 if (new_text == new_text_end)
3892 return;
3893
3894 const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3895 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
3896 if (new_text_len + BufTextLen >= BufSize)
3897 {
3898 if (!is_resizable)
3899 return;
3900
3901 // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
3902 ImGuiContext& g = *Ctx;
3903 ImGuiInputTextState* edit_state = &g.InputTextState;
3904 IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
3905 IM_ASSERT(Buf == edit_state->TextA.Data);
3906 int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
3907 edit_state->TextA.reserve(new_buf_size + 1);
3908 Buf = edit_state->TextA.Data;
3909 BufSize = edit_state->BufCapacityA = new_buf_size;
3910 }
3911
3912 if (BufTextLen != pos)
3913 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
3914 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
3915 Buf[BufTextLen + new_text_len] = '\0';
3916
3917 if (CursorPos >= pos)
3918 CursorPos += new_text_len;
3919 SelectionStart = SelectionEnd = CursorPos;
3920 BufDirty = true;
3921 BufTextLen += new_text_len;
3922}
3923
3924// Return false to discard a character.
3925static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source)
3926{
3927 IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard);
3928 unsigned int c = *p_char;
3929
3930 // Filter non-printable (NB: isprint is unreliable! see #2467)
3931 bool apply_named_filters = true;
3932 if (c < 0x20)
3933 {
3934 bool pass = false;
3935 pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
3936 pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
3937 if (!pass)
3938 return false;
3939 apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
3940 }
3941
3942 if (input_source != ImGuiInputSource_Clipboard)
3943 {
3944 // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
3945 if (c == 127)
3946 return false;
3947
3948 // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
3949 if (c >= 0xE000 && c <= 0xF8FF)
3950 return false;
3951 }
3952
3953 // Filter Unicode ranges we are not handling in this build
3954 if (c > IM_UNICODE_CODEPOINT_MAX)
3955 return false;
3956
3957 // Generic named filters
3958 if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)))
3959 {
3960 // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.
3961 // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
3962 // 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.
3963 // Change the default decimal_point with:
3964 // ImGui::GetIO()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
3965 // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.
3966 ImGuiContext& g = *ctx;
3967 const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint;
3968 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific))
3969 if (c == '.' || c == ',')
3970 c = c_decimal_point;
3971
3972 // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
3973 // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
3974 // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
3975 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
3976 if (c >= 0xFF01 && c <= 0xFF5E)
3977 c = c - 0xFF01 + 0x21;
3978
3979 // Allow 0-9 . - + * /
3980 if (flags & ImGuiInputTextFlags_CharsDecimal)
3981 if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
3982 return false;
3983
3984 // Allow 0-9 . - + * / e E
3985 if (flags & ImGuiInputTextFlags_CharsScientific)
3986 if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
3987 return false;
3988
3989 // Allow 0-9 a-F A-F
3990 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
3991 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
3992 return false;
3993
3994 // Turn a-z into A-Z
3995 if (flags & ImGuiInputTextFlags_CharsUppercase)
3996 if (c >= 'a' && c <= 'z')
3997 c += (unsigned int)('A' - 'a');
3998
3999 if (flags & ImGuiInputTextFlags_CharsNoBlank)
4000 if (ImCharIsBlankW(c))
4001 return false;
4002
4003 *p_char = c;
4004 }
4005
4006 // Custom callback filter
4007 if (flags & ImGuiInputTextFlags_CallbackCharFilter)
4008 {
4009 ImGuiContext& g = *GImGui;
4010 ImGuiInputTextCallbackData callback_data;
4011 callback_data.Ctx = &g;
4012 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
4013 callback_data.EventChar = (ImWchar)c;
4014 callback_data.Flags = flags;
4015 callback_data.UserData = user_data;
4016 if (callback(&callback_data) != 0)
4017 return false;
4018 *p_char = callback_data.EventChar;
4019 if (!callback_data.EventChar)
4020 return false;
4021 }
4022
4023 return true;
4024}
4025
4026// Find the shortest single replacement we can make to get the new text from the old text.
4027// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end.
4028// FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly.
4029static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a)
4030{
4031 ImGuiContext& g = *GImGui;
4032 const ImWchar* old_buf = state->TextW.Data;
4033 const int old_length = state->CurLenW;
4034 const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a);
4035 g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar));
4036 ImWchar* new_buf = (ImWchar*)(void*)g.TempBuffer.Data;
4037 ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a);
4038
4039 const int shorter_length = ImMin(old_length, new_length);
4040 int first_diff;
4041 for (first_diff = 0; first_diff < shorter_length; first_diff++)
4042 if (old_buf[first_diff] != new_buf[first_diff])
4043 break;
4044 if (first_diff == old_length && first_diff == new_length)
4045 return;
4046
4047 int old_last_diff = old_length - 1;
4048 int new_last_diff = new_length - 1;
4049 for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
4050 if (old_buf[old_last_diff] != new_buf[new_last_diff])
4051 break;
4052
4053 const int insert_len = new_last_diff - first_diff + 1;
4054 const int delete_len = old_last_diff - first_diff + 1;
4055 if (insert_len > 0 || delete_len > 0)
4056 if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len))
4057 for (int i = 0; i < delete_len; i++)
4058 p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i);
4059}
4060
4061// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)
4062// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)
4063// It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick,
4064// but that more likely be attractive when we do have _NoLiveEdit flag available.
4065void ImGui::InputTextDeactivateHook(ImGuiID id)
4066{
4067 ImGuiContext& g = *GImGui;
4068 ImGuiInputTextState* state = &g.InputTextState;
4069 if (id == 0 || state->ID != id)
4070 return;
4071 g.InputTextDeactivatedState.ID = state->ID;
4072 if (state->Flags & ImGuiInputTextFlags_ReadOnly)
4073 {
4074 g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.
4075 }
4076 else
4077 {
4078 IM_ASSERT(state->TextA.Data != 0);
4079 g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1);
4080 memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1);
4081 }
4082}
4083
4084// Edit a string of text
4085// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
4086// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
4087// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
4088// - 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.
4089// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
4090// (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
4091// 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)
4092bool 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)
4093{
4094 ImGuiWindow* window = GetCurrentWindow();
4095 if (window->SkipItems)
4096 return false;
4097
4098 IM_ASSERT(buf != NULL && buf_size >= 0);
4099 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
4100 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
4101
4102 ImGuiContext& g = *GImGui;
4103 ImGuiIO& io = g.IO;
4104 const ImGuiStyle& style = g.Style;
4105
4106 const bool RENDER_SELECTION_WHEN_INACTIVE = false;
4107 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
4108
4109 if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
4110 BeginGroup();
4111 const ImGuiID id = window->GetID(label);
4112 const ImVec2 label_size = CalcTextSize(label, NULL, true);
4113 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
4114 const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
4115
4116 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
4117 const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
4118
4119 ImGuiWindow* draw_window = window;
4120 ImVec2 inner_size = frame_size;
4121 ImGuiLastItemData item_data_backup;
4122 if (is_multiline)
4123 {
4124 ImVec2 backup_pos = window->DC.CursorPos;
4125 ItemSize(total_bb, style.FramePadding.y);
4126 if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
4127 {
4128 EndGroup();
4129 return false;
4130 }
4131 item_data_backup = g.LastItemData;
4132 window->DC.CursorPos = backup_pos;
4133
4134 // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
4135 if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
4136 g.NavActivateId = 0;
4137
4138 // Prevent NavActivate reactivating in BeginChild() when we are already active.
4139 const ImGuiID backup_activate_id = g.NavActivateId;
4140 if (g.ActiveId == id) // Prevent reactivation
4141 g.NavActivateId = 0;
4142
4143 // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
4144 PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
4145 PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
4146 PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
4147 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
4148 bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove);
4149 g.NavActivateId = backup_activate_id;
4150 PopStyleVar(3);
4151 PopStyleColor();
4152 if (!child_visible)
4153 {
4154 EndChild();
4155 EndGroup();
4156 return false;
4157 }
4158 draw_window = g.CurrentWindow; // Child window
4159 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.
4160 draw_window->DC.CursorPos += style.FramePadding;
4161 inner_size.x -= draw_window->ScrollbarSizes.x;
4162 }
4163 else
4164 {
4165 // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
4166 ItemSize(total_bb, style.FramePadding.y);
4167 if (!(flags & ImGuiInputTextFlags_MergedItem))
4168 if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
4169 return false;
4170 }
4171 const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
4172 if (hovered)
4173 g.MouseCursor = ImGuiMouseCursor_TextInput;
4174
4175 // We are only allowed to access the state if we are already the active widget.
4176 ImGuiInputTextState* state = GetInputTextState(id);
4177
4178 if (g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly)
4179 flags |= ImGuiInputTextFlags_ReadOnly;
4180 const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
4181 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
4182 const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
4183 const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
4184 if (is_resizable)
4185 IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
4186
4187 const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));
4188
4189 const bool user_clicked = hovered && io.MouseClicked[0];
4190 const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4191 const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4192 bool clear_active_id = false;
4193 bool select_all = false;
4194
4195 float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
4196
4197 const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
4198 const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); // state != NULL means its our state.
4199 const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
4200 const bool init_state = (init_make_active || user_scroll_active);
4201 if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
4202 {
4203 // Access state even if we don't own it yet.
4204 state = &g.InputTextState;
4205 state->CursorAnimReset();
4206 state->ReloadUserBuf = false;
4207
4208 // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
4209 InputTextDeactivateHook(state->ID);
4210
4211 // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
4212 const int buf_len = (int)strlen(buf);
4213 if (!init_reload_from_user_buf)
4214 {
4215 // Take a copy of the initial buffer value.
4216 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.
4217 memcpy(state->InitialTextA.Data, buf, buf_len + 1);
4218 }
4219
4220 // Preserve cursor position and undo/redo stack if we come back to same widget
4221 // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
4222 bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
4223 if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0)))
4224 recycle_state = false;
4225
4226 // Start edition
4227 const char* buf_end = NULL;
4228 state->ID = id;
4229 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.
4230 state->TextA.resize(0);
4231 state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then)
4232 state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
4233 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.
4234
4235 if (recycle_state)
4236 {
4237 // Recycle existing cursor/selection/undo stack but clamp position
4238 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
4239 state->CursorClamp();
4240 }
4241 else
4242 {
4243 state->ScrollX = 0.0f;
4244 stb_textedit_initialize_state(&state->Stb, !is_multiline);
4245 }
4246
4247 if (init_reload_from_user_buf)
4248 {
4249 state->Stb.select_start = state->ReloadSelectionStart;
4250 state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd;
4251 state->CursorClamp();
4252 }
4253 else if (!is_multiline)
4254 {
4255 if (flags & ImGuiInputTextFlags_AutoSelectAll)
4256 select_all = true;
4257 if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
4258 select_all = true;
4259 if (user_clicked && io.KeyCtrl)
4260 select_all = true;
4261 }
4262
4263 if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
4264 state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863)
4265 }
4266
4267 const bool is_osx = io.ConfigMacOSXBehaviors;
4268 if (g.ActiveId != id && init_make_active)
4269 {
4270 IM_ASSERT(state && state->ID == id);
4271 SetActiveID(id, window);
4272 SetFocusID(id, window);
4273 FocusWindow(window);
4274 }
4275 if (g.ActiveId == id)
4276 {
4277 // Declare some inputs, the other are registered and polled via Shortcut() routing system.
4278 if (user_clicked)
4279 SetKeyOwner(ImGuiKey_MouseLeft, id);
4280 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
4281 if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
4282 g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
4283 SetKeyOwner(ImGuiKey_Enter, id);
4284 SetKeyOwner(ImGuiKey_KeypadEnter, id);
4285 SetKeyOwner(ImGuiKey_Home, id);
4286 SetKeyOwner(ImGuiKey_End, id);
4287 if (is_multiline)
4288 {
4289 SetKeyOwner(ImGuiKey_PageUp, id);
4290 SetKeyOwner(ImGuiKey_PageDown, id);
4291 }
4292 if (is_osx)
4293 SetKeyOwner(ImGuiMod_Alt, id);
4294 }
4295
4296 // 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)
4297 if (g.ActiveId == id && state == NULL)
4298 ClearActiveID();
4299
4300 // Release focus when we click outside
4301 if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
4302 clear_active_id = true;
4303
4304 // Lock the decision of whether we are going to take the path displaying the cursor or selection
4305 bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
4306 bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4307 bool value_changed = false;
4308 bool validated = false;
4309
4310 // When read-only we always use the live data passed to the function
4311 // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
4312 if (is_readonly && state != NULL && (render_cursor || render_selection))
4313 {
4314 const char* buf_end = NULL;
4315 state->TextW.resize(buf_size + 1);
4316 state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
4317 state->CurLenA = (int)(buf_end - buf);
4318 state->CursorClamp();
4319 render_selection &= state->HasSelection();
4320 }
4321
4322 // Select the buffer to render.
4323 const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
4324 const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
4325
4326 // Password pushes a temporary font with only a fallback glyph
4327 if (is_password && !is_displaying_hint)
4328 {
4329 const ImFontGlyph* glyph = g.Font->FindGlyph('*');
4330 ImFont* password_font = &g.InputTextPasswordFont;
4331 password_font->FontSize = g.Font->FontSize;
4332 password_font->Scale = g.Font->Scale;
4333 password_font->Ascent = g.Font->Ascent;
4334 password_font->Descent = g.Font->Descent;
4335 password_font->ContainerAtlas = g.Font->ContainerAtlas;
4336 password_font->FallbackGlyph = glyph;
4337 password_font->FallbackAdvanceX = glyph->AdvanceX;
4338 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
4339 PushFont(password_font);
4340 }
4341
4342 // Process mouse inputs and character inputs
4343 int backup_current_text_length = 0;
4344 if (g.ActiveId == id)
4345 {
4346 IM_ASSERT(state != NULL);
4347 backup_current_text_length = state->CurLenA;
4348 state->Edited = false;
4349 state->BufCapacityA = buf_size;
4350 state->Flags = flags;
4351
4352 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
4353 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
4354 g.ActiveIdAllowOverlap = !io.MouseDown[0];
4355
4356 // Edit in progress
4357 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX;
4358 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
4359
4360 if (select_all)
4361 {
4362 state->SelectAll();
4363 state->SelectedAllMouseLock = true;
4364 }
4365 else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
4366 {
4367 stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
4368 const int multiclick_count = (io.MouseClickedCount[0] - 2);
4369 if ((multiclick_count % 2) == 0)
4370 {
4371 // Double-click: Select word
4372 // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant:
4373 // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS)
4374 const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n';
4375 if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol)
4376 state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
4377 //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
4378 if (!STB_TEXT_HAS_SELECTION(&state->Stb))
4379 ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb);
4380 state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor);
4381 state->Stb.select_end = state->Stb.cursor;
4382 ImStb::stb_textedit_clamp(state, &state->Stb);
4383 }
4384 else
4385 {
4386 // Triple-click: Select line
4387 const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n';
4388 state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
4389 state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
4390 state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
4391 if (!is_eol && is_multiline)
4392 {
4393 ImSwap(state->Stb.select_start, state->Stb.select_end);
4394 state->Stb.cursor = state->Stb.select_end;
4395 }
4396 state->CursorFollow = false;
4397 }
4398 state->CursorAnimReset();
4399 }
4400 else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
4401 {
4402 if (hovered)
4403 {
4404 if (io.KeyShift)
4405 stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
4406 else
4407 stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
4408 state->CursorAnimReset();
4409 }
4410 }
4411 else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
4412 {
4413 stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
4414 state->CursorAnimReset();
4415 state->CursorFollow = true;
4416 }
4417 if (state->SelectedAllMouseLock && !io.MouseDown[0])
4418 state->SelectedAllMouseLock = false;
4419
4420 // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
4421 // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)
4422 if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
4423 {
4424 if (Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat))
4425 {
4426 unsigned int c = '\t'; // Insert TAB
4427 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4428 state->OnKeyPressed((int)c);
4429 }
4430 // FIXME: Implement Shift+Tab
4431 /*
4432 if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, id, ImGuiInputFlags_Repeat))
4433 {
4434 }
4435 */
4436 }
4437
4438 // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
4439 // 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.
4440 const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
4441 if (io.InputQueueCharacters.Size > 0)
4442 {
4443 if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
4444 for (int n = 0; n < io.InputQueueCharacters.Size; n++)
4445 {
4446 // Insert character if they pass filtering
4447 unsigned int c = (unsigned int)io.InputQueueCharacters[n];
4448 if (c == '\t') // Skip Tab, see above.
4449 continue;
4450 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4451 state->OnKeyPressed((int)c);
4452 }
4453
4454 // Consume characters
4455 io.InputQueueCharacters.resize(0);
4456 }
4457 }
4458
4459 // Process other shortcuts/key-presses
4460 bool revert_edit = false;
4461 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
4462 {
4463 IM_ASSERT(state != NULL);
4464
4465 const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
4466 state->Stb.row_count_per_page = row_count_per_page;
4467
4468 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
4469 const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
4470 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
4471
4472 // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText)
4473 // Otherwise we could simply assume that we own the keys as we are active.
4474 const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
4475 const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, f_repeat)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
4476 const bool is_copy = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_C, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, id)) && !is_password && (!is_multiline || state->HasSelection());
4477 const bool is_paste = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_V, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, id, f_repeat)) && !is_readonly;
4478 const bool is_undo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Z, id, f_repeat)) && !is_readonly && is_undoable;
4479 const bool is_redo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Y, id, f_repeat) || (is_osx && Shortcut(ImGuiMod_Shortcut | ImGuiMod_Shift | ImGuiKey_Z, id, f_repeat))) && !is_readonly && is_undoable;
4480 const bool is_select_all = Shortcut(ImGuiMod_Shortcut | ImGuiKey_A, id);
4481
4482 // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
4483 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
4484 const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
4485 const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
4486 const bool is_cancel = Shortcut(ImGuiKey_Escape, id, f_repeat) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, id, f_repeat));
4487
4488 // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of.
4489 if (IsKeyPressed(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); }
4490 else if (IsKeyPressed(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); }
4491 else if (IsKeyPressed(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); }
4492 else if (IsKeyPressed(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); }
4493 else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
4494 else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
4495 else if (IsKeyPressed(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
4496 else if (IsKeyPressed(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
4497 else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
4498 {
4499 if (!state->HasSelection())
4500 {
4501 // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace)
4502 if (is_wordmove_key_down)
4503 state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
4504 }
4505 state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
4506 }
4507 else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
4508 {
4509 if (!state->HasSelection())
4510 {
4511 if (is_wordmove_key_down)
4512 state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
4513 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl)
4514 state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
4515 }
4516 state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
4517 }
4518 else if (is_enter_pressed || is_gamepad_validate)
4519 {
4520 // Determine if we turn Enter into a \n character
4521 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
4522 if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
4523 {
4524 validated = true;
4525 if (io.ConfigInputTextEnterKeepActive && !is_multiline)
4526 state->SelectAll(); // No need to scroll
4527 else
4528 clear_active_id = true;
4529 }
4530 else if (!is_readonly)
4531 {
4532 unsigned int c = '\n'; // Insert new line
4533 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
4534 state->OnKeyPressed((int)c);
4535 }
4536 }
4537 else if (is_cancel)
4538 {
4539 if (flags & ImGuiInputTextFlags_EscapeClearsAll)
4540 {
4541 if (buf[0] != 0)
4542 {
4543 revert_edit = true;
4544 }
4545 else
4546 {
4547 render_cursor = render_selection = false;
4548 clear_active_id = true;
4549 }
4550 }
4551 else
4552 {
4553 clear_active_id = revert_edit = true;
4554 render_cursor = render_selection = false;
4555 }
4556 }
4557 else if (is_undo || is_redo)
4558 {
4559 state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
4560 state->ClearSelection();
4561 }
4562 else if (is_select_all)
4563 {
4564 state->SelectAll();
4565 state->CursorFollow = true;
4566 }
4567 else if (is_cut || is_copy)
4568 {
4569 // Cut, Copy
4570 if (io.SetClipboardTextFn)
4571 {
4572 const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
4573 const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
4574 const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
4575 char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
4576 ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
4577 SetClipboardText(clipboard_data);
4578 MemFree(clipboard_data);
4579 }
4580 if (is_cut)
4581 {
4582 if (!state->HasSelection())
4583 state->SelectAll();
4584 state->CursorFollow = true;
4585 stb_textedit_cut(state, &state->Stb);
4586 }
4587 }
4588 else if (is_paste)
4589 {
4590 if (const char* clipboard = GetClipboardText())
4591 {
4592 // Filter pasted buffer
4593 const int clipboard_len = (int)strlen(clipboard);
4594 ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar));
4595 int clipboard_filtered_len = 0;
4596 for (const char* s = clipboard; *s != 0; )
4597 {
4598 unsigned int c;
4599 s += ImTextCharFromUtf8(&c, s, NULL);
4600 if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
4601 continue;
4602 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
4603 }
4604 clipboard_filtered[clipboard_filtered_len] = 0;
4605 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
4606 {
4607 stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
4608 state->CursorFollow = true;
4609 }
4610 MemFree(clipboard_filtered);
4611 }
4612 }
4613
4614 // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
4615 render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4616 }
4617
4618 // Process callbacks and apply result back to user's buffer.
4619 const char* apply_new_text = NULL;
4620 int apply_new_text_length = 0;
4621 if (g.ActiveId == id)
4622 {
4623 IM_ASSERT(state != NULL);
4624 if (revert_edit && !is_readonly)
4625 {
4626 if (flags & ImGuiInputTextFlags_EscapeClearsAll)
4627 {
4628 // Clear input
4629 IM_ASSERT(buf[0] != 0);
4630 apply_new_text = "";
4631 apply_new_text_length = 0;
4632 value_changed = true;
4633 IMSTB_TEXTEDIT_CHARTYPE empty_string;
4634 stb_textedit_replace(state, &state->Stb, &empty_string, 0);
4635 }
4636 else if (strcmp(buf, state->InitialTextA.Data) != 0)
4637 {
4638 // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
4639 // Push records into the undo stack so we can CTRL+Z the revert operation itself
4640 apply_new_text = state->InitialTextA.Data;
4641 apply_new_text_length = state->InitialTextA.Size - 1;
4642 value_changed = true;
4643 ImVector<ImWchar> w_text;
4644 if (apply_new_text_length > 0)
4645 {
4646 w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1);
4647 ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length);
4648 }
4649 stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0);
4650 }
4651 }
4652
4653 // Apply ASCII value
4654 if (!is_readonly)
4655 {
4656 state->TextAIsValid = true;
4657 state->TextA.resize(state->TextW.Size * 4 + 1);
4658 ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
4659 }
4660
4661 // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer
4662 // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
4663 // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
4664 // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage
4665 // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object
4666 // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
4667 const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
4668 if (apply_edit_back_to_user_buffer)
4669 {
4670 // Apply new value immediately - copy modified buffer back
4671 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
4672 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
4673 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
4674
4675 // User callback
4676 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
4677 {
4678 IM_ASSERT(callback != NULL);
4679
4680 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
4681 ImGuiInputTextFlags event_flag = 0;
4682 ImGuiKey event_key = ImGuiKey_None;
4683 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, id))
4684 {
4685 event_flag = ImGuiInputTextFlags_CallbackCompletion;
4686 event_key = ImGuiKey_Tab;
4687 }
4688 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
4689 {
4690 event_flag = ImGuiInputTextFlags_CallbackHistory;
4691 event_key = ImGuiKey_UpArrow;
4692 }
4693 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
4694 {
4695 event_flag = ImGuiInputTextFlags_CallbackHistory;
4696 event_key = ImGuiKey_DownArrow;
4697 }
4698 else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
4699 {
4700 event_flag = ImGuiInputTextFlags_CallbackEdit;
4701 }
4702 else if (flags & ImGuiInputTextFlags_CallbackAlways)
4703 {
4704 event_flag = ImGuiInputTextFlags_CallbackAlways;
4705 }
4706
4707 if (event_flag)
4708 {
4709 ImGuiInputTextCallbackData callback_data;
4710 callback_data.Ctx = &g;
4711 callback_data.EventFlag = event_flag;
4712 callback_data.Flags = flags;
4713 callback_data.UserData = callback_user_data;
4714
4715 char* callback_buf = is_readonly ? buf : state->TextA.Data;
4716 callback_data.EventKey = event_key;
4717 callback_data.Buf = callback_buf;
4718 callback_data.BufTextLen = state->CurLenA;
4719 callback_data.BufSize = state->BufCapacityA;
4720 callback_data.BufDirty = false;
4721
4722 // 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)
4723 ImWchar* text = state->TextW.Data;
4724 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
4725 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
4726 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
4727
4728 // Call user code
4729 callback(&callback_data);
4730
4731 // Read back what user may have modified
4732 callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
4733 IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields
4734 IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
4735 IM_ASSERT(callback_data.Flags == flags);
4736 const bool buf_dirty = callback_data.BufDirty;
4737 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; }
4738 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); }
4739 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); }
4740 if (buf_dirty)
4741 {
4742 IM_ASSERT(!is_readonly);
4743 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
4744 InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ?
4745 if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
4746 state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize
4747 state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
4748 state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
4749 state->CursorAnimReset();
4750 }
4751 }
4752 }
4753
4754 // Will copy result string if modified
4755 if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
4756 {
4757 apply_new_text = state->TextA.Data;
4758 apply_new_text_length = state->CurLenA;
4759 value_changed = true;
4760 }
4761 }
4762 }
4763
4764 // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
4765 if (g.InputTextDeactivatedState.ID == id)
4766 {
4767 if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
4768 {
4769 apply_new_text = g.InputTextDeactivatedState.TextA.Data;
4770 apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
4771 value_changed = true;
4772 //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
4773 }
4774 g.InputTextDeactivatedState.ID = 0;
4775 }
4776
4777 // Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
4778 if (apply_new_text != NULL)
4779 {
4780 // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
4781 // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
4782 // without any storage on user's side.
4783 IM_ASSERT(apply_new_text_length >= 0);
4784 if (is_resizable)
4785 {
4786 ImGuiInputTextCallbackData callback_data;
4787 callback_data.Ctx = &g;
4788 callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
4789 callback_data.Flags = flags;
4790 callback_data.Buf = buf;
4791 callback_data.BufTextLen = apply_new_text_length;
4792 callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
4793 callback_data.UserData = callback_user_data;
4794 callback(&callback_data);
4795 buf = callback_data.Buf;
4796 buf_size = callback_data.BufSize;
4797 apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
4798 IM_ASSERT(apply_new_text_length <= buf_size);
4799 }
4800 //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
4801
4802 // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
4803 ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
4804 }
4805
4806 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
4807 // Otherwise request text input ahead for next frame.
4808 if (g.ActiveId == id && clear_active_id)
4809 ClearActiveID();
4810 else if (g.ActiveId == id)
4811 g.WantTextInputNextFrame = 1;
4812
4813 // Render frame
4814 if (!is_multiline)
4815 {
4816 RenderNavHighlight(frame_bb, id);
4817 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
4818 }
4819
4820 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
4821 ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
4822 ImVec2 text_size(0.0f, 0.0f);
4823
4824 // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
4825 // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
4826 // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
4827 const int buf_display_max_length = 2 * 1024 * 1024;
4828 const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
4829 const char* buf_display_end = NULL; // We have specialized paths below for setting the length
4830 if (is_displaying_hint)
4831 {
4832 buf_display = hint;
4833 buf_display_end = hint + strlen(hint);
4834 }
4835
4836 // Render text. We currently only render selection when the widget is active or while scrolling.
4837 // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
4838 if (render_cursor || render_selection)
4839 {
4840 IM_ASSERT(state != NULL);
4841 if (!is_displaying_hint)
4842 buf_display_end = buf_display + state->CurLenA;
4843
4844 // Render text (with cursor and selection)
4845 // This is going to be messy. We need to:
4846 // - Display the text (this alone can be more easily clipped)
4847 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
4848 // - Measure text height (for scrollbar)
4849 // 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)
4850 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
4851 const ImWchar* text_begin = state->TextW.Data;
4852 ImVec2 cursor_offset, select_start_offset;
4853
4854 {
4855 // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
4856 const ImWchar* searches_input_ptr[2] = { NULL, NULL };
4857 int searches_result_line_no[2] = { -1000, -1000 };
4858 int searches_remaining = 0;
4859 if (render_cursor)
4860 {
4861 searches_input_ptr[0] = text_begin + state->Stb.cursor;
4862 searches_result_line_no[0] = -1;
4863 searches_remaining++;
4864 }
4865 if (render_selection)
4866 {
4867 searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
4868 searches_result_line_no[1] = -1;
4869 searches_remaining++;
4870 }
4871
4872 // Iterate all lines to find our line numbers
4873 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
4874 searches_remaining += is_multiline ? 1 : 0;
4875 int line_count = 0;
4876 //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
4877 for (const ImWchar* s = text_begin; *s != 0; s++)
4878 if (*s == '\n')
4879 {
4880 line_count++;
4881 if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
4882 if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
4883 }
4884 line_count++;
4885 if (searches_result_line_no[0] == -1)
4886 searches_result_line_no[0] = line_count;
4887 if (searches_result_line_no[1] == -1)
4888 searches_result_line_no[1] = line_count;
4889
4890 // Calculate 2d position by finding the beginning of the line and measuring distance
4891 cursor_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
4892 cursor_offset.y = searches_result_line_no[0] * g.FontSize;
4893 if (searches_result_line_no[1] >= 0)
4894 {
4895 select_start_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
4896 select_start_offset.y = searches_result_line_no[1] * g.FontSize;
4897 }
4898
4899 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
4900 if (is_multiline)
4901 text_size = ImVec2(inner_size.x, line_count * g.FontSize);
4902 }
4903
4904 // Scroll
4905 if (render_cursor && state->CursorFollow)
4906 {
4907 // Horizontal scroll in chunks of quarter width
4908 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
4909 {
4910 const float scroll_increment_x = inner_size.x * 0.25f;
4911 const float visible_width = inner_size.x - style.FramePadding.x;
4912 if (cursor_offset.x < state->ScrollX)
4913 state->ScrollX = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
4914 else if (cursor_offset.x - visible_width >= state->ScrollX)
4915 state->ScrollX = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
4916 }
4917 else
4918 {
4919 state->ScrollX = 0.0f;
4920 }
4921
4922 // Vertical scroll
4923 if (is_multiline)
4924 {
4925 // Test if cursor is vertically visible
4926 if (cursor_offset.y - g.FontSize < scroll_y)
4927 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
4928 else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
4929 scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
4930 const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
4931 scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
4932 draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
4933 draw_window->Scroll.y = scroll_y;
4934 }
4935
4936 state->CursorFollow = false;
4937 }
4938
4939 // Draw selection
4940 const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
4941 if (render_selection)
4942 {
4943 const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
4944 const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
4945
4946 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.
4947 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.
4948 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
4949 ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
4950 for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
4951 {
4952 if (rect_pos.y > clip_rect.w + g.FontSize)
4953 break;
4954 if (rect_pos.y < clip_rect.y)
4955 {
4956 //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit
4957 //p = p ? p + 1 : text_selected_end;
4958 while (p < text_selected_end)
4959 if (*p++ == '\n')
4960 break;
4961 }
4962 else
4963 {
4964 ImVec2 rect_size = InputTextCalcTextSizeW(&g, p, text_selected_end, &p, NULL, true);
4965 if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
4966 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
4967 rect.ClipWith(clip_rect);
4968 if (rect.Overlaps(clip_rect))
4969 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
4970 }
4971 rect_pos.x = draw_pos.x - draw_scroll.x;
4972 rect_pos.y += g.FontSize;
4973 }
4974 }
4975
4976 // 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.
4977 if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
4978 {
4979 ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
4980 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);
4981 }
4982
4983 // Draw blinking cursor
4984 if (render_cursor)
4985 {
4986 state->CursorAnim += io.DeltaTime;
4987 bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
4988 ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
4989 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);
4990 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
4991 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
4992
4993 // 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.)
4994 if (!is_readonly)
4995 {
4996 g.PlatformImeData.WantVisible = true;
4997 g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
4998 g.PlatformImeData.InputLineHeight = g.FontSize;
4999 g.PlatformImeViewport = window->Viewport->ID;
5000 }
5001 }
5002 }
5003 else
5004 {
5005 // Render text only (no selection, no cursor)
5006 if (is_multiline)
5007 text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
5008 else if (!is_displaying_hint && g.ActiveId == id)
5009 buf_display_end = buf_display + state->CurLenA;
5010 else if (!is_displaying_hint)
5011 buf_display_end = buf_display + strlen(buf_display);
5012
5013 if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
5014 {
5015 ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
5016 draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
5017 }
5018 }
5019
5020 if (is_password && !is_displaying_hint)
5021 PopFont();
5022
5023 if (is_multiline)
5024 {
5025 // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (ref issue #4761)...
5026 Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
5027 g.NextItemData.ItemFlags |= ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
5028 EndChild();
5029 item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
5030
5031 // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active...
5032 // FIXME: This quite messy/tricky, should attempt to get rid of the child window.
5033 EndGroup();
5034 if (g.LastItemData.ID == 0)
5035 {
5036 g.LastItemData.ID = id;
5037 g.LastItemData.InFlags = item_data_backup.InFlags;
5038 g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
5039 }
5040 }
5041
5042 // Log as text
5043 if (g.LogEnabled && (!is_password || is_displaying_hint))
5044 {
5045 LogSetNextTextDecoration("{", "}");
5046 LogRenderedText(&draw_pos, buf_display, buf_display_end);
5047 }
5048
5049 if (label_size.x > 0)
5050 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
5051
5052 if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited))
5053 MarkItemEdited(id);
5054
5055 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
5056 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
5057 return validated;
5058 else
5059 return value_changed;
5060}
5061
5062void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
5063{
5064#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5065 ImGuiContext& g = *GImGui;
5066 ImStb::STB_TexteditState* stb_state = &state->Stb;
5067 ImStb::StbUndoState* undo_state = &stb_state->undostate;
5068 Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
5069 DebugLocateItemOnHover(state->ID);
5070 Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenW, state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end);
5071 Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
5072 Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point);
5073 if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY)) // Visualize undo state
5074 {
5075 PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
5076 for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)
5077 {
5078 ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
5079 const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';
5080 if (undo_rec_type == ' ')
5081 BeginDisabled();
5082 char buf[64] = "";
5083 if (undo_rec_type != ' ' && undo_rec->char_storage != -1)
5084 ImTextStrToUtf8(buf, IM_ARRAYSIZE(buf), undo_state->undo_char + undo_rec->char_storage, undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length);
5085 Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"",
5086 undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf);
5087 if (undo_rec_type == ' ')
5088 EndDisabled();
5089 }
5090 PopStyleVar();
5091 }
5092 EndChild();
5093#else
5094 IM_UNUSED(state);
5095#endif
5096}
5097
5098//-------------------------------------------------------------------------
5099// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
5100//-------------------------------------------------------------------------
5101// - ColorEdit3()
5102// - ColorEdit4()
5103// - ColorPicker3()
5104// - RenderColorRectWithAlphaCheckerboard() [Internal]
5105// - ColorPicker4()
5106// - ColorButton()
5107// - SetColorEditOptions()
5108// - ColorTooltip() [Internal]
5109// - ColorEditOptionsPopup() [Internal]
5110// - ColorPickerOptionsPopup() [Internal]
5111//-------------------------------------------------------------------------
5112
5113bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
5114{
5115 return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
5116}
5117
5118static void ColorEditRestoreH(const float* col, float* H)
5119{
5120 ImGuiContext& g = *GImGui;
5121 IM_ASSERT(g.ColorEditCurrentID != 0);
5122 if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
5123 return;
5124 *H = g.ColorEditSavedHue;
5125}
5126
5127// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.
5128// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting.
5129static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
5130{
5131 ImGuiContext& g = *GImGui;
5132 IM_ASSERT(g.ColorEditCurrentID != 0);
5133 if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
5134 return;
5135
5136 // When S == 0, H is undefined.
5137 // When H == 1 it wraps around to 0.
5138 if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
5139 *H = g.ColorEditSavedHue;
5140
5141 // When V == 0, S is undefined.
5142 if (*V == 0.0f)
5143 *S = g.ColorEditSavedSat;
5144}
5145
5146// Edit colors components (each component in 0.0f..1.0f range).
5147// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5148// 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.
5149bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
5150{
5151 ImGuiWindow* window = GetCurrentWindow();
5152 if (window->SkipItems)
5153 return false;
5154
5155 ImGuiContext& g = *GImGui;
5156 const ImGuiStyle& style = g.Style;
5157 const float square_sz = GetFrameHeight();
5158 const char* label_display_end = FindRenderedTextEnd(label);
5159 float w_full = CalcItemWidth();
5160 g.NextItemData.ClearFlags();
5161
5162 BeginGroup();
5163 PushID(label);
5164 const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
5165 if (set_current_color_edit_id)
5166 g.ColorEditCurrentID = window->IDStack.back();
5167
5168 // If we're not showing any slider there's no point in doing any HSV conversions
5169 const ImGuiColorEditFlags flags_untouched = flags;
5170 if (flags & ImGuiColorEditFlags_NoInputs)
5171 flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
5172
5173 // Context menu: display and modify options (before defaults are applied)
5174 if (!(flags & ImGuiColorEditFlags_NoOptions))
5175 ColorEditOptionsPopup(col, flags);
5176
5177 // Read stored options
5178 if (!(flags & ImGuiColorEditFlags_DisplayMask_))
5179 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
5180 if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
5181 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
5182 if (!(flags & ImGuiColorEditFlags_PickerMask_))
5183 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
5184 if (!(flags & ImGuiColorEditFlags_InputMask_))
5185 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
5186 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
5187 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
5188 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected
5189
5190 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
5191 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
5192 const int components = alpha ? 4 : 3;
5193 const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
5194 const float w_inputs = ImMax(w_full - w_button, 1.0f);
5195 w_full = w_inputs + w_button;
5196
5197 // Convert to the formats we need
5198 float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
5199 if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
5200 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
5201 else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
5202 {
5203 // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
5204 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
5205 ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);
5206 }
5207 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]) };
5208
5209 bool value_changed = false;
5210 bool value_changed_as_float = false;
5211
5212 const ImVec2 pos = window->DC.CursorPos;
5213 const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
5214 window->DC.CursorPos.x = pos.x + inputs_offset_x;
5215
5216 if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
5217 {
5218 // RGB/HSV 0..255 Sliders
5219 const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
5220
5221 const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
5222 static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
5223 static const char* fmt_table_int[3][4] =
5224 {
5225 { "%3d", "%3d", "%3d", "%3d" }, // Short display
5226 { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
5227 { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
5228 };
5229 static const char* fmt_table_float[3][4] =
5230 {
5231 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
5232 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
5233 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
5234 };
5235 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
5236
5237 float prev_split = 0.0f;
5238 for (int n = 0; n < components; n++)
5239 {
5240 if (n > 0)
5241 SameLine(0, style.ItemInnerSpacing.x);
5242 float next_split = IM_TRUNC(w_items * (n + 1) / components);
5243 SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
5244 prev_split = next_split;
5245
5246 // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
5247 if (flags & ImGuiColorEditFlags_Float)
5248 {
5249 value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
5250 value_changed_as_float |= value_changed;
5251 }
5252 else
5253 {
5254 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
5255 }
5256 if (!(flags & ImGuiColorEditFlags_NoOptions))
5257 OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5258 }
5259 }
5260 else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
5261 {
5262 // RGB Hexadecimal Input
5263 char buf[64];
5264 if (alpha)
5265 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));
5266 else
5267 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
5268 SetNextItemWidth(w_inputs);
5269 if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))
5270 {
5271 value_changed = true;
5272 char* p = buf;
5273 while (*p == '#' || ImCharIsBlankA(*p))
5274 p++;
5275 i[0] = i[1] = i[2] = 0;
5276 i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
5277 int r;
5278 if (alpha)
5279 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)
5280 else
5281 r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
5282 IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
5283 }
5284 if (!(flags & ImGuiColorEditFlags_NoOptions))
5285 OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5286 }
5287
5288 ImGuiWindow* picker_active_window = NULL;
5289 if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
5290 {
5291 const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
5292 window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
5293
5294 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
5295 if (ColorButton("##ColorButton", col_v4, flags))
5296 {
5297 if (!(flags & ImGuiColorEditFlags_NoPicker))
5298 {
5299 // Store current color and open a picker
5300 g.ColorPickerRef = col_v4;
5301 OpenPopup("picker");
5302 SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
5303 }
5304 }
5305 if (!(flags & ImGuiColorEditFlags_NoOptions))
5306 OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5307
5308 if (BeginPopup("picker"))
5309 {
5310 if (g.CurrentWindow->BeginCount == 1)
5311 {
5312 picker_active_window = g.CurrentWindow;
5313 if (label != label_display_end)
5314 {
5315 TextEx(label, label_display_end);
5316 Spacing();
5317 }
5318 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
5319 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
5320 SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
5321 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
5322 }
5323 EndPopup();
5324 }
5325 }
5326
5327 if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
5328 {
5329 // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),
5330 // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this.
5331 SameLine(0.0f, style.ItemInnerSpacing.x);
5332 window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);
5333 TextEx(label, label_display_end);
5334 }
5335
5336 // Convert back
5337 if (value_changed && picker_active_window == NULL)
5338 {
5339 if (!value_changed_as_float)
5340 for (int n = 0; n < 4; n++)
5341 f[n] = i[n] / 255.0f;
5342 if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
5343 {
5344 g.ColorEditSavedHue = f[0];
5345 g.ColorEditSavedSat = f[1];
5346 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
5347 g.ColorEditSavedID = g.ColorEditCurrentID;
5348 g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
5349 }
5350 if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
5351 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
5352
5353 col[0] = f[0];
5354 col[1] = f[1];
5355 col[2] = f[2];
5356 if (alpha)
5357 col[3] = f[3];
5358 }
5359
5360 if (set_current_color_edit_id)
5361 g.ColorEditCurrentID = 0;
5362 PopID();
5363 EndGroup();
5364
5365 // Drag and Drop Target
5366 // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
5367 if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
5368 {
5369 bool accepted_drag_drop = false;
5370 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
5371 {
5372 memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
5373 value_changed = accepted_drag_drop = true;
5374 }
5375 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
5376 {
5377 memcpy((float*)col, payload->Data, sizeof(float) * components);
5378 value_changed = accepted_drag_drop = true;
5379 }
5380
5381 // Drag-drop payloads are always RGB
5382 if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
5383 ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
5384 EndDragDropTarget();
5385 }
5386
5387 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
5388 if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
5389 g.LastItemData.ID = g.ActiveId;
5390
5391 if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
5392 MarkItemEdited(g.LastItemData.ID);
5393
5394 return value_changed;
5395}
5396
5397bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
5398{
5399 float col4[4] = { col[0], col[1], col[2], 1.0f };
5400 if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
5401 return false;
5402 col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
5403 return true;
5404}
5405
5406// Helper for ColorPicker4()
5407static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
5408{
5409 ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
5410 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));
5411 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
5412 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));
5413 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8));
5414}
5415
5416// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5417// (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.)
5418// 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..)
5419// 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)
5420bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
5421{
5422 ImGuiContext& g = *GImGui;
5423 ImGuiWindow* window = GetCurrentWindow();
5424 if (window->SkipItems)
5425 return false;
5426
5427 ImDrawList* draw_list = window->DrawList;
5428 ImGuiStyle& style = g.Style;
5429 ImGuiIO& io = g.IO;
5430
5431 const float width = CalcItemWidth();
5432 const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
5433 g.NextItemData.ClearFlags();
5434
5435 PushID(label);
5436 const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
5437 if (set_current_color_edit_id)
5438 g.ColorEditCurrentID = window->IDStack.back();
5439 BeginGroup();
5440
5441 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5442 flags |= ImGuiColorEditFlags_NoSmallPreview;
5443
5444 // Context menu: display and store options.
5445 if (!(flags & ImGuiColorEditFlags_NoOptions))
5446 ColorPickerOptionsPopup(col, flags);
5447
5448 // Read stored options
5449 if (!(flags & ImGuiColorEditFlags_PickerMask_))
5450 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
5451 if (!(flags & ImGuiColorEditFlags_InputMask_))
5452 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
5453 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
5454 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected
5455 if (!(flags & ImGuiColorEditFlags_NoOptions))
5456 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
5457
5458 // Setup
5459 int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
5460 bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
5461 ImVec2 picker_pos = window->DC.CursorPos;
5462 float square_sz = GetFrameHeight();
5463 float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
5464 float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
5465 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
5466 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
5467 float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);
5468
5469 float backup_initial_col[4];
5470 memcpy(backup_initial_col, col, components * sizeof(float));
5471
5472 float wheel_thickness = sv_picker_size * 0.08f;
5473 float wheel_r_outer = sv_picker_size * 0.50f;
5474 float wheel_r_inner = wheel_r_outer - wheel_thickness;
5475 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
5476
5477 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
5478 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
5479 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
5480 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
5481 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
5482
5483 float H = col[0], S = col[1], V = col[2];
5484 float R = col[0], G = col[1], B = col[2];
5485 if (flags & ImGuiColorEditFlags_InputRGB)
5486 {
5487 // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
5488 ColorConvertRGBtoHSV(R, G, B, H, S, V);
5489 ColorEditRestoreHS(col, &H, &S, &V);
5490 }
5491 else if (flags & ImGuiColorEditFlags_InputHSV)
5492 {
5493 ColorConvertHSVtoRGB(H, S, V, R, G, B);
5494 }
5495
5496 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
5497
5498 PushItemFlag(ImGuiItemFlags_NoNav, true);
5499 if (flags & ImGuiColorEditFlags_PickerHueWheel)
5500 {
5501 // Hue wheel + SV triangle logic
5502 InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
5503 if (IsItemActive() && !is_readonly)
5504 {
5505 ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
5506 ImVec2 current_off = g.IO.MousePos - wheel_center;
5507 float initial_dist2 = ImLengthSqr(initial_off);
5508 if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
5509 {
5510 // Interactive with Hue wheel
5511 H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
5512 if (H < 0.0f)
5513 H += 1.0f;
5514 value_changed = value_changed_h = true;
5515 }
5516 float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
5517 float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
5518 if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
5519 {
5520 // Interacting with SV triangle
5521 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
5522 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
5523 current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
5524 float uu, vv, ww;
5525 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
5526 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
5527 S = ImClamp(uu / V, 0.0001f, 1.0f);
5528 value_changed = value_changed_sv = true;
5529 }
5530 }
5531 if (!(flags & ImGuiColorEditFlags_NoOptions))
5532 OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5533 }
5534 else if (flags & ImGuiColorEditFlags_PickerHueBar)
5535 {
5536 // SV rectangle logic
5537 InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
5538 if (IsItemActive() && !is_readonly)
5539 {
5540 S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
5541 V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5542 ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
5543 value_changed = value_changed_sv = true;
5544 }
5545 if (!(flags & ImGuiColorEditFlags_NoOptions))
5546 OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5547
5548 // Hue bar logic
5549 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
5550 InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
5551 if (IsItemActive() && !is_readonly)
5552 {
5553 H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5554 value_changed = value_changed_h = true;
5555 }
5556 }
5557
5558 // Alpha bar logic
5559 if (alpha_bar)
5560 {
5561 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
5562 InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
5563 if (IsItemActive())
5564 {
5565 col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5566 value_changed = true;
5567 }
5568 }
5569 PopItemFlag(); // ImGuiItemFlags_NoNav
5570
5571 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5572 {
5573 SameLine(0, style.ItemInnerSpacing.x);
5574 BeginGroup();
5575 }
5576
5577 if (!(flags & ImGuiColorEditFlags_NoLabel))
5578 {
5579 const char* label_display_end = FindRenderedTextEnd(label);
5580 if (label != label_display_end)
5581 {
5582 if ((flags & ImGuiColorEditFlags_NoSidePreview))
5583 SameLine(0, style.ItemInnerSpacing.x);
5584 TextEx(label, label_display_end);
5585 }
5586 }
5587
5588 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5589 {
5590 PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
5591 ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5592 if ((flags & ImGuiColorEditFlags_NoLabel))
5593 Text("Current");
5594
5595 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
5596 ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
5597 if (ref_col != NULL)
5598 {
5599 Text("Original");
5600 ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
5601 if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
5602 {
5603 memcpy(col, ref_col, components * sizeof(float));
5604 value_changed = true;
5605 }
5606 }
5607 PopItemFlag();
5608 EndGroup();
5609 }
5610
5611 // Convert back color to RGB
5612 if (value_changed_h || value_changed_sv)
5613 {
5614 if (flags & ImGuiColorEditFlags_InputRGB)
5615 {
5616 ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
5617 g.ColorEditSavedHue = H;
5618 g.ColorEditSavedSat = S;
5619 g.ColorEditSavedID = g.ColorEditCurrentID;
5620 g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
5621 }
5622 else if (flags & ImGuiColorEditFlags_InputHSV)
5623 {
5624 col[0] = H;
5625 col[1] = S;
5626 col[2] = V;
5627 }
5628 }
5629
5630 // R,G,B and H,S,V slider color editor
5631 bool value_changed_fix_hue_wrap = false;
5632 if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
5633 {
5634 PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
5635 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
5636 ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
5637 if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5638 if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
5639 {
5640 // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
5641 // 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)
5642 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
5643 value_changed = true;
5644 }
5645 if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5646 value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
5647 if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5648 value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
5649 PopItemWidth();
5650 }
5651
5652 // Try to cancel hue wrap (after ColorEdit4 call), if any
5653 if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
5654 {
5655 float new_H, new_S, new_V;
5656 ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
5657 if (new_H <= 0 && H > 0)
5658 {
5659 if (new_V <= 0 && V != new_V)
5660 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
5661 else if (new_S <= 0)
5662 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
5663 }
5664 }
5665
5666 if (value_changed)
5667 {
5668 if (flags & ImGuiColorEditFlags_InputRGB)
5669 {
5670 R = col[0];
5671 G = col[1];
5672 B = col[2];
5673 ColorConvertRGBtoHSV(R, G, B, H, S, V);
5674 ColorEditRestoreHS(col, &H, &S, &V); // Fix local Hue as display below will use it immediately.
5675 }
5676 else if (flags & ImGuiColorEditFlags_InputHSV)
5677 {
5678 H = col[0];
5679 S = col[1];
5680 V = col[2];
5681 ColorConvertHSVtoRGB(H, S, V, R, G, B);
5682 }
5683 }
5684
5685 const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
5686 const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
5687 const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
5688 const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
5689 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) };
5690
5691 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);
5692 ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
5693 ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
5694
5695 ImVec2 sv_cursor_pos;
5696
5697 if (flags & ImGuiColorEditFlags_PickerHueWheel)
5698 {
5699 // Render Hue Wheel
5700 const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
5701 const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
5702 for (int n = 0; n < 6; n++)
5703 {
5704 const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
5705 const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
5706 const int vert_start_idx = draw_list->VtxBuffer.Size;
5707 draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
5708 draw_list->PathStroke(col_white, 0, wheel_thickness);
5709 const int vert_end_idx = draw_list->VtxBuffer.Size;
5710
5711 // Paint colors over existing vertices
5712 ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
5713 ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
5714 ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
5715 }
5716
5717 // Render Cursor + preview on Hue Wheel
5718 float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
5719 float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
5720 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);
5721 float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
5722 int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others.
5723 draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
5724 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
5725 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
5726
5727 // Render SV triangle (rotated according to hue)
5728 ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
5729 ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
5730 ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
5731 ImVec2 uv_white = GetFontTexUvWhitePixel();
5732 draw_list->PrimReserve(3, 3);
5733 draw_list->PrimVtx(tra, uv_white, hue_color32);
5734 draw_list->PrimVtx(trb, uv_white, col_black);
5735 draw_list->PrimVtx(trc, uv_white, col_white);
5736 draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
5737 sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
5738 }
5739 else if (flags & ImGuiColorEditFlags_PickerHueBar)
5740 {
5741 // Render SV Square
5742 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
5743 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
5744 RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
5745 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
5746 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);
5747
5748 // Render Hue Bar
5749 for (int i = 0; i < 6; ++i)
5750 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]);
5751 float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
5752 RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
5753 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);
5754 }
5755
5756 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
5757 float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;
5758 int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others.
5759 draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments);
5760 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);
5761 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);
5762
5763 // Render alpha bar
5764 if (alpha_bar)
5765 {
5766 float alpha = ImSaturate(col[3]);
5767 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
5768 RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
5769 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);
5770 float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
5771 RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
5772 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);
5773 }
5774
5775 EndGroup();
5776
5777 if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
5778 value_changed = false;
5779 if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
5780 MarkItemEdited(g.LastItemData.ID);
5781
5782 if (set_current_color_edit_id)
5783 g.ColorEditCurrentID = 0;
5784 PopID();
5785
5786 return value_changed;
5787}
5788
5789// A little color square. Return true when clicked.
5790// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
5791// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
5792// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
5793bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
5794{
5795 ImGuiWindow* window = GetCurrentWindow();
5796 if (window->SkipItems)
5797 return false;
5798
5799 ImGuiContext& g = *GImGui;
5800 const ImGuiID id = window->GetID(desc_id);
5801 const float default_size = GetFrameHeight();
5802 const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
5803 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
5804 ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
5805 if (!ItemAdd(bb, id))
5806 return false;
5807
5808 bool hovered, held;
5809 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
5810
5811 if (flags & ImGuiColorEditFlags_NoAlpha)
5812 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
5813
5814 ImVec4 col_rgb = col;
5815 if (flags & ImGuiColorEditFlags_InputHSV)
5816 ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
5817
5818 ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
5819 float grid_step = ImMin(size.x, size.y) / 2.99f;
5820 float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
5821 ImRect bb_inner = bb;
5822 float off = 0.0f;
5823 if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
5824 {
5825 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.
5826 bb_inner.Expand(off);
5827 }
5828 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
5829 {
5830 float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
5831 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);
5832 window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
5833 }
5834 else
5835 {
5836 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
5837 ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
5838 if (col_source.w < 1.0f)
5839 RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
5840 else
5841 window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
5842 }
5843 RenderNavHighlight(bb, id);
5844 if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
5845 {
5846 if (g.Style.FrameBorderSize > 0.0f)
5847 RenderFrameBorder(bb.Min, bb.Max, rounding);
5848 else
5849 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
5850 }
5851
5852 // Drag and Drop Source
5853 // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
5854 if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
5855 {
5856 if (flags & ImGuiColorEditFlags_NoAlpha)
5857 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
5858 else
5859 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
5860 ColorButton(desc_id, col, flags);
5861 SameLine();
5862 TextEx("Color");
5863 EndDragDropSource();
5864 }
5865
5866 // Tooltip
5867 if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))
5868 ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
5869
5870 return pressed;
5871}
5872
5873// Initialize/override default color options
5874void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
5875{
5876 ImGuiContext& g = *GImGui;
5877 if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5878 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
5879 if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
5880 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
5881 if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
5882 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
5883 if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
5884 flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
5885 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected
5886 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected
5887 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected
5888 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected
5889 g.ColorEditOptions = flags;
5890}
5891
5892// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5893void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
5894{
5895 ImGuiContext& g = *GImGui;
5896
5897 if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
5898 return;
5899 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
5900 if (text_end > text)
5901 {
5902 TextEx(text, text_end);
5903 Separator();
5904 }
5905
5906 ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
5907 ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5908 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]);
5909 ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
5910 SameLine();
5911 if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
5912 {
5913 if (flags & ImGuiColorEditFlags_NoAlpha)
5914 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]);
5915 else
5916 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]);
5917 }
5918 else if (flags & ImGuiColorEditFlags_InputHSV)
5919 {
5920 if (flags & ImGuiColorEditFlags_NoAlpha)
5921 Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
5922 else
5923 Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
5924 }
5925 EndTooltip();
5926}
5927
5928void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
5929{
5930 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
5931 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
5932 if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
5933 return;
5934 ImGuiContext& g = *GImGui;
5935 g.LockMarkEdited++;
5936 ImGuiColorEditFlags opts = g.ColorEditOptions;
5937 if (allow_opt_inputs)
5938 {
5939 if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
5940 if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
5941 if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
5942 }
5943 if (allow_opt_datatype)
5944 {
5945 if (allow_opt_inputs) Separator();
5946 if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
5947 if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
5948 }
5949
5950 if (allow_opt_inputs || allow_opt_datatype)
5951 Separator();
5952 if (Button("Copy as..", ImVec2(-1, 0)))
5953 OpenPopup("Copy");
5954 if (BeginPopup("Copy"))
5955 {
5956 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]);
5957 char buf[64];
5958 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5959 if (Selectable(buf))
5960 SetClipboardText(buf);
5961 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
5962 if (Selectable(buf))
5963 SetClipboardText(buf);
5964 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
5965 if (Selectable(buf))
5966 SetClipboardText(buf);
5967 if (!(flags & ImGuiColorEditFlags_NoAlpha))
5968 {
5969 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
5970 if (Selectable(buf))
5971 SetClipboardText(buf);
5972 }
5973 EndPopup();
5974 }
5975
5976 g.ColorEditOptions = opts;
5977 EndPopup();
5978 g.LockMarkEdited--;
5979}
5980
5981void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
5982{
5983 bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
5984 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
5985 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
5986 return;
5987 ImGuiContext& g = *GImGui;
5988 g.LockMarkEdited++;
5989 if (allow_opt_picker)
5990 {
5991 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
5992 PushItemWidth(picker_size.x);
5993 for (int picker_type = 0; picker_type < 2; picker_type++)
5994 {
5995 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
5996 if (picker_type > 0) Separator();
5997 PushID(picker_type);
5998 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
5999 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
6000 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
6001 ImVec2 backup_pos = GetCursorScreenPos();
6002 if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
6003 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
6004 SetCursorScreenPos(backup_pos);
6005 ImVec4 previewing_ref_col;
6006 memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
6007 ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
6008 PopID();
6009 }
6010 PopItemWidth();
6011 }
6012 if (allow_opt_alpha_bar)
6013 {
6014 if (allow_opt_picker) Separator();
6015 CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
6016 }
6017 EndPopup();
6018 g.LockMarkEdited--;
6019}
6020
6021//-------------------------------------------------------------------------
6022// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
6023//-------------------------------------------------------------------------
6024// - TreeNode()
6025// - TreeNodeV()
6026// - TreeNodeEx()
6027// - TreeNodeExV()
6028// - TreeNodeBehavior() [Internal]
6029// - TreePush()
6030// - TreePop()
6031// - GetTreeNodeToLabelSpacing()
6032// - SetNextItemOpen()
6033// - CollapsingHeader()
6034//-------------------------------------------------------------------------
6035
6036bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
6037{
6038 va_list args;
6039 va_start(args, fmt);
6040 bool is_open = TreeNodeExV(str_id, 0, fmt, args);
6041 va_end(args);
6042 return is_open;
6043}
6044
6045bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
6046{
6047 va_list args;
6048 va_start(args, fmt);
6049 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
6050 va_end(args);
6051 return is_open;
6052}
6053
6054bool ImGui::TreeNode(const char* label)
6055{
6056 ImGuiWindow* window = GetCurrentWindow();
6057 if (window->SkipItems)
6058 return false;
6059 return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
6060}
6061
6062bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
6063{
6064 return TreeNodeExV(str_id, 0, fmt, args);
6065}
6066
6067bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
6068{
6069 return TreeNodeExV(ptr_id, 0, fmt, args);
6070}
6071
6072bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
6073{
6074 ImGuiWindow* window = GetCurrentWindow();
6075 if (window->SkipItems)
6076 return false;
6077
6078 return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
6079}
6080
6081bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
6082{
6083 va_list args;
6084 va_start(args, fmt);
6085 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
6086 va_end(args);
6087 return is_open;
6088}
6089
6090bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
6091{
6092 va_list args;
6093 va_start(args, fmt);
6094 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
6095 va_end(args);
6096 return is_open;
6097}
6098
6099bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
6100{
6101 ImGuiWindow* window = GetCurrentWindow();
6102 if (window->SkipItems)
6103 return false;
6104
6105 const char* label, *label_end;
6106 ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
6107 return TreeNodeBehavior(window->GetID(str_id), flags, label, label_end);
6108}
6109
6110bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
6111{
6112 ImGuiWindow* window = GetCurrentWindow();
6113 if (window->SkipItems)
6114 return false;
6115
6116 const char* label, *label_end;
6117 ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
6118 return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end);
6119}
6120
6121void ImGui::TreeNodeSetOpen(ImGuiID id, bool open)
6122{
6123 ImGuiContext& g = *GImGui;
6124 ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
6125 storage->SetInt(id, open ? 1 : 0);
6126}
6127
6128bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
6129{
6130 if (flags & ImGuiTreeNodeFlags_Leaf)
6131 return true;
6132
6133 // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function)
6134 ImGuiContext& g = *GImGui;
6135 ImGuiWindow* window = g.CurrentWindow;
6136 ImGuiStorage* storage = window->DC.StateStorage;
6137
6138 bool is_open;
6139 if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen)
6140 {
6141 if (g.NextItemData.OpenCond & ImGuiCond_Always)
6142 {
6143 is_open = g.NextItemData.OpenVal;
6144 TreeNodeSetOpen(id, is_open);
6145 }
6146 else
6147 {
6148 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
6149 const int stored_value = storage->GetInt(id, -1);
6150 if (stored_value == -1)
6151 {
6152 is_open = g.NextItemData.OpenVal;
6153 TreeNodeSetOpen(id, is_open);
6154 }
6155 else
6156 {
6157 is_open = stored_value != 0;
6158 }
6159 }
6160 }
6161 else
6162 {
6163 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
6164 }
6165
6166 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
6167 // NB- If we are above max depth we still allow manually opened nodes to be logged.
6168 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
6169 is_open = true;
6170
6171 return is_open;
6172}
6173
6174bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
6175{
6176 ImGuiWindow* window = GetCurrentWindow();
6177 if (window->SkipItems)
6178 return false;
6179
6180 ImGuiContext& g = *GImGui;
6181 const ImGuiStyle& style = g.Style;
6182 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
6183 const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
6184
6185 if (!label_end)
6186 label_end = FindRenderedTextEnd(label);
6187 const ImVec2 label_size = CalcTextSize(label, label_end, false);
6188
6189 // We vertically grow up to current line height up the typical widget height.
6190 const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
6191 const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
6192 ImRect frame_bb;
6193 frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
6194 frame_bb.Min.y = window->DC.CursorPos.y;
6195 frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
6196 frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
6197 if (display_frame)
6198 {
6199 // Framed header expand a little outside the default padding, to the edge of InnerClipRect
6200 // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f)
6201 frame_bb.Min.x -= IM_TRUNC(window->WindowPadding.x * 0.5f - 1.0f);
6202 frame_bb.Max.x += IM_TRUNC(window->WindowPadding.x * 0.5f);
6203 }
6204
6205 const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing
6206 const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it
6207 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapsing
6208 ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
6209 ItemSize(ImVec2(text_width, frame_height), padding.y);
6210
6211 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
6212 ImRect interact_bb = frame_bb;
6213 if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)
6214 interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f;
6215
6216 // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
6217 const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6218 const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6219 if (span_all_columns)
6220 {
6221 window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6222 window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6223 }
6224
6225 // Compute open and multi-select states before ItemAdd() as it clear NextItem data.
6226 bool is_open = TreeNodeUpdateNextOpen(id, flags);
6227 bool item_add = ItemAdd(interact_bb, id);
6228 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
6229 g.LastItemData.DisplayRect = frame_bb;
6230
6231 if (span_all_columns)
6232 {
6233 window->ClipRect.Min.x = backup_clip_rect_min_x;
6234 window->ClipRect.Max.x = backup_clip_rect_max_x;
6235 }
6236
6237 // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled:
6238 // Store data for the current depth to allow returning to this node from any child item.
6239 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
6240 // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.
6241 // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
6242 if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6243 if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6244 {
6245 g.NavTreeNodeStack.resize(g.NavTreeNodeStack.Size + 1);
6246 ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back();
6247 nav_tree_node_data->ID = id;
6248 nav_tree_node_data->InFlags = g.LastItemData.InFlags;
6249 nav_tree_node_data->NavRect = g.LastItemData.NavRect;
6250 window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
6251 }
6252
6253 const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
6254 if (!item_add)
6255 {
6256 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6257 TreePushOverrideID(id);
6258 IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
6259 return is_open;
6260 }
6261
6262 if (span_all_columns)
6263 {
6264 TablePushBackgroundChannel();
6265 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
6266 g.LastItemData.ClipRect = window->ClipRect;
6267 }
6268
6269 ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
6270 if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap))
6271 button_flags |= ImGuiButtonFlags_AllowOverlap;
6272 if (!is_leaf)
6273 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
6274
6275 // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
6276 // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
6277 // When clicking on the rest of the tree node we always disallow keyboard modifiers.
6278 const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
6279 const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
6280 const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
6281 if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
6282 button_flags |= ImGuiButtonFlags_NoKeyModifiers;
6283
6284 // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
6285 // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
6286 // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
6287 // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
6288 // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
6289 // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
6290 // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
6291 // It is rather standard that arrow click react on Down rather than Up.
6292 // 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.
6293 if (is_mouse_x_over_arrow)
6294 button_flags |= ImGuiButtonFlags_PressedOnClick;
6295 else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
6296 button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
6297 else
6298 button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
6299
6300 bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
6301 const bool was_selected = selected;
6302
6303 bool hovered, held;
6304 bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
6305 bool toggled = false;
6306 if (!is_leaf)
6307 {
6308 if (pressed && g.DragDropHoldJustPressedId != id)
6309 {
6310 if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
6311 toggled = true;
6312 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
6313 toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
6314 if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)
6315 toggled = true;
6316 }
6317 else if (pressed && g.DragDropHoldJustPressedId == id)
6318 {
6319 IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
6320 if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
6321 toggled = true;
6322 }
6323
6324 if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
6325 {
6326 toggled = true;
6327 NavClearPreferredPosForAxis(ImGuiAxis_X);
6328 NavMoveRequestCancel();
6329 }
6330 if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
6331 {
6332 toggled = true;
6333 NavClearPreferredPosForAxis(ImGuiAxis_X);
6334 NavMoveRequestCancel();
6335 }
6336
6337 if (toggled)
6338 {
6339 is_open = !is_open;
6340 window->DC.StateStorage->SetInt(id, is_open);
6341 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
6342 }
6343 }
6344
6345 // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
6346 if (selected != was_selected) //-V547
6347 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
6348
6349 // Render
6350 const ImU32 text_col = GetColorU32(ImGuiCol_Text);
6351 ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact;
6352 if (display_frame)
6353 {
6354 // Framed type
6355 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6356 RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
6357 RenderNavHighlight(frame_bb, id, nav_highlight_flags);
6358 if (flags & ImGuiTreeNodeFlags_Bullet)
6359 RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
6360 else if (!is_leaf)
6361 RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f);
6362 else // Leaf without bullet, left-adjusted text
6363 text_pos.x -= text_offset_x -padding.x;
6364 if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
6365 frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
6366
6367 if (g.LogEnabled)
6368 LogSetNextTextDecoration("###", "###");
6369 }
6370 else
6371 {
6372 // Unframed typed for tree nodes
6373 if (hovered || selected)
6374 {
6375 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6376 RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
6377 }
6378 RenderNavHighlight(frame_bb, id, nav_highlight_flags);
6379 if (flags & ImGuiTreeNodeFlags_Bullet)
6380 RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
6381 else if (!is_leaf)
6382 RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f);
6383 if (g.LogEnabled)
6384 LogSetNextTextDecoration(">", NULL);
6385 }
6386
6387 if (span_all_columns)
6388 TablePopBackgroundChannel();
6389
6390 // Label
6391 if (display_frame)
6392 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
6393 else
6394 RenderText(text_pos, label, label_end, false);
6395
6396 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6397 TreePushOverrideID(id);
6398 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
6399 return is_open;
6400}
6401
6402void ImGui::TreePush(const char* str_id)
6403{
6404 ImGuiWindow* window = GetCurrentWindow();
6405 Indent();
6406 window->DC.TreeDepth++;
6407 PushID(str_id);
6408}
6409
6410void ImGui::TreePush(const void* ptr_id)
6411{
6412 ImGuiWindow* window = GetCurrentWindow();
6413 Indent();
6414 window->DC.TreeDepth++;
6415 PushID(ptr_id);
6416}
6417
6418void ImGui::TreePushOverrideID(ImGuiID id)
6419{
6420 ImGuiContext& g = *GImGui;
6421 ImGuiWindow* window = g.CurrentWindow;
6422 Indent();
6423 window->DC.TreeDepth++;
6424 PushOverrideID(id);
6425}
6426
6427void ImGui::TreePop()
6428{
6429 ImGuiContext& g = *GImGui;
6430 ImGuiWindow* window = g.CurrentWindow;
6431 Unindent();
6432
6433 window->DC.TreeDepth--;
6434 ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
6435
6436 // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
6437 if (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask) // Only set during request
6438 {
6439 ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back();
6440 IM_ASSERT(nav_tree_node_data->ID == window->IDStack.back());
6441 if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6442 NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, nav_tree_node_data);
6443 g.NavTreeNodeStack.pop_back();
6444 }
6445 window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1;
6446
6447 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.
6448 PopID();
6449}
6450
6451// Horizontal distance preceding label when using TreeNode() or Bullet()
6452float ImGui::GetTreeNodeToLabelSpacing()
6453{
6454 ImGuiContext& g = *GImGui;
6455 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
6456}
6457
6458// Set next TreeNode/CollapsingHeader open state.
6459void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
6460{
6461 ImGuiContext& g = *GImGui;
6462 if (g.CurrentWindow->SkipItems)
6463 return;
6464 g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen;
6465 g.NextItemData.OpenVal = is_open;
6466 g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always;
6467}
6468
6469// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
6470// 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().
6471bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
6472{
6473 ImGuiWindow* window = GetCurrentWindow();
6474 if (window->SkipItems)
6475 return false;
6476
6477 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
6478}
6479
6480// p_visible == NULL : regular collapsing header
6481// 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
6482// p_visible != NULL && *p_visible == false : do not show the header at all
6483// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
6484bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
6485{
6486 ImGuiWindow* window = GetCurrentWindow();
6487 if (window->SkipItems)
6488 return false;
6489
6490 if (p_visible && !*p_visible)
6491 return false;
6492
6493 ImGuiID id = window->GetID(label);
6494 flags |= ImGuiTreeNodeFlags_CollapsingHeader;
6495 if (p_visible)
6496 flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
6497 bool is_open = TreeNodeBehavior(id, flags, label);
6498 if (p_visible != NULL)
6499 {
6500 // Create a small overlapping close button
6501 // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
6502 // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
6503 ImGuiContext& g = *GImGui;
6504 ImGuiLastItemData last_item_backup = g.LastItemData;
6505 float button_size = g.FontSize;
6506 float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);
6507 float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
6508 ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
6509 if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
6510 *p_visible = false;
6511 g.LastItemData = last_item_backup;
6512 }
6513
6514 return is_open;
6515}
6516
6517//-------------------------------------------------------------------------
6518// [SECTION] Widgets: Selectable
6519//-------------------------------------------------------------------------
6520// - Selectable()
6521//-------------------------------------------------------------------------
6522
6523// Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
6524// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
6525// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
6526// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
6527bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6528{
6529 ImGuiWindow* window = GetCurrentWindow();
6530 if (window->SkipItems)
6531 return false;
6532
6533 ImGuiContext& g = *GImGui;
6534 const ImGuiStyle& style = g.Style;
6535
6536 // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
6537 ImGuiID id = window->GetID(label);
6538 ImVec2 label_size = CalcTextSize(label, NULL, true);
6539 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
6540 ImVec2 pos = window->DC.CursorPos;
6541 pos.y += window->DC.CurrLineTextBaseOffset;
6542 ItemSize(size, 0.0f);
6543
6544 // Fill horizontal space
6545 // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
6546 const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
6547 const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
6548 const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
6549 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
6550 size.x = ImMax(label_size.x, max_x - min_x);
6551
6552 // Text stays at the submission position, but bounding box may be extended on both sides
6553 const ImVec2 text_min = pos;
6554 const ImVec2 text_max(min_x + size.x, pos.y + size.y);
6555
6556 // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
6557 ImRect bb(min_x, pos.y, text_max.x, text_max.y);
6558 if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
6559 {
6560 const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
6561 const float spacing_y = style.ItemSpacing.y;
6562 const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
6563 const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
6564 bb.Min.x -= spacing_L;
6565 bb.Min.y -= spacing_U;
6566 bb.Max.x += (spacing_x - spacing_L);
6567 bb.Max.y += (spacing_y - spacing_U);
6568 }
6569 //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
6570
6571 // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
6572 const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6573 const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6574 if (span_all_columns)
6575 {
6576 window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6577 window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6578 }
6579
6580 const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
6581 const bool item_add = ItemAdd(bb, id, NULL, disabled_item ? ImGuiItemFlags_Disabled : ImGuiItemFlags_None);
6582 if (span_all_columns)
6583 {
6584 window->ClipRect.Min.x = backup_clip_rect_min_x;
6585 window->ClipRect.Max.x = backup_clip_rect_max_x;
6586 }
6587
6588 if (!item_add)
6589 return false;
6590
6591 const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
6592 if (disabled_item && !disabled_global) // Only testing this as an optimization
6593 BeginDisabled();
6594
6595 // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
6596 // which would be advantageous since most selectable are not selected.
6597 if (span_all_columns)
6598 {
6599 if (g.CurrentTable)
6600 TablePushBackgroundChannel();
6601 else if (window->DC.CurrentColumns)
6602 PushColumnsBackground();
6603 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
6604 g.LastItemData.ClipRect = window->ClipRect;
6605 }
6606
6607 // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
6608 ImGuiButtonFlags button_flags = 0;
6609 if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
6610 if (flags & ImGuiSelectableFlags_NoSetKeyOwner) { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }
6611 if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
6612 if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
6613 if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
6614 if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
6615
6616 const bool was_selected = selected;
6617 bool hovered, held;
6618 bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
6619
6620 // Auto-select when moved into
6621 // - This will be more fully fleshed in the range-select branch
6622 // - This is not exposed as it won't nicely work with some user side handling of shift/control
6623 // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
6624 // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
6625 // - (2) usage will fail with clipped items
6626 // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
6627 if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
6628 if (g.NavJustMovedToId == id)
6629 selected = pressed = true;
6630
6631 // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
6632 if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
6633 {
6634 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
6635 {
6636 SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
6637 g.NavDisableHighlight = true;
6638 }
6639 }
6640 if (pressed)
6641 MarkItemEdited(id);
6642
6643 // In this branch, Selectable() cannot toggle the selection so this will never trigger.
6644 if (selected != was_selected) //-V547
6645 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
6646
6647 // Render
6648 if (hovered || selected)
6649 {
6650 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6651 RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
6652 }
6653 if (g.NavId == id)
6654 RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding);
6655
6656 if (span_all_columns)
6657 {
6658 if (g.CurrentTable)
6659 TablePopBackgroundChannel();
6660 else if (window->DC.CurrentColumns)
6661 PopColumnsBackground();
6662 }
6663
6664 RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
6665
6666 // Automatically close popups
6667 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup))
6668 CloseCurrentPopup();
6669
6670 if (disabled_item && !disabled_global)
6671 EndDisabled();
6672
6673 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
6674 return pressed; //-V1020
6675}
6676
6677bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6678{
6679 if (Selectable(label, *p_selected, flags, size_arg))
6680 {
6681 *p_selected = !*p_selected;
6682 return true;
6683 }
6684 return false;
6685}
6686
6687
6688//-------------------------------------------------------------------------
6689// [SECTION] Widgets: Typing-Select support
6690//-------------------------------------------------------------------------
6691
6692// [Experimental] Currently not exposed in public API.
6693// Consume character inputs and return search request, if any.
6694// This would typically only be called on the focused window or location you want to grab inputs for, e.g.
6695// if (ImGui::IsWindowFocused(...))
6696// if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())
6697// focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1);
6698// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer).
6699ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)
6700{
6701 ImGuiContext& g = *GImGui;
6702 ImGuiTypingSelectState* data = &g.TypingSelectState;
6703 ImGuiTypingSelectRequest* out_request = &data->Request;
6704
6705 // Clear buffer
6706 const float TYPING_SELECT_RESET_TIMER = 1.80f; // FIXME: Potentially move to IO config.
6707 const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times
6708 if (data->SearchBuffer[0] != 0)
6709 {
6710 bool clear_buffer = false;
6711 clear_buffer |= (g.NavFocusScopeId != data->FocusScope);
6712 clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);
6713 clear_buffer |= g.NavAnyRequest;
6714 clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere
6715 clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);
6716 clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;
6717 //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); }
6718 if (clear_buffer)
6719 data->Clear();
6720 }
6721
6722 // Append to buffer
6723 const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
6724 int buffer_len = (int)strlen(data->SearchBuffer);
6725 bool select_request = false;
6726 for (ImWchar w : g.IO.InputQueueCharacters)
6727 {
6728 const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);
6729 if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks
6730 continue;
6731 char w_buf[5];
6732 ImTextCharToUtf8(w_buf, (unsigned int)w);
6733 if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)
6734 {
6735 select_request = true; // Same character: don't need to append to buffer.
6736 continue;
6737 }
6738 if (data->SingleCharModeLock)
6739 {
6740 data->Clear(); // Different character: clear
6741 buffer_len = 0;
6742 }
6743 memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append
6744 buffer_len += w_len;
6745 select_request = true;
6746 }
6747 g.IO.InputQueueCharacters.resize(0);
6748
6749 // Handle backspace
6750 if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, 0, ImGuiInputFlags_Repeat))
6751 {
6752 char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);
6753 *p = 0;
6754 buffer_len = (int)(p - data->SearchBuffer);
6755 }
6756
6757 // Return request if any
6758 if (buffer_len == 0)
6759 return NULL;
6760 if (select_request)
6761 {
6762 data->FocusScope = g.NavFocusScopeId;
6763 data->LastRequestFrame = g.FrameCount;
6764 data->LastRequestTime = (float)g.Time;
6765 }
6766 out_request->Flags = flags;
6767 out_request->SearchBufferLen = buffer_len;
6768 out_request->SearchBuffer = data->SearchBuffer;
6769 out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);
6770 out_request->SingleCharMode = false;
6771 out_request->SingleCharSize = 0;
6772
6773 // Calculate if buffer contains the same character repeated.
6774 // - This can be used to implement a special search mode on first character.
6775 // - Performed on UTF-8 codepoint for correctness.
6776 // - SingleCharMode is always set for first input character, because it usually leads to a "next".
6777 if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)
6778 {
6779 const char* buf_begin = out_request->SearchBuffer;
6780 const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;
6781 const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);
6782 const char* p = buf_begin + c0_len;
6783 for (; p < buf_end; p += c0_len)
6784 if (memcmp(buf_begin, p, (size_t)c0_len) != 0)
6785 break;
6786 const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;
6787 out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);
6788 out_request->SingleCharSize = (ImS8)c0_len;
6789 data->SingleCharModeLock |= (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK); // From now on we stop search matching to lock to single char mode.
6790 }
6791
6792 return out_request;
6793}
6794
6795static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)
6796{
6797 int match_len = 0;
6798 while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))
6799 match_len++;
6800 return match_len;
6801}
6802
6803// Default handler for finding a result for typing-select. You may implement your own.
6804// You might want to display a tooltip to visualize the current request SearchBuffer
6805// When SingleCharMode is set:
6806// - it is better to NOT display a tooltip of other on-screen display indicator.
6807// - the index of the currently focused item is required.
6808// if your SetNextItemSelectionData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData.
6809int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
6810{
6811 if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot.
6812 return -1;
6813 int idx = -1;
6814 if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))
6815 idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);
6816 else
6817 idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);
6818 if (idx != -1)
6819 NavRestoreHighlightAfterMove();
6820 return idx;
6821}
6822
6823// Special handling when a single character is repeated: perform search on a single letter and goes to next.
6824int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
6825{
6826 // FIXME: Assume selection user data is index. Would be extremely practical.
6827 //if (nav_item_idx == -1)
6828 // nav_item_idx = (int)g.NavLastValidSelectionUserData;
6829
6830 int first_match_idx = -1;
6831 bool return_next_match = false;
6832 for (int idx = 0; idx < items_count; idx++)
6833 {
6834 const char* item_name = get_item_name_func(user_data, idx);
6835 if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)
6836 continue;
6837 if (return_next_match) // Return next matching item after current item.
6838 return idx;
6839 if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value.
6840 return idx;
6841 if (first_match_idx == -1) // Record first match for wrapping.
6842 first_match_idx = idx;
6843 if (nav_item_idx == idx) // Record that we encountering nav_item so we can return next match.
6844 return_next_match = true;
6845 }
6846 return first_match_idx; // First result
6847}
6848
6849int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data)
6850{
6851 int longest_match_idx = -1;
6852 int longest_match_len = 0;
6853 for (int idx = 0; idx < items_count; idx++)
6854 {
6855 const char* item_name = get_item_name_func(user_data, idx);
6856 const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);
6857 if (match_len <= longest_match_len)
6858 continue;
6859 longest_match_idx = idx;
6860 longest_match_len = match_len;
6861 if (match_len == req->SearchBufferLen)
6862 break;
6863 }
6864 return longest_match_idx;
6865}
6866
6867void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
6868{
6869#ifndef IMGUI_DISABLE_DEBUG_TOOLS
6870 Text("SearchBuffer = \"%s\"", data->SearchBuffer);
6871 Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);
6872 Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame);
6873#else
6874 IM_UNUSED(data);
6875#endif
6876}
6877
6878
6879//-------------------------------------------------------------------------
6880// [SECTION] Widgets: Multi-Select support
6881//-------------------------------------------------------------------------
6882
6883void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
6884{
6885 // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
6886 // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
6887 ImGuiContext& g = *GImGui;
6888 g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
6889 g.NextItemData.SelectionUserData = selection_user_data;
6890}
6891
6892
6893//-------------------------------------------------------------------------
6894// [SECTION] Widgets: ListBox
6895//-------------------------------------------------------------------------
6896// - BeginListBox()
6897// - EndListBox()
6898// - ListBox()
6899//-------------------------------------------------------------------------
6900
6901// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
6902// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
6903// 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).
6904bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
6905{
6906 ImGuiContext& g = *GImGui;
6907 ImGuiWindow* window = GetCurrentWindow();
6908 if (window->SkipItems)
6909 return false;
6910
6911 const ImGuiStyle& style = g.Style;
6912 const ImGuiID id = GetID(label);
6913 const ImVec2 label_size = CalcTextSize(label, NULL, true);
6914
6915 // Size default to hold ~7.25 items.
6916 // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
6917 ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
6918 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
6919 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
6920 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
6921 g.NextItemData.ClearFlags();
6922
6923 if (!IsRectVisible(bb.Min, bb.Max))
6924 {
6925 ItemSize(bb.GetSize(), style.FramePadding.y);
6926 ItemAdd(bb, 0, &frame_bb);
6927 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
6928 return false;
6929 }
6930
6931 // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well.
6932 BeginGroup();
6933 if (label_size.x > 0.0f)
6934 {
6935 ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
6936 RenderText(label_pos, label);
6937 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
6938 }
6939
6940 BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
6941 return true;
6942}
6943
6944void ImGui::EndListBox()
6945{
6946 ImGuiContext& g = *GImGui;
6947 ImGuiWindow* window = g.CurrentWindow;
6948 IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
6949 IM_UNUSED(window);
6950
6951 EndChild();
6952 EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
6953}
6954
6955bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
6956{
6957 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
6958 return value_changed;
6959}
6960
6961// This is merely a helper around BeginListBox(), EndListBox().
6962// Considering using those directly to submit custom data or store selection differently.
6963bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items)
6964{
6965 ImGuiContext& g = *GImGui;
6966
6967 // Calculate size from "height_in_items"
6968 if (height_in_items < 0)
6969 height_in_items = ImMin(items_count, 7);
6970 float height_in_items_f = height_in_items + 0.25f;
6971 ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
6972
6973 if (!BeginListBox(label, size))
6974 return false;
6975
6976 // Assume all items have even height (= 1 line of text). If you need items of different height,
6977 // you can create a custom version of ListBox() in your code without using the clipper.
6978 bool value_changed = false;
6979 ImGuiListClipper clipper;
6980 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.
6981 while (clipper.Step())
6982 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
6983 {
6984 const char* item_text = getter(user_data, i);
6985 if (item_text == NULL)
6986 item_text = "*Unknown item*";
6987
6988 PushID(i);
6989 const bool item_selected = (i == *current_item);
6990 if (Selectable(item_text, item_selected))
6991 {
6992 *current_item = i;
6993 value_changed = true;
6994 }
6995 if (item_selected)
6996 SetItemDefaultFocus();
6997 PopID();
6998 }
6999 EndListBox();
7000
7001 if (value_changed)
7002 MarkItemEdited(g.LastItemData.ID);
7003
7004 return value_changed;
7005}
7006
7007//-------------------------------------------------------------------------
7008// [SECTION] Widgets: PlotLines, PlotHistogram
7009//-------------------------------------------------------------------------
7010// - PlotEx() [Internal]
7011// - PlotLines()
7012// - PlotHistogram()
7013//-------------------------------------------------------------------------
7014// Plot/Graph widgets are not very good.
7015// Consider writing your own, or using a third-party one, see:
7016// - ImPlot https://github.com/epezent/implot
7017// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
7018//-------------------------------------------------------------------------
7019
7020int 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, const ImVec2& size_arg)
7021{
7022 ImGuiContext& g = *GImGui;
7023 ImGuiWindow* window = GetCurrentWindow();
7024 if (window->SkipItems)
7025 return -1;
7026
7027 const ImGuiStyle& style = g.Style;
7028 const ImGuiID id = window->GetID(label);
7029
7030 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7031 const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
7032
7033 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
7034 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
7035 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));
7036 ItemSize(total_bb, style.FramePadding.y);
7037 if (!ItemAdd(total_bb, 0, &frame_bb))
7038 return -1;
7039 const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
7040
7041 // Determine scale from values if not specified
7042 if (scale_min == FLT_MAX || scale_max == FLT_MAX)
7043 {
7044 float v_min = FLT_MAX;
7045 float v_max = -FLT_MAX;
7046 for (int i = 0; i < values_count; i++)
7047 {
7048 const float v = values_getter(data, i);
7049 if (v != v) // Ignore NaN values
7050 continue;
7051 v_min = ImMin(v_min, v);
7052 v_max = ImMax(v_max, v);
7053 }
7054 if (scale_min == FLT_MAX)
7055 scale_min = v_min;
7056 if (scale_max == FLT_MAX)
7057 scale_max = v_max;
7058 }
7059
7060 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
7061
7062 const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
7063 int idx_hovered = -1;
7064 if (values_count >= values_count_min)
7065 {
7066 int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
7067 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
7068
7069 // Tooltip on hover
7070 if (hovered && inner_bb.Contains(g.IO.MousePos))
7071 {
7072 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
7073 const int v_idx = (int)(t * item_count);
7074 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
7075
7076 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
7077 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
7078 if (plot_type == ImGuiPlotType_Lines)
7079 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
7080 else if (plot_type == ImGuiPlotType_Histogram)
7081 SetTooltip("%d: %8.4g", v_idx, v0);
7082 idx_hovered = v_idx;
7083 }
7084
7085 const float t_step = 1.0f / (float)res_w;
7086 const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
7087
7088 float v0 = values_getter(data, (0 + values_offset) % values_count);
7089 float t0 = 0.0f;
7090 ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
7091 float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
7092
7093 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
7094 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
7095
7096 for (int n = 0; n < res_w; n++)
7097 {
7098 const float t1 = t0 + t_step;
7099 const int v1_idx = (int)(t0 * item_count + 0.5f);
7100 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
7101 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
7102 const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
7103
7104 // 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.
7105 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
7106 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
7107 if (plot_type == ImGuiPlotType_Lines)
7108 {
7109 window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
7110 }
7111 else if (plot_type == ImGuiPlotType_Histogram)
7112 {
7113 if (pos1.x >= pos0.x + 2.0f)
7114 pos1.x -= 1.0f;
7115 window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
7116 }
7117
7118 t0 = t1;
7119 tp0 = tp1;
7120 }
7121 }
7122
7123 // Text overlay
7124 if (overlay_text)
7125 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));
7126
7127 if (label_size.x > 0.0f)
7128 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
7129
7130 // Return hovered index or -1 if none are hovered.
7131 // 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().
7132 return idx_hovered;
7133}
7134
7136{
7137 const float* Values;
7139
7140 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
7141};
7142
7143static float Plot_ArrayGetter(void* data, int idx)
7144{
7146 const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
7147 return v;
7148}
7149
7150void 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)
7151{
7152 ImGuiPlotArrayGetterData data(values, stride);
7153 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
7154}
7155
7156void 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)
7157{
7158 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
7159}
7160
7161void 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)
7162{
7163 ImGuiPlotArrayGetterData data(values, stride);
7164 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
7165}
7166
7167void 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)
7168{
7169 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
7170}
7171
7172//-------------------------------------------------------------------------
7173// [SECTION] Widgets: Value helpers
7174// Those is not very useful, legacy API.
7175//-------------------------------------------------------------------------
7176// - Value()
7177//-------------------------------------------------------------------------
7178
7179void ImGui::Value(const char* prefix, bool b)
7180{
7181 Text("%s: %s", prefix, (b ? "true" : "false"));
7182}
7183
7184void ImGui::Value(const char* prefix, int v)
7185{
7186 Text("%s: %d", prefix, v);
7187}
7188
7189void ImGui::Value(const char* prefix, unsigned int v)
7190{
7191 Text("%s: %d", prefix, v);
7192}
7193
7194void ImGui::Value(const char* prefix, float v, const char* float_format)
7195{
7196 if (float_format)
7197 {
7198 char fmt[64];
7199 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
7200 Text(fmt, prefix, v);
7201 }
7202 else
7203 {
7204 Text("%s: %.3f", prefix, v);
7205 }
7206}
7207
7208//-------------------------------------------------------------------------
7209// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
7210//-------------------------------------------------------------------------
7211// - ImGuiMenuColumns [Internal]
7212// - BeginMenuBar()
7213// - EndMenuBar()
7214// - BeginMainMenuBar()
7215// - EndMainMenuBar()
7216// - BeginMenu()
7217// - EndMenu()
7218// - MenuItemEx() [Internal]
7219// - MenuItem()
7220//-------------------------------------------------------------------------
7221
7222// Helpers for internal use
7223void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
7224{
7225 if (window_reappearing)
7226 memset(Widths, 0, sizeof(Widths));
7227 Spacing = (ImU16)spacing;
7228 CalcNextTotalWidth(true);
7229 memset(Widths, 0, sizeof(Widths));
7230 TotalWidth = NextTotalWidth;
7231 NextTotalWidth = 0;
7232}
7233
7234void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
7235{
7236 ImU16 offset = 0;
7237 bool want_spacing = false;
7238 for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
7239 {
7240 ImU16 width = Widths[i];
7241 if (want_spacing && width > 0)
7242 offset += Spacing;
7243 want_spacing |= (width > 0);
7244 if (update_offsets)
7245 {
7246 if (i == 1) { OffsetLabel = offset; }
7247 if (i == 2) { OffsetShortcut = offset; }
7248 if (i == 3) { OffsetMark = offset; }
7249 }
7250 offset += width;
7251 }
7252 NextTotalWidth = offset;
7253}
7254
7255float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
7256{
7257 Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
7258 Widths[1] = ImMax(Widths[1], (ImU16)w_label);
7259 Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
7260 Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
7261 CalcNextTotalWidth(false);
7262 return (float)ImMax(TotalWidth, NextTotalWidth);
7263}
7264
7265// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
7266// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
7267// 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.
7268// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
7269bool ImGui::BeginMenuBar()
7270{
7271 ImGuiWindow* window = GetCurrentWindow();
7272 if (window->SkipItems)
7273 return false;
7274 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
7275 return false;
7276
7277 IM_ASSERT(!window->DC.MenuBarAppending);
7278 BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
7279 PushID("##menubar");
7280
7281 // 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.
7282 // 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.
7283 ImRect bar_rect = window->MenuBarRect();
7284 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));
7285 clip_rect.ClipWith(window->OuterRectClipped);
7286 PushClipRect(clip_rect.Min, clip_rect.Max, false);
7287
7288 // 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).
7289 window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
7290 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
7291 window->DC.IsSameLine = false;
7292 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
7293 window->DC.MenuBarAppending = true;
7294 AlignTextToFramePadding();
7295 return true;
7296}
7297
7298void ImGui::EndMenuBar()
7299{
7300 ImGuiWindow* window = GetCurrentWindow();
7301 if (window->SkipItems)
7302 return;
7303 ImGuiContext& g = *GImGui;
7304
7305 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
7306 if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
7307 {
7308 // Try to find out if the request is for one of our child menu
7309 ImGuiWindow* nav_earliest_child = g.NavWindow;
7310 while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
7311 nav_earliest_child = nav_earliest_child->ParentWindow;
7312 if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
7313 {
7314 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
7315 // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth bothering)
7316 const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
7317 IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)
7318 FocusWindow(window);
7319 SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
7320 g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
7321 g.NavDisableMouseHover = g.NavMousePosDirty = true;
7322 NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
7323 }
7324 }
7325
7326 IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
7327 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
7328 IM_ASSERT(window->DC.MenuBarAppending);
7329 PopClipRect();
7330 PopID();
7331 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.
7332
7333 // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here.
7334 ImGuiGroupData& group_data = g.GroupStack.back();
7335 group_data.EmitItem = false;
7336 ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;
7337 window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent.
7338 EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore
7339 window->DC.LayoutType = ImGuiLayoutType_Vertical;
7340 window->DC.IsSameLine = false;
7341 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
7342 window->DC.MenuBarAppending = false;
7343 window->DC.CursorMaxPos = restore_cursor_max_pos;
7344}
7345
7346// Important: calling order matters!
7347// FIXME: Somehow overlapping with docking tech.
7348// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
7349bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
7350{
7351 IM_ASSERT(dir != ImGuiDir_None);
7352
7353 ImGuiWindow* bar_window = FindWindowByName(name);
7354 ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
7355 if (bar_window == NULL || bar_window->BeginCount == 0)
7356 {
7357 // Calculate and set window size/position
7358 ImRect avail_rect = viewport->GetBuildWorkRect();
7359 ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
7360 ImVec2 pos = avail_rect.Min;
7361 if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
7362 pos[axis] = avail_rect.Max[axis] - axis_size;
7363 ImVec2 size = avail_rect.GetSize();
7364 size[axis] = axis_size;
7365 SetNextWindowPos(pos);
7366 SetNextWindowSize(size);
7367
7368 // Report our size into work area (for next frame) using actual window size
7369 if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
7370 viewport->BuildWorkOffsetMin[axis] += axis_size;
7371 else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
7372 viewport->BuildWorkOffsetMax[axis] -= axis_size;
7373 }
7374
7375 window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
7376 SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
7377 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
7378 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
7379 bool is_open = Begin(name, NULL, window_flags);
7380 PopStyleVar(2);
7381
7382 return is_open;
7383}
7384
7385bool ImGui::BeginMainMenuBar()
7386{
7387 ImGuiContext& g = *GImGui;
7388 ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
7389
7390 // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
7391 SetCurrentViewport(NULL, viewport);
7392
7393 // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
7394 // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
7395 // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
7396 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
7397 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
7398 float height = GetFrameHeight();
7399 bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
7400 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
7401
7402 if (is_open)
7403 BeginMenuBar();
7404 else
7405 End();
7406 return is_open;
7407}
7408
7409void ImGui::EndMainMenuBar()
7410{
7411 EndMenuBar();
7412
7413 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
7414 // FIXME: With this strategy we won't be able to restore a NULL focus.
7415 ImGuiContext& g = *GImGui;
7416 if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
7417 FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
7418
7419 End();
7420}
7421
7422static bool IsRootOfOpenMenuSet()
7423{
7424 ImGuiContext& g = *GImGui;
7425 ImGuiWindow* window = g.CurrentWindow;
7426 if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
7427 return false;
7428
7429 // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others
7430 // (e.g. inside menu bar vs loose menu items) based on parent ID.
7431 // This would however prevent the use of e.g. PushID() user code submitting menus.
7432 // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
7433 // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
7434 // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
7435 // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.
7436 // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer.
7437 // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still
7438 // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart
7439 // it likely won't be a problem anyone runs into.
7440 const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
7441 if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)
7442 return false;
7443 return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false);
7444}
7445
7446bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
7447{
7448 ImGuiWindow* window = GetCurrentWindow();
7449 if (window->SkipItems)
7450 return false;
7451
7452 ImGuiContext& g = *GImGui;
7453 const ImGuiStyle& style = g.Style;
7454 const ImGuiID id = window->GetID(label);
7455 bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
7456
7457 // 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)
7458 // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.
7459 ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
7460 if (window->Flags & ImGuiWindowFlags_ChildMenu)
7461 window_flags |= ImGuiWindowFlags_ChildWindow;
7462
7463 // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
7464 // 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.
7465 // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
7466 if (g.MenusIdSubmittedThisFrame.contains(id))
7467 {
7468 if (menu_is_open)
7469 menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
7470 else
7471 g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values
7472 return menu_is_open;
7473 }
7474
7475 // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
7476 g.MenusIdSubmittedThisFrame.push_back(id);
7477
7478 ImVec2 label_size = CalcTextSize(label, NULL, true);
7479
7480 // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)
7481 // This is only done for items for the menu set and not the full parent window.
7482 const bool menuset_is_open = IsRootOfOpenMenuSet();
7483 if (menuset_is_open)
7484 PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
7485
7486 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
7487 // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
7488 // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
7489 ImVec2 popup_pos, pos = window->DC.CursorPos;
7490 PushID(label);
7491 if (!enabled)
7492 BeginDisabled();
7493 const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
7494 bool pressed;
7495
7496 // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
7497 const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups;
7498 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
7499 {
7500 // Menu inside an horizontal menu bar
7501 // Selectable extend their highlight by half ItemSpacing in each direction.
7502 // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
7503 popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
7504 window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
7505 PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
7506 float w = label_size.x;
7507 ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
7508 pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y));
7509 RenderText(text_pos, label);
7510 PopStyleVar();
7511 window->DC.CursorPos.x += IM_TRUNC(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().
7512 }
7513 else
7514 {
7515 // Menu inside a regular/vertical menu
7516 // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
7517 // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
7518 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
7519 float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
7520 float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
7521 float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
7522 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
7523 ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
7524 pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
7525 RenderText(text_pos, label);
7526 if (icon_w > 0.0f)
7527 RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
7528 RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
7529 }
7530 if (!enabled)
7531 EndDisabled();
7532
7533 const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover;
7534 if (menuset_is_open)
7535 PopItemFlag();
7536
7537 bool want_open = false;
7538 bool want_open_nav_init = false;
7539 bool want_close = false;
7540 if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
7541 {
7542 // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
7543 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
7544 bool moving_toward_child_menu = false;
7545 ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)
7546 ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;
7547 if (g.HoveredWindow == window && child_menu_window != NULL)
7548 {
7549 const float ref_unit = g.FontSize; // FIXME-DPI
7550 const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
7551 const ImRect next_window_rect = child_menu_window->Rect();
7552 ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
7553 ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
7554 ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();
7555 const float pad_farmost_h = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // Add a bit of extra slack.
7556 ta.x += child_dir * -0.5f;
7557 tb.x += child_dir * ref_unit;
7558 tc.x += child_dir * ref_unit;
7559 tb.y = ta.y + ImMax((tb.y - pad_farmost_h) - ta.y, -ref_unit * 8.0f); // Triangle has maximum height to limit the slope and the bias toward large sub-menus
7560 tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);
7561 moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
7562 //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
7563 }
7564
7565 // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not)
7566 // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon.
7567 // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.)
7568 if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavDisableMouseHover && g.ActiveId == 0)
7569 want_close = true;
7570
7571 // Open
7572 // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)
7573 if (!menu_is_open && pressed) // Click/activate to open
7574 want_open = true;
7575 else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
7576 want_open = true;
7577 else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback)
7578 want_open = true;
7579 if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
7580 {
7581 want_open = want_open_nav_init = true;
7582 NavMoveRequestCancel();
7583 NavRestoreHighlightAfterMove();
7584 }
7585 }
7586 else
7587 {
7588 // Menu bar
7589 if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
7590 {
7591 want_close = true;
7592 want_open = menu_is_open = false;
7593 }
7594 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
7595 {
7596 want_open = true;
7597 }
7598 else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
7599 {
7600 want_open = true;
7601 NavMoveRequestCancel();
7602 }
7603 }
7604
7605 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.. }'
7606 want_close = true;
7607 if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
7608 ClosePopupToLevel(g.BeginPopupStack.Size, true);
7609
7610 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
7611 PopID();
7612
7613 if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
7614 {
7615 // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.
7616 OpenPopup(label);
7617 }
7618 else if (want_open)
7619 {
7620 menu_is_open = true;
7621 OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
7622 }
7623
7624 if (menu_is_open)
7625 {
7626 ImGuiLastItemData last_item_in_parent = g.LastItemData;
7627 SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
7628 PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
7629 menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
7630 PopStyleVar();
7631 if (menu_is_open)
7632 {
7633 // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
7634 // Perform an init request in the case the popup was already open (via a previous mouse hover)
7635 if (want_open && want_open_nav_init && !g.NavInitRequest)
7636 {
7637 FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
7638 NavInitWindow(g.CurrentWindow, false);
7639 }
7640
7641 // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
7642 // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
7643 g.LastItemData = last_item_in_parent;
7644 if (g.HoveredWindow == window)
7645 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
7646 }
7647 }
7648 else
7649 {
7650 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
7651 }
7652
7653 return menu_is_open;
7654}
7655
7656bool ImGui::BeginMenu(const char* label, bool enabled)
7657{
7658 return BeginMenuEx(label, NULL, enabled);
7659}
7660
7661void ImGui::EndMenu()
7662{
7663 // Nav: When a left move request our menu failed, close ourselves.
7664 ImGuiContext& g = *GImGui;
7665 ImGuiWindow* window = g.CurrentWindow;
7666 IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls
7667 ImGuiWindow* parent_window = window->ParentWindow; // Should always be != NULL is we passed assert.
7668 if (window->BeginCount == window->BeginCountPreviousFrame)
7669 if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
7670 if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
7671 {
7672 ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
7673 NavMoveRequestCancel();
7674 }
7675
7676 EndPopup();
7677}
7678
7679bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
7680{
7681 ImGuiWindow* window = GetCurrentWindow();
7682 if (window->SkipItems)
7683 return false;
7684
7685 ImGuiContext& g = *GImGui;
7686 ImGuiStyle& style = g.Style;
7687 ImVec2 pos = window->DC.CursorPos;
7688 ImVec2 label_size = CalcTextSize(label, NULL, true);
7689
7690 // See BeginMenuEx() for comments about this.
7691 const bool menuset_is_open = IsRootOfOpenMenuSet();
7692 if (menuset_is_open)
7693 PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
7694
7695 // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
7696 // 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.
7697 bool pressed;
7698 PushID(label);
7699 if (!enabled)
7700 BeginDisabled();
7701
7702 // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
7703 const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;
7704 const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
7705 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
7706 {
7707 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
7708 // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
7709 float w = label_size.x;
7710 window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
7711 ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
7712 PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
7713 pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f));
7714 PopStyleVar();
7715 if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
7716 RenderText(text_pos, label);
7717 window->DC.CursorPos.x += IM_TRUNC(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().
7718 }
7719 else
7720 {
7721 // Menu item inside a vertical menu
7722 // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
7723 // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
7724 float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
7725 float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
7726 float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
7727 float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
7728 float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
7729 pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
7730 if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
7731 {
7732 RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
7733 if (icon_w > 0.0f)
7734 RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
7735 if (shortcut_w > 0.0f)
7736 {
7737 PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
7738 RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
7739 PopStyleColor();
7740 }
7741 if (selected)
7742 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);
7743 }
7744 }
7745 IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
7746 if (!enabled)
7747 EndDisabled();
7748 PopID();
7749 if (menuset_is_open)
7750 PopItemFlag();
7751
7752 return pressed;
7753}
7754
7755bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
7756{
7757 return MenuItemEx(label, NULL, shortcut, selected, enabled);
7758}
7759
7760bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
7761{
7762 if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
7763 {
7764 if (p_selected)
7765 *p_selected = !*p_selected;
7766 return true;
7767 }
7768 return false;
7769}
7770
7771//-------------------------------------------------------------------------
7772// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
7773//-------------------------------------------------------------------------
7774// - BeginTabBar()
7775// - BeginTabBarEx() [Internal]
7776// - EndTabBar()
7777// - TabBarLayout() [Internal]
7778// - TabBarCalcTabID() [Internal]
7779// - TabBarCalcMaxTabWidth() [Internal]
7780// - TabBarFindTabById() [Internal]
7781// - TabBarFindTabByOrder() [Internal]
7782// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal]
7783// - TabBarGetCurrentTab() [Internal]
7784// - TabBarGetTabName() [Internal]
7785// - TabBarAddTab() [Internal]
7786// - TabBarRemoveTab() [Internal]
7787// - TabBarCloseTab() [Internal]
7788// - TabBarScrollClamp() [Internal]
7789// - TabBarScrollToTab() [Internal]
7790// - TabBarQueueFocus() [Internal]
7791// - TabBarQueueReorder() [Internal]
7792// - TabBarProcessReorderFromMousePos() [Internal]
7793// - TabBarProcessReorder() [Internal]
7794// - TabBarScrollingButtons() [Internal]
7795// - TabBarTabListPopupButton() [Internal]
7796//-------------------------------------------------------------------------
7797
7799{
7800 int TabCount; // Number of tabs in this section.
7801 float Width; // Sum of width of tabs in this section (after shrinking down)
7802 float Spacing; // Horizontal spacing at the end of the section.
7803
7804 ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
7805};
7806
7807namespace ImGui
7808{
7809 static void TabBarLayout(ImGuiTabBar* tab_bar);
7810 static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
7811 static float TabBarCalcMaxTabWidth();
7812 static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
7813 static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
7814 static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar);
7815 static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
7816}
7817
7818ImGuiTabBar::ImGuiTabBar()
7819{
7820 memset(this, 0, sizeof(*this));
7821 CurrFrameVisible = PrevFrameVisible = -1;
7822 LastTabItemIdx = -1;
7823}
7824
7825static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
7826{
7827 return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
7828}
7829
7830static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
7831{
7832 const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
7833 const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
7834 const int a_section = TabItemGetSectionIdx(a);
7835 const int b_section = TabItemGetSectionIdx(b);
7836 if (a_section != b_section)
7837 return a_section - b_section;
7838 return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
7839}
7840
7841static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
7842{
7843 const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
7844 const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
7845 return (int)(a->BeginOrder - b->BeginOrder);
7846}
7847
7848static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
7849{
7850 ImGuiContext& g = *GImGui;
7851 return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
7852}
7853
7854static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
7855{
7856 ImGuiContext& g = *GImGui;
7857 if (g.TabBars.Contains(tab_bar))
7858 return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
7859 return ImGuiPtrOrIndex(tab_bar);
7860}
7861
7862bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
7863{
7864 ImGuiContext& g = *GImGui;
7865 ImGuiWindow* window = g.CurrentWindow;
7866 if (window->SkipItems)
7867 return false;
7868
7869 ImGuiID id = window->GetID(str_id);
7870 ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
7871 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);
7872 tab_bar->ID = id;
7873 tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);
7874 tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);
7875 return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
7876}
7877
7878bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
7879{
7880 ImGuiContext& g = *GImGui;
7881 ImGuiWindow* window = g.CurrentWindow;
7882 if (window->SkipItems)
7883 return false;
7884
7885 IM_ASSERT(tab_bar->ID != 0);
7886 if ((flags & ImGuiTabBarFlags_DockNode) == 0)
7887 PushOverrideID(tab_bar->ID);
7888
7889 // Add to stack
7890 g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
7891 g.CurrentTabBar = tab_bar;
7892
7893 // Append with multiple BeginTabBar()/EndTabBar() pairs.
7894 tab_bar->BackupCursorPos = window->DC.CursorPos;
7895 if (tab_bar->CurrFrameVisible == g.FrameCount)
7896 {
7897 window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
7898 tab_bar->BeginCount++;
7899 return true;
7900 }
7901
7902 // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
7903 if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
7904 if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
7905 ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
7906 tab_bar->TabsAddedNew = false;
7907
7908 // Flags
7909 if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
7910 flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
7911
7912 tab_bar->Flags = flags;
7913 tab_bar->BarRect = tab_bar_bb;
7914 tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
7915 tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
7916 tab_bar->CurrFrameVisible = g.FrameCount;
7917 tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
7918 tab_bar->CurrTabsContentsHeight = 0.0f;
7919 tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
7920 tab_bar->FramePadding = g.Style.FramePadding;
7921 tab_bar->TabsActiveCount = 0;
7922 tab_bar->LastTabItemIdx = -1;
7923 tab_bar->BeginCount = 1;
7924
7925 // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
7926 window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
7927
7928 // Draw separator
7929 // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable)
7930 const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
7931 if (g.Style.TabBarBorderSize > 0.0f)
7932 {
7933 const float y = tab_bar->BarRect.Max.y;
7934 window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col);
7935 }
7936 return true;
7937}
7938
7939void ImGui::EndTabBar()
7940{
7941 ImGuiContext& g = *GImGui;
7942 ImGuiWindow* window = g.CurrentWindow;
7943 if (window->SkipItems)
7944 return;
7945
7946 ImGuiTabBar* tab_bar = g.CurrentTabBar;
7947 if (tab_bar == NULL)
7948 {
7949 IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
7950 return;
7951 }
7952
7953 // Fallback in case no TabItem have been submitted
7954 if (tab_bar->WantLayout)
7955 TabBarLayout(tab_bar);
7956
7957 // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
7958 const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
7959 if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
7960 {
7961 tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
7962 window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
7963 }
7964 else
7965 {
7966 window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
7967 }
7968 if (tab_bar->BeginCount > 1)
7969 window->DC.CursorPos = tab_bar->BackupCursorPos;
7970
7971 tab_bar->LastTabItemIdx = -1;
7972 if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
7973 PopID();
7974
7975 g.CurrentTabBarStack.pop_back();
7976 g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
7977}
7978
7979// Scrolling happens only in the central section (leading/trailing sections are not scrolling)
7980static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)
7981{
7982 return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
7983}
7984
7985// This is called only once a frame before by the first call to ItemTab()
7986// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
7987static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
7988{
7989 ImGuiContext& g = *GImGui;
7990 tab_bar->WantLayout = false;
7991
7992 // Garbage collect by compacting list
7993 // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
7994 int tab_dst_n = 0;
7995 bool need_sort_by_section = false;
7996 ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
7997 for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
7998 {
7999 ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
8000 if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
8001 {
8002 // Remove tab
8003 if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
8004 if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
8005 if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
8006 continue;
8007 }
8008 if (tab_dst_n != tab_src_n)
8009 tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
8010
8011 tab = &tab_bar->Tabs[tab_dst_n];
8012 tab->IndexDuringLayout = (ImS16)tab_dst_n;
8013
8014 // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
8015 int curr_tab_section_n = TabItemGetSectionIdx(tab);
8016 if (tab_dst_n > 0)
8017 {
8018 ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
8019 int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
8020 if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
8021 need_sort_by_section = true;
8022 if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
8023 need_sort_by_section = true;
8024 }
8025
8026 sections[curr_tab_section_n].TabCount++;
8027 tab_dst_n++;
8028 }
8029 if (tab_bar->Tabs.Size != tab_dst_n)
8030 tab_bar->Tabs.resize(tab_dst_n);
8031
8032 if (need_sort_by_section)
8033 ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
8034
8035 // Calculate spacing between sections
8036 sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
8037 sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
8038
8039 // Setup next selected tab
8040 ImGuiID scroll_to_tab_id = 0;
8041 if (tab_bar->NextSelectedTabId)
8042 {
8043 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
8044 tab_bar->NextSelectedTabId = 0;
8045 scroll_to_tab_id = tab_bar->SelectedTabId;
8046 }
8047
8048 // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
8049 if (tab_bar->ReorderRequestTabId != 0)
8050 {
8051 if (TabBarProcessReorder(tab_bar))
8052 if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
8053 scroll_to_tab_id = tab_bar->ReorderRequestTabId;
8054 tab_bar->ReorderRequestTabId = 0;
8055 }
8056
8057 // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
8058 const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
8059 if (tab_list_popup_button)
8060 if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
8061 scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
8062
8063 // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
8064 // (whereas our tabs are stored as: leading, central, trailing)
8065 int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
8066 g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
8067
8068 // Compute ideal tabs widths + store them into shrink buffer
8069 ImGuiTabItem* most_recently_selected_tab = NULL;
8070 int curr_section_n = -1;
8071 bool found_selected_tab_id = false;
8072 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
8073 {
8074 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
8075 IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
8076
8077 if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
8078 most_recently_selected_tab = tab;
8079 if (tab->ID == tab_bar->SelectedTabId)
8080 found_selected_tab_id = true;
8081 if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
8082 scroll_to_tab_id = tab->ID;
8083
8084 // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
8085 // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
8086 // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
8087 const char* tab_name = TabBarGetTabName(tab_bar, tab);
8088 const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
8089 tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
8090
8091 int section_n = TabItemGetSectionIdx(tab);
8092 ImGuiTabBarSection* section = &sections[section_n];
8093 section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
8094 curr_section_n = section_n;
8095
8096 // Store data so we can build an array sorted by width if we need to shrink tabs down
8097 IM_MSVC_WARNING_SUPPRESS(6385);
8098 ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
8099 shrink_width_item->Index = tab_n;
8100 shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
8101 tab->Width = ImMax(tab->ContentWidth, 1.0f);
8102 }
8103
8104 // Compute total ideal width (used for e.g. auto-resizing a window)
8105 tab_bar->WidthAllTabsIdeal = 0.0f;
8106 for (int section_n = 0; section_n < 3; section_n++)
8107 tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
8108
8109 // Horizontal scrolling buttons
8110 // (note that TabBarScrollButtons() will alter BarRect.Max.x)
8111 if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
8112 if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
8113 {
8114 scroll_to_tab_id = scroll_and_select_tab->ID;
8115 if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
8116 tab_bar->SelectedTabId = scroll_to_tab_id;
8117 }
8118
8119 // Shrink widths if full tabs don't fit in their allocated space
8120 float section_0_w = sections[0].Width + sections[0].Spacing;
8121 float section_1_w = sections[1].Width + sections[1].Spacing;
8122 float section_2_w = sections[2].Width + sections[2].Spacing;
8123 bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
8124 float width_excess;
8125 if (central_section_is_visible)
8126 width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
8127 else
8128 width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
8129
8130 // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
8131 if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
8132 {
8133 int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
8134 int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
8135 ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
8136
8137 // Apply shrunk values into tabs and sections
8138 for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
8139 {
8140 ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
8141 float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);
8142 if (shrinked_width < 0.0f)
8143 continue;
8144
8145 shrinked_width = ImMax(1.0f, shrinked_width);
8146 int section_n = TabItemGetSectionIdx(tab);
8147 sections[section_n].Width -= (tab->Width - shrinked_width);
8148 tab->Width = shrinked_width;
8149 }
8150 }
8151
8152 // Layout all active tabs
8153 int section_tab_index = 0;
8154 float tab_offset = 0.0f;
8155 tab_bar->WidthAllTabs = 0.0f;
8156 for (int section_n = 0; section_n < 3; section_n++)
8157 {
8158 ImGuiTabBarSection* section = &sections[section_n];
8159 if (section_n == 2)
8160 tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
8161
8162 for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
8163 {
8164 ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
8165 tab->Offset = tab_offset;
8166 tab->NameOffset = -1;
8167 tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
8168 }
8169 tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
8170 tab_offset += section->Spacing;
8171 section_tab_index += section->TabCount;
8172 }
8173
8174 // Clear name buffers
8175 tab_bar->TabsNames.Buf.resize(0);
8176
8177 // If we have lost the selected tab, select the next most recently active one
8178 if (found_selected_tab_id == false)
8179 tab_bar->SelectedTabId = 0;
8180 if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
8181 scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
8182
8183 // Lock in visible tab
8184 tab_bar->VisibleTabId = tab_bar->SelectedTabId;
8185 tab_bar->VisibleTabWasSubmitted = false;
8186
8187 // CTRL+TAB can override visible tab temporarily
8188 if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
8189 tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId;
8190
8191 // Apply request requests
8192 if (scroll_to_tab_id != 0)
8193 TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
8194 else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
8195 {
8196 const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
8197 const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;
8198 if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)
8199 {
8200 const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;
8201 tab_bar->ScrollingTargetDistToVisibility = 0.0f;
8202 tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);
8203 }
8204 SetKeyOwner(wheel_key, tab_bar->ID);
8205 }
8206
8207 // Update scrolling
8208 tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
8209 tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
8210 if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
8211 {
8212 // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
8213 // Teleport if we are aiming far off the visible line
8214 tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
8215 tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
8216 const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
8217 tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
8218 }
8219 else
8220 {
8221 tab_bar->ScrollingSpeed = 0.0f;
8222 }
8223 tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
8224 tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
8225
8226 // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
8227 ImGuiWindow* window = g.CurrentWindow;
8228 window->DC.CursorPos = tab_bar->BarRect.Min;
8229 ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
8230 window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
8231}
8232
8233// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.
8234static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
8235{
8236 if (docked_window != NULL)
8237 {
8238 IM_UNUSED(tab_bar);
8239 IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
8240 ImGuiID id = docked_window->TabId;
8241 KeepAliveID(id);
8242 return id;
8243 }
8244 else
8245 {
8246 ImGuiWindow* window = GImGui->CurrentWindow;
8247 return window->GetID(label);
8248 }
8249}
8250
8251static float ImGui::TabBarCalcMaxTabWidth()
8252{
8253 ImGuiContext& g = *GImGui;
8254 return g.FontSize * 20.0f;
8255}
8256
8257ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
8258{
8259 if (tab_id != 0)
8260 for (int n = 0; n < tab_bar->Tabs.Size; n++)
8261 if (tab_bar->Tabs[n].ID == tab_id)
8262 return &tab_bar->Tabs[n];
8263 return NULL;
8264}
8265
8266// Order = visible order, not submission order! (which is tab->BeginOrder)
8267ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)
8268{
8269 if (order < 0 || order >= tab_bar->Tabs.Size)
8270 return NULL;
8271 return &tab_bar->Tabs[order];
8272}
8273
8274// FIXME: See references to #2304 in TODO.txt
8275ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
8276{
8277 ImGuiTabItem* most_recently_selected_tab = NULL;
8278 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
8279 {
8280 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
8281 if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
8282 if (tab->Window && tab->Window->WasActive)
8283 most_recently_selected_tab = tab;
8284 }
8285 return most_recently_selected_tab;
8286}
8287
8288ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)
8289{
8290 if (tab_bar->LastTabItemIdx <= 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)
8291 return NULL;
8292 return &tab_bar->Tabs[tab_bar->LastTabItemIdx];
8293}
8294
8295const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
8296{
8297 if (tab->Window)
8298 return tab->Window->Name;
8299 if (tab->NameOffset == -1)
8300 return "N/A";
8301 IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);
8302 return tab_bar->TabsNames.Buf.Data + tab->NameOffset;
8303}
8304
8305// The purpose of this call is to register tab in advance so we can control their order at the time they appear.
8306// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
8307void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
8308{
8309 ImGuiContext& g = *GImGui;
8310 IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL);
8311 IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame)
8312
8313 if (!window->HasCloseButton)
8314 tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation.
8315
8316 ImGuiTabItem new_tab;
8317 new_tab.ID = window->TabId;
8318 new_tab.Flags = tab_flags;
8319 new_tab.LastFrameVisible = tab_bar->CurrFrameVisible; // Required so BeginTabBar() doesn't ditch the tab
8320 if (new_tab.LastFrameVisible == -1)
8321 new_tab.LastFrameVisible = g.FrameCount - 1;
8322 new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission
8323 tab_bar->Tabs.push_back(new_tab);
8324}
8325
8326// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
8327void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
8328{
8329 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
8330 tab_bar->Tabs.erase(tab);
8331 if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; }
8332 if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; }
8333 if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
8334}
8335
8336// Called on manual closure attempt
8337void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
8338{
8339 if (tab->Flags & ImGuiTabItemFlags_Button)
8340 return; // A button appended with TabItemButton().
8341
8342 if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)
8343 {
8344 // This will remove a frame of lag for selecting another tab on closure.
8345 // 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
8346 tab->WantClose = true;
8347 if (tab_bar->VisibleTabId == tab->ID)
8348 {
8349 tab->LastFrameVisible = -1;
8350 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
8351 }
8352 }
8353 else
8354 {
8355 // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
8356 if (tab_bar->VisibleTabId != tab->ID)
8357 TabBarQueueFocus(tab_bar, tab);
8358 }
8359}
8360
8361static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
8362{
8363 scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
8364 return ImMax(scrolling, 0.0f);
8365}
8366
8367// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
8368static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
8369{
8370 ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
8371 if (tab == NULL)
8372 return;
8373 if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
8374 return;
8375
8376 ImGuiContext& g = *GImGui;
8377 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)
8378 int order = TabBarGetTabOrder(tab_bar, tab);
8379
8380 // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
8381 float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);
8382
8383 // We make all tabs positions all relative Sections[0].Width to make code simpler
8384 float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
8385 float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
8386 tab_bar->ScrollingTargetDistToVisibility = 0.0f;
8387 if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
8388 {
8389 // Scroll to the left
8390 tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
8391 tab_bar->ScrollingTarget = tab_x1;
8392 }
8393 else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
8394 {
8395 // Scroll to the right
8396 tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
8397 tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
8398 }
8399}
8400
8401void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
8402{
8403 tab_bar->NextSelectedTabId = tab->ID;
8404}
8405
8406void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)
8407{
8408 IM_ASSERT(offset != 0);
8409 IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
8410 tab_bar->ReorderRequestTabId = tab->ID;
8411 tab_bar->ReorderRequestOffset = (ImS16)offset;
8412}
8413
8414void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)
8415{
8416 ImGuiContext& g = *GImGui;
8417 IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
8418 if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
8419 return;
8420
8421 const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
8422 const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
8423
8424 // Count number of contiguous tabs we are crossing over
8425 const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
8426 const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
8427 int dst_idx = src_idx;
8428 for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
8429 {
8430 // Reordered tabs must share the same section
8431 const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
8432 if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
8433 break;
8434 if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
8435 break;
8436 dst_idx = i;
8437
8438 // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
8439 const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
8440 const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
8441 //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
8442 if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
8443 break;
8444 }
8445
8446 if (dst_idx != src_idx)
8447 TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
8448}
8449
8450bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
8451{
8452 ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
8453 if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
8454 return false;
8455
8456 //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
8457 int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;
8458 if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
8459 return false;
8460
8461 // Reordered tabs must share the same section
8462 // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
8463 ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
8464 if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
8465 return false;
8466 if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
8467 return false;
8468
8469 ImGuiTabItem item_tmp = *tab1;
8470 ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
8471 ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
8472 const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
8473 memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
8474 *tab2 = item_tmp;
8475
8476 if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
8477 MarkIniSettingsDirty();
8478 return true;
8479}
8480
8481static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
8482{
8483 ImGuiContext& g = *GImGui;
8484 ImGuiWindow* window = g.CurrentWindow;
8485
8486 const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
8487 const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
8488
8489 const ImVec2 backup_cursor_pos = window->DC.CursorPos;
8490 //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));
8491
8492 int select_dir = 0;
8493 ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
8494 arrow_col.w *= 0.5f;
8495
8496 PushStyleColor(ImGuiCol_Text, arrow_col);
8497 PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
8498 const float backup_repeat_delay = g.IO.KeyRepeatDelay;
8499 const float backup_repeat_rate = g.IO.KeyRepeatRate;
8500 g.IO.KeyRepeatDelay = 0.250f;
8501 g.IO.KeyRepeatRate = 0.200f;
8502 float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
8503 window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
8504 if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
8505 select_dir = -1;
8506 window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
8507 if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
8508 select_dir = +1;
8509 PopStyleColor(2);
8510 g.IO.KeyRepeatRate = backup_repeat_rate;
8511 g.IO.KeyRepeatDelay = backup_repeat_delay;
8512
8513 ImGuiTabItem* tab_to_scroll_to = NULL;
8514 if (select_dir != 0)
8515 if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
8516 {
8517 int selected_order = TabBarGetTabOrder(tab_bar, tab_item);
8518 int target_order = selected_order + select_dir;
8519
8520 // Skip tab item buttons until another tab item is found or end is reached
8521 while (tab_to_scroll_to == NULL)
8522 {
8523 // If we are at the end of the list, still scroll to make our tab visible
8524 tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
8525
8526 // Cross through buttons
8527 // (even if first/last item is a button, return it so we can update the scroll)
8528 if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
8529 {
8530 target_order += select_dir;
8531 selected_order += select_dir;
8532 tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
8533 }
8534 }
8535 }
8536 window->DC.CursorPos = backup_cursor_pos;
8537 tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
8538
8539 return tab_to_scroll_to;
8540}
8541
8542static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
8543{
8544 ImGuiContext& g = *GImGui;
8545 ImGuiWindow* window = g.CurrentWindow;
8546
8547 // We use g.Style.FramePadding.y to match the square ArrowButton size
8548 const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
8549 const ImVec2 backup_cursor_pos = window->DC.CursorPos;
8550 window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
8551 tab_bar->BarRect.Min.x += tab_list_popup_button_width;
8552
8553 ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
8554 arrow_col.w *= 0.5f;
8555 PushStyleColor(ImGuiCol_Text, arrow_col);
8556 PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
8557 bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
8558 PopStyleColor(2);
8559
8560 ImGuiTabItem* tab_to_select = NULL;
8561 if (open)
8562 {
8563 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
8564 {
8565 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
8566 if (tab->Flags & ImGuiTabItemFlags_Button)
8567 continue;
8568
8569 const char* tab_name = TabBarGetTabName(tab_bar, tab);
8570 if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
8571 tab_to_select = tab;
8572 }
8573 EndCombo();
8574 }
8575
8576 window->DC.CursorPos = backup_cursor_pos;
8577 return tab_to_select;
8578}
8579
8580//-------------------------------------------------------------------------
8581// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
8582//-------------------------------------------------------------------------
8583// - BeginTabItem()
8584// - EndTabItem()
8585// - TabItemButton()
8586// - TabItemEx() [Internal]
8587// - SetTabItemClosed()
8588// - TabItemCalcSize() [Internal]
8589// - TabItemBackground() [Internal]
8590// - TabItemLabelAndCloseButton() [Internal]
8591//-------------------------------------------------------------------------
8592
8593bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
8594{
8595 ImGuiContext& g = *GImGui;
8596 ImGuiWindow* window = g.CurrentWindow;
8597 if (window->SkipItems)
8598 return false;
8599
8600 ImGuiTabBar* tab_bar = g.CurrentTabBar;
8601 if (tab_bar == NULL)
8602 {
8603 IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
8604 return false;
8605 }
8606 IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
8607
8608 bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
8609 if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
8610 {
8611 ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
8612 PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
8613 }
8614 return ret;
8615}
8616
8617void ImGui::EndTabItem()
8618{
8619 ImGuiContext& g = *GImGui;
8620 ImGuiWindow* window = g.CurrentWindow;
8621 if (window->SkipItems)
8622 return;
8623
8624 ImGuiTabBar* tab_bar = g.CurrentTabBar;
8625 if (tab_bar == NULL)
8626 {
8627 IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
8628 return;
8629 }
8630 IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
8631 ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
8632 if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
8633 PopID();
8634}
8635
8636bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
8637{
8638 ImGuiContext& g = *GImGui;
8639 ImGuiWindow* window = g.CurrentWindow;
8640 if (window->SkipItems)
8641 return false;
8642
8643 ImGuiTabBar* tab_bar = g.CurrentTabBar;
8644 if (tab_bar == NULL)
8645 {
8646 IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
8647 return false;
8648 }
8649 return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
8650}
8651
8652bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
8653{
8654 // Layout whole tab bar if not already done
8655 ImGuiContext& g = *GImGui;
8656 if (tab_bar->WantLayout)
8657 {
8658 ImGuiNextItemData backup_next_item_data = g.NextItemData;
8659 TabBarLayout(tab_bar);
8660 g.NextItemData = backup_next_item_data;
8661 }
8662 ImGuiWindow* window = g.CurrentWindow;
8663 if (window->SkipItems)
8664 return false;
8665
8666 const ImGuiStyle& style = g.Style;
8667 const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
8668
8669 // If the user called us with *p_open == false, we early out and don't render.
8670 // 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.
8671 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
8672 if (p_open && !*p_open)
8673 {
8674 ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
8675 return false;
8676 }
8677
8678 IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
8679 IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
8680
8681 // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
8682 if (flags & ImGuiTabItemFlags_NoCloseButton)
8683 p_open = NULL;
8684 else if (p_open == NULL)
8685 flags |= ImGuiTabItemFlags_NoCloseButton;
8686
8687 // Acquire tab data
8688 ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
8689 bool tab_is_new = false;
8690 if (tab == NULL)
8691 {
8692 tab_bar->Tabs.push_back(ImGuiTabItem());
8693 tab = &tab_bar->Tabs.back();
8694 tab->ID = id;
8695 tab_bar->TabsAddedNew = tab_is_new = true;
8696 }
8697 tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
8698
8699 // Calculate tab contents size
8700 ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
8701 tab->RequestedWidth = -1.0f;
8702 if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
8703 size.x = tab->RequestedWidth = g.NextItemData.Width;
8704 if (tab_is_new)
8705 tab->Width = ImMax(1.0f, size.x);
8706 tab->ContentWidth = size.x;
8707 tab->BeginOrder = tab_bar->TabsActiveCount++;
8708
8709 const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
8710 const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
8711 const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
8712 const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
8713 const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
8714 tab->LastFrameVisible = g.FrameCount;
8715 tab->Flags = flags;
8716 tab->Window = docked_window;
8717
8718 // Append name _WITH_ the zero-terminator
8719 // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar)
8720 if (docked_window != NULL)
8721 {
8722 IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
8723 tab->NameOffset = -1;
8724 }
8725 else
8726 {
8727 tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
8728 tab_bar->TabsNames.append(label, label + strlen(label) + 1);
8729 }
8730
8731 // Update selected tab
8732 if (!is_tab_button)
8733 {
8734 if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
8735 if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
8736 TabBarQueueFocus(tab_bar, tab); // New tabs gets activated
8737 if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar
8738 TabBarQueueFocus(tab_bar, tab);
8739 }
8740
8741 // Lock visibility
8742 // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
8743 bool tab_contents_visible = (tab_bar->VisibleTabId == id);
8744 if (tab_contents_visible)
8745 tab_bar->VisibleTabWasSubmitted = true;
8746
8747 // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
8748 if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
8749 if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
8750 tab_contents_visible = true;
8751
8752 // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
8753 // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
8754 if (tab_appearing && (!tab_bar_appearing || tab_is_new))
8755 {
8756 ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
8757 if (is_tab_button)
8758 return false;
8759 return tab_contents_visible;
8760 }
8761
8762 if (tab_bar->SelectedTabId == id)
8763 tab->LastFrameSelected = g.FrameCount;
8764
8765 // Backup current layout position
8766 const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
8767
8768 // Layout
8769 const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
8770 size.x = tab->Width;
8771 if (is_central_section)
8772 window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
8773 else
8774 window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
8775 ImVec2 pos = window->DC.CursorPos;
8776 ImRect bb(pos, pos + size);
8777
8778 // 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)
8779 const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
8780 if (want_clip_rect)
8781 PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
8782
8783 ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
8784 ItemSize(bb.GetSize(), style.FramePadding.y);
8785 window->DC.CursorMaxPos = backup_cursor_max_pos;
8786
8787 if (!ItemAdd(bb, id))
8788 {
8789 if (want_clip_rect)
8790 PopClipRect();
8791 window->DC.CursorPos = backup_main_cursor_pos;
8792 return tab_contents_visible;
8793 }
8794
8795 // Click to Select a tab
8796 ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
8797 if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
8798 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
8799 bool hovered, held;
8800 bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
8801 if (pressed && !is_tab_button)
8802 TabBarQueueFocus(tab_bar, tab);
8803
8804 // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow()
8805 // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id.
8806 if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
8807 g.ActiveIdWindow = docked_window;
8808
8809 // Drag and drop a single floating window node moves it
8810 ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
8811 const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
8812 if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
8813 {
8814 // Move
8815 StartMouseMovingWindow(docked_window);
8816 }
8817 else if (held && !tab_appearing && IsMouseDragging(0))
8818 {
8819 // Drag and drop: re-order tabs
8820 int drag_dir = 0;
8821 float drag_distance_from_edge_x = 0.0f;
8822 if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
8823 {
8824 // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
8825 if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
8826 {
8827 drag_dir = -1;
8828 drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
8829 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
8830 }
8831 else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
8832 {
8833 drag_dir = +1;
8834 drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
8835 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
8836 }
8837 }
8838
8839 // Extract a Dockable window out of it's tab bar
8840 const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking);
8841 if (can_undock)
8842 {
8843 // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar
8844 bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
8845 if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
8846 {
8847 float threshold_base = g.FontSize;
8848 float threshold_x = (threshold_base * 2.2f);
8849 float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f);
8850 //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG]
8851
8852 float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y);
8853 if (distance_from_edge_y >= threshold_y)
8854 undocking_tab = true;
8855 if (drag_distance_from_edge_x > threshold_x)
8856 if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1))
8857 undocking_tab = true;
8858 }
8859
8860 if (undocking_tab)
8861 {
8862 // Undock
8863 // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
8864 DockContextQueueUndockWindow(&g, docked_window);
8865 g.MovingWindow = docked_window;
8866 SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
8867 g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
8868 g.ActiveIdNoClearOnFocusLoss = true;
8869 SetActiveIdUsingAllKeyboardKeys();
8870 }
8871 }
8872 }
8873
8874#if 0
8875 if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
8876 {
8877 // Enlarge tab display when hovering
8878 bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
8879 display_draw_list = GetForegroundDrawList(window);
8880 TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
8881 }
8882#endif
8883
8884 // Render tab shape
8885 ImDrawList* display_draw_list = window->DrawList;
8886 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));
8887 TabItemBackground(display_draw_list, bb, flags, tab_col);
8888 RenderNavHighlight(bb, id);
8889
8890 // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
8891 const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
8892 if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)
8893 TabBarQueueFocus(tab_bar, tab);
8894
8895 if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
8896 flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
8897
8898 // Render tab label, process close button
8899 const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
8900 bool just_closed;
8901 bool text_clipped;
8902 TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
8903 if (just_closed && p_open != NULL)
8904 {
8905 *p_open = false;
8906 TabBarCloseTab(tab_bar, tab);
8907 }
8908
8909 // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent)
8910 // That state is copied to window->DockTabItemStatusFlags by our caller.
8911 if (docked_window && (hovered || g.HoveredId == close_button_id))
8912 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
8913
8914 // Restore main window position so user can draw there
8915 if (want_clip_rect)
8916 PopClipRect();
8917 window->DC.CursorPos = backup_main_cursor_pos;
8918
8919 // Tooltip
8920 // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
8921 // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
8922 // FIXME: This is a mess.
8923 // FIXME: We may want disabled tab to still display the tooltip?
8924 if (text_clipped && g.HoveredId == id && !held)
8925 if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
8926 SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
8927
8928 IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
8929 if (is_tab_button)
8930 return pressed;
8931 return tab_contents_visible;
8932}
8933
8934// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
8935// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
8936// Tabs closed by the close button will automatically be flagged to avoid this issue.
8937void ImGui::SetTabItemClosed(const char* label)
8938{
8939 ImGuiContext& g = *GImGui;
8940 bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
8941 if (is_within_manual_tab_bar)
8942 {
8943 ImGuiTabBar* tab_bar = g.CurrentTabBar;
8944 ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
8945 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
8946 tab->WantClose = true; // Will be processed by next call to TabBarLayout()
8947 }
8948 else if (ImGuiWindow* window = FindWindowByName(label))
8949 {
8950 if (window->DockIsActive)
8951 if (ImGuiDockNode* node = window->DockNode)
8952 {
8953 ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
8954 TabBarRemoveTab(node->TabBar, tab_id);
8955 window->DockTabWantClose = true;
8956 }
8957 }
8958}
8959
8960ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
8961{
8962 ImGuiContext& g = *GImGui;
8963 ImVec2 label_size = CalcTextSize(label, NULL, true);
8964 ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
8965 if (has_close_button_or_unsaved_marker)
8966 size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
8967 else
8968 size.x += g.Style.FramePadding.x + 1.0f;
8969 return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
8970}
8971
8972ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window)
8973{
8974 return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument));
8975}
8976
8977void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
8978{
8979 // 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.
8980 ImGuiContext& g = *GImGui;
8981 const float width = bb.GetWidth();
8982 IM_UNUSED(flags);
8983 IM_ASSERT(width > 0.0f);
8984 const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
8985 const float y1 = bb.Min.y + 1.0f;
8986 const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
8987 draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
8988 draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
8989 draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
8990 draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
8991 draw_list->PathFillConvex(col);
8992 if (g.Style.TabBorderSize > 0.0f)
8993 {
8994 draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
8995 draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
8996 draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
8997 draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
8998 draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
8999 }
9000}
9001
9002// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
9003// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
9004void 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)
9005{
9006 ImGuiContext& g = *GImGui;
9007 ImVec2 label_size = CalcTextSize(label, NULL, true);
9008
9009 if (out_just_closed)
9010 *out_just_closed = false;
9011 if (out_text_clipped)
9012 *out_text_clipped = false;
9013
9014 if (bb.GetWidth() <= 1.0f)
9015 return;
9016
9017 // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
9018 // But right now if you want to alter text color of tabs this is what you need to do.
9019#if 0
9020 const float backup_alpha = g.Style.Alpha;
9021 if (!is_contents_visible)
9022 g.Style.Alpha *= 0.7f;
9023#endif
9024
9025 // Render text label (with clipping + alpha gradient) + unsaved marker
9026 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);
9027 ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
9028
9029 // Return clipped state ignoring the close button
9030 if (out_text_clipped)
9031 {
9032 *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
9033 //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));
9034 }
9035
9036 const float button_sz = g.FontSize;
9037 const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);
9038
9039 // Close Button & Unsaved Marker
9040 // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
9041 // 'hovered' will be true when hovering the Tab but NOT when hovering the close button
9042 // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
9043 // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
9044 bool close_button_pressed = false;
9045 bool close_button_visible = false;
9046 if (close_button_id != 0)
9047 if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
9048 if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
9049 close_button_visible = true;
9050 bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
9051
9052 if (close_button_visible)
9053 {
9054 ImGuiLastItemData last_item_backup = g.LastItemData;
9055 if (CloseButton(close_button_id, button_pos))
9056 close_button_pressed = true;
9057 g.LastItemData = last_item_backup;
9058
9059 // Close with middle mouse button
9060 if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
9061 close_button_pressed = true;
9062 }
9063 else if (unsaved_marker_visible)
9064 {
9065 const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
9066 RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
9067 }
9068
9069 // This is all rather complicated
9070 // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
9071 // 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..
9072 float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
9073 if (close_button_visible || unsaved_marker_visible)
9074 {
9075 text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
9076 text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
9077 ellipsis_max_x = text_pixel_clip_bb.Max.x;
9078 }
9079 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);
9080
9081#if 0
9082 if (!is_contents_visible)
9083 g.Style.Alpha = backup_alpha;
9084#endif
9085
9086 if (out_just_closed)
9087 *out_just_closed = close_button_pressed;
9088}
9089
9090
9091#endif // #ifndef IMGUI_DISABLE
uintptr_t id
int g
TclObject t
ImGuiContext * GImGui
Definition imgui.cc:1185
const ImWchar * ImStrbolW(const ImWchar *buf_mid_line, const ImWchar *buf_begin)
Definition imgui.cc:1895
void ImTriangleBarycentricCoords(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p, float &out_u, float &out_v, float &out_w)
Definition imgui.cc:1798
int ImTextStrToUtf8(char *out_buf, int out_buf_size, const ImWchar *in_text, const ImWchar *in_text_end)
Definition imgui.cc:2342
int ImFormatString(char *buf, size_t buf_size, const char *fmt,...)
Definition imgui.cc:1971
void ImStrTrimBlanks(char *buf)
Definition imgui.cc:1925
void ImFormatStringToTempBufferV(const char **out_buf, const char **out_buf_end, const char *fmt, va_list args)
Definition imgui.cc:2013
const char * ImTextCharToUtf8(char out_buf[5], unsigned int c)
Definition imgui.cc:2319
bool ImTriangleContainsPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p)
Definition imgui.cc:1790
int ImTextCountUtf8BytesFromStr(const ImWchar *in_text, const ImWchar *in_text_end)
Definition imgui.cc:2358
ImGuiID ImHashStr(const char *data_p, size_t data_size, ImGuiID seed)
Definition imgui.cc:2086
const char * ImTextFindPreviousUtf8Codepoint(const char *in_text_start, const char *in_text_curr)
Definition imgui.cc:2372
IM_MSVC_RUNTIME_CHECKS_OFF int ImTextCharFromUtf8(unsigned int *out_char, const char *in_text, const char *in_text_end)
Definition imgui.cc:2203
int ImTextCountCharsFromUtf8(const char *in_text, const char *in_text_end)
Definition imgui.cc:2271
int ImTextCountUtf8BytesFromChar(const char *in_text, const char *in_text_end)
Definition imgui.cc:2327
int ImTextStrFromUtf8(ImWchar *buf, int buf_size, const char *in_text, const char *in_text_end, const char **in_text_remaining)
Definition imgui.cc:2255
void ImStrncpy(char *dst, const char *src, size_t count)
Definition imgui.cc:1844
ImVec2 ImTriangleClosestPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p)
Definition imgui.cc:1809
#define IMGUI_CDECL
#define STB_TEXTEDIT_K_DOWN
#define STB_TEXTEDIT_K_DELETE
#define STB_TEXTEDIT_K_RIGHT
#define STB_TEXTEDIT_K_SHIFT
const char * ImParseFormatSanitizeForScanning(const char *fmt_in, char *fmt_out, size_t fmt_out_size)
#define STB_TEXTEDIT_K_REDO
#define STB_TEXTEDIT_K_BACKSPACE
#define STB_TEXTEDIT_K_WORDRIGHT
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo)==ImGuiDataType_COUNT)
#define STB_TEXTEDIT_K_UP
const char * ImParseFormatFindStart(const char *fmt)
#define STB_TEXTEDIT_K_TEXTEND
int ImParseFormatPrecision(const char *fmt, int default_precision)
#define STB_TEXTEDIT_K_LINEEND
#define STB_TEXTEDIT_K_UNDO
#define STB_TEXTEDIT_K_LEFT
#define STB_TEXTEDIT_K_PGUP
#define STB_TEXTEDIT_K_WORDLEFT
#define STB_TEXTEDIT_K_TEXTSTART
const char * ImParseFormatFindEnd(const char *fmt)
const char * ImParseFormatTrimDecorations(const char *fmt, char *buf, size_t buf_size)
void ImParseFormatSanitizeForPrinting(const char *fmt_in, char *fmt_out, size_t fmt_out_size)
#define STB_TEXTEDIT_K_LINESTART
#define STB_TEXTEDIT_K_PGDOWN
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition lz4.cc:146
constexpr mat4 scale(const vec3 &xyz)
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:293
void ListBox(const char *label, const ImVec2 &size, std::invocable<> auto next)
Definition ImGuiCpp.hh:332
void Indent(float indent_w, std::invocable<> auto next)
Definition ImGuiCpp.hh:224
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
Definition ImGuiUtils.cc:81
bool SliderFloat(FloatSetting &setting, const char *format, ImGuiSliderFlags flags)
bool InputText(Setting &setting)
RegFunction R
size_t size(std::string_view utf8)
ImGuiPlotArrayGetterData(const float *values, int stride)
Definition stl_test.cc:7