1// dear imgui, v1.90.5
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, bool input_source_is_clipboard = false);
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(s: 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(pos: window->DC.CursorPos, wrap_pos_x) : 0.0f;
171 const ImVec2 text_size = CalcTextSize(text: text_begin, text_end, hide_text_after_double_hash: false, wrap_width);
172
173 ImRect bb(text_pos, text_pos + text_size);
174 ItemSize(size: text_size, text_baseline_y: 0.0f);
175 if (!ItemAdd(bb, id: 0))
176 return;
177
178 // Render (we don't hide text after ## in this end-user function)
179 RenderTextWrapped(pos: bb.Min, text: 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(s: line, c: '\n', n: text_end - line);
203 if (!line_end)
204 line_end = text_end;
205 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
206 text_size.x = ImMax(lhs: text_size.x, rhs: CalcTextSize(text: line, text_end: 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(bb: line_rect, id: 0))
221 break;
222
223 const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line);
224 if (!line_end)
225 line_end = text_end;
226 text_size.x = ImMax(lhs: text_size.x, rhs: CalcTextSize(text: line, text_end: line_end).x);
227 RenderText(pos, text: line, text_end: line_end, hide_text_after_hash: 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(s: line, c: '\n', n: text_end - line);
239 if (!line_end)
240 line_end = text_end;
241 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
242 text_size.x = ImMax(lhs: text_size.x, rhs: CalcTextSize(text: line, text_end: 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(size: text_size, text_baseline_y: 0.0f);
252 ItemAdd(bb, id: 0);
253 }
254}
255
256void ImGui::TextUnformatted(const char* text, const char* text_end)
257{
258 TextEx(text, text_end, flags: 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(out_buf: &text, out_buf_end: &text_end, fmt, args);
277 TextEx(text, text_end, flags: 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(idx: 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(idx: ImGuiCol_Text, col: 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(wrap_local_pos_x: 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(out_buf: &value_text_begin, out_buf_end: &value_text_end, fmt, args);
351 const ImVec2 value_size = CalcTextSize(text: value_text_begin, text_end: value_text_end, hide_text_after_double_hash: false);
352 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: 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(lhs: value_size.y, rhs: label_size.y) + style.FramePadding.y * 2));
357 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
358 if (!ItemAdd(bb: total_bb, id: 0))
359 return;
360
361 // Render
362 RenderTextClipped(pos_min: value_bb.Min + style.FramePadding, pos_max: value_bb.Max, text: value_text_begin, text_end: value_text_end, text_size_if_known: &value_size, align: ImVec2(0.0f, 0.0f));
363 if (label_size.x > 0.0f)
364 RenderText(pos: ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), text: 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(out_buf: &text_begin, out_buf_end: &text_end, fmt, args);
387 const ImVec2 label_size = CalcTextSize(text: text_begin, text_end, hide_text_after_double_hash: 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(size: total_size, text_baseline_y: 0.0f);
392 const ImRect bb(pos, pos + total_size);
393 if (!ItemAdd(bb, id: 0))
394 return;
395
396 // Render
397 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
398 RenderBullet(draw_list: window->DrawList, pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), col: text_col);
399 RenderText(pos: bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text: text_begin, text_end, hide_text_after_hash: 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(flags: 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, owner_id: test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
548 if (IsMouseReleased(button, owner_id: 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(key: MouseButtonToKey(button: mouse_button_clicked), owner_id: 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(button: g.ActiveIdMouseButton, owner_id: test_owner_id, flags: 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(key: ImGuiKey_Space);
616 const ImGuiKeyData* key2 = GetKeyData(key: ImGuiKey_Enter);
617 const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
618 const float t1 = ImMax(lhs: ImMax(lhs: key1->DownDuration, rhs: key2->DownDuration), rhs: key3->DownDuration);
619 nav_activated_by_inputs = CalcTypematicRepeatAmount(t0: t1 - g.IO.DeltaTime, t1, repeat_delay: g.IO.KeyRepeatDelay, repeat_rate: 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(button: mouse_button, owner_id: 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(key: MouseButtonToKey(button: mouse_button), owner_id: 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(str: label);
702 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: 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: size_arg, default_w: label_size.x + style.FramePadding.x * 2.0f, default_h: label_size.y + style.FramePadding.y * 2.0f);
708
709 const ImRect bb(pos, pos + size);
710 ItemSize(size, text_baseline_y: style.FramePadding.y);
711 if (!ItemAdd(bb, id))
712 return false;
713
714 bool hovered, held;
715 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
716
717 // Render
718 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
719 RenderNavHighlight(bb, id);
720 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: style.FrameRounding);
721
722 if (g.LogEnabled)
723 LogSetNextTextDecoration(prefix: "[", suffix: "]");
724 RenderTextClipped(pos_min: bb.Min + style.FramePadding, pos_max: bb.Max - style.FramePadding, text: label, NULL, text_size_if_known: &label_size, align: style.ButtonTextAlign, clip_rect: &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, flags: 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, size_arg: ImVec2(0, 0), flags: 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: str_id);
763 ImVec2 size = CalcItemSize(size: size_arg, default_w: 0.0f, default_h: 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, out_hovered: &hovered, out_held: &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: str_id);
784 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
785 const float default_size = GetFrameHeight();
786 ItemSize(size, text_baseline_y: (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, out_hovered: &hovered, out_held: &held, flags);
792
793 // Render
794 const ImU32 bg_col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
795 const ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
796 RenderNavHighlight(bb, id);
797 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: bg_col, border: true, rounding: g.Style.FrameRounding);
798 RenderArrow(draw_list: window->DrawList, pos: bb.Min + ImVec2(ImMax(lhs: 0.0f, rhs: (size.x - g.FontSize) * 0.5f), ImMax(lhs: 0.0f, rhs: (size.y - g.FontSize) * 0.5f)), col: 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, size: ImVec2(sz, sz), flags: 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(amount: ImTrunc(v: 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: bb_interact, id);
827
828 bool hovered, held;
829 bool pressed = ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held);
830 if (is_clipped)
831 return pressed;
832
833 // Render
834 // FIXME: Clarify this mess
835 ImU32 col = GetColorU32(idx: held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
836 ImVec2 center = bb.GetCenter();
837 if (hovered)
838 window->DrawList->AddCircleFilled(center, radius: ImMax(lhs: 2.0f, rhs: 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(idx: ImGuiCol_Text);
842 center -= ImVec2(0.5f, 0.5f);
843 window->DrawList->AddLine(p1: center + ImVec2(+cross_extent, +cross_extent), p2: center + ImVec2(-cross_extent, -cross_extent), col: cross_col, thickness: 1.0f);
844 window->DrawList->AddLine(p1: center + ImVec2(+cross_extent, -cross_extent), p2: center + ImVec2(-cross_extent, +cross_extent), col: cross_col, thickness: 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, out_hovered: &hovered, out_held: &held, flags: 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(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
865 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
866 if (hovered || held)
867 window->DrawList->AddCircleFilled(center: bb.GetCenter() + ImVec2(0.0f, -0.5f), radius: g.FontSize * 0.5f + 1.0f, col: bg_col);
868
869 if (dock_node)
870 RenderArrowDockMenu(draw_list: window->DrawList, p_min: bb.Min, sz: g.FontSize, col: text_col);
871 else
872 RenderArrow(draw_list: window->DrawList, pos: bb.Min, col: text_col, dir: window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, scale: 1.0f);
873
874 // Switch to moving the window after mouse is moved beyond the initial drag threshold
875 if (IsItemActive() && IsMouseDragging(button: 0))
876 StartMouseMovingWindowOrNode(window, node: dock_node, undock: 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(str: 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(lhs: outer_rect.Min.y, rhs: 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(lhs: outer_rect.Min.x, rhs: 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, p_scroll_v: &scroll, avail_v: (ImS64)size_avail, contents_v: (ImS64)size_contents, flags: 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(f: (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(amount: ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), mn: 0.0f, mx: 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), mn: 0.0f, mx: 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(lhs: ImMax(lhs: size_contents_v, rhs: size_avail_v), rhs: (ImS64)1);
967 const float grab_h_pixels = ImClamp(v: scrollbar_size_v * ((float)size_avail_v / (float)win_size_v), mn: style.GrabMinSize, mx: 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: bb_frame, id, NULL, extra_flags: ImGuiItemFlags_NoNav);
974 ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_NoNavFocus);
975
976 const ImS64 scroll_max = ImMax(lhs: (ImS64)1, rhs: size_contents_v - size_avail_v);
977 float scroll_ratio = ImSaturate(f: (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(f: (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(f: (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(f: (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(idx: ImGuiCol_ScrollbarBg);
1014 const ImU32 grab_col = GetColorU32(idx: held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha_mul: alpha);
1015 window->DrawList->AddRectFilled(p_min: bb_frame.Min, p_max: bb_frame.Max, col: bg_col, rounding: window->WindowRounding, flags);
1016 ImRect grab_rect;
1017 if (axis == ImGuiAxis_X)
1018 grab_rect = ImRect(ImLerp(a: bb.Min.x, b: bb.Max.x, t: grab_v_norm), bb.Min.y, ImLerp(a: bb.Min.x, b: bb.Max.x, t: grab_v_norm) + grab_h_pixels, bb.Max.y);
1019 else
1020 grab_rect = ImRect(bb.Min.x, ImLerp(a: bb.Min.y, b: bb.Max.y, t: grab_v_norm), bb.Max.x, ImLerp(a: bb.Min.y, b: bb.Max.y, t: grab_v_norm) + grab_h_pixels);
1021 window->DrawList->AddRectFilled(p_min: grab_rect.Min, p_max: grab_rect.Max, col: grab_col, rounding: 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, id: 0))
1039 return;
1040
1041 // Render
1042 if (border_size > 0.0f)
1043 window->DrawList->AddRect(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(col: border_col), rounding: 0.0f, flags: ImDrawFlags_None, thickness: border_size);
1044 window->DrawList->AddImage(user_texture_id, p_min: bb.Min + padding, p_max: bb.Max - padding, uv_min: uv0, uv_max: uv1, col: GetColorU32(col: 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, out_hovered: &hovered, out_held: &held, flags);
1064
1065 // Render
1066 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1067 RenderNavHighlight(bb, id);
1068 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: ImClamp(v: (float)ImMin(lhs: padding.x, rhs: padding.y), mn: 0.0f, mx: g.Style.FrameRounding));
1069 if (bg_col.w > 0.0f)
1070 window->DrawList->AddRectFilled(p_min: bb.Min + padding, p_max: bb.Max - padding, col: GetColorU32(col: bg_col));
1071 window->DrawList->AddImage(user_texture_id: texture_id, p_min: bb.Min + padding, p_max: bb.Max - padding, uv_min: uv0, uv_max: uv1, col: GetColorU32(col: 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(id: window->GetID(str: str_id), texture_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(ptr_id: (void*)(intptr_t)user_texture_id);
1101 const ImGuiID id = window->GetID(str: "#image");
1102 PopID();
1103
1104 if (frame_padding >= 0)
1105 PushStyleVar(idx: ImGuiStyleVar_FramePadding, val: ImVec2((float)frame_padding, (float)frame_padding));
1106 bool ret = ImageButtonEx(id, texture_id: user_texture_id, image_size: 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(str: label);
1122 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: 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(bb: total_bb, text_baseline_y: style.FramePadding.y);
1128 if (!ItemAdd(bb: 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(bb: total_bb, id, out_hovered: &hovered, out_held: &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(bb: total_bb, id);
1144 RenderFrame(p_min: check_bb.Min, p_max: check_bb.Max, fill_col: GetColorU32(idx: (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding);
1145 ImU32 check_col = GetColorU32(idx: 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(lhs: 1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 3.6f)));
1152 window->DrawList->AddRectFilled(p_min: check_bb.Min + pad, p_max: check_bb.Max - pad, col: check_col, rounding: style.FrameRounding);
1153 }
1154 else if (*v)
1155 {
1156 const float pad = ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 6.0f));
1157 RenderCheckMark(draw_list: window->DrawList, pos: check_bb.Min + ImVec2(pad, pad), col: check_col, sz: 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(ref_pos: &label_pos, text: mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1163 if (label_size.x > 0.0f)
1164 RenderText(pos: label_pos, text: 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, v: &all_on);
1181 }
1182 else
1183 {
1184 pressed = Checkbox(label, v: &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(str: label);
1226 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: 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(bb: total_bb, text_baseline_y: style.FramePadding.y);
1233 if (!ItemAdd(bb: 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(bb: total_bb, id, out_hovered: &hovered, out_held: &held);
1243 if (pressed)
1244 MarkItemEdited(id);
1245
1246 RenderNavHighlight(bb: total_bb, id);
1247 const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1248 window->DrawList->AddCircleFilled(center, radius, col: GetColorU32(idx: (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segments: num_segment);
1249 if (active)
1250 {
1251 const float pad = ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 6.0f));
1252 window->DrawList->AddCircleFilled(center, radius: radius - pad, col: GetColorU32(idx: ImGuiCol_CheckMark));
1253 }
1254
1255 if (style.FrameBorderSize > 0.0f)
1256 {
1257 window->DrawList->AddCircle(center: center + ImVec2(1, 1), radius, col: GetColorU32(idx: ImGuiCol_BorderShadow), num_segments: num_segment, thickness: style.FrameBorderSize);
1258 window->DrawList->AddCircle(center, radius, col: GetColorU32(idx: ImGuiCol_Border), num_segments: num_segment, thickness: 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(ref_pos: &label_pos, text: active ? "(x)" : "( )");
1264 if (label_size.x > 0.0f)
1265 RenderText(pos: label_pos, text: 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, active: *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: size_arg, default_w: CalcItemWidth(), default_h: g.FontSize + style.FramePadding.y * 2.0f);
1292 ImRect bb(pos, pos + size);
1293 ItemSize(size, text_baseline_y: style.FramePadding.y);
1294 if (!ItemAdd(bb, id: 0))
1295 return;
1296
1297 // Out of courtesy we accept a NaN fraction without crashing
1298 fraction = ImSaturate(f: fraction);
1299 const float fraction_not_nan = (fraction == fraction) ? fraction : 0.0f;
1300
1301 // Render
1302 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: GetColorU32(idx: ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding);
1303 bb.Expand(amount: ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1304 const ImVec2 fill_br = ImVec2(ImLerp(a: bb.Min.x, b: bb.Max.x, t: fraction_not_nan), bb.Max.y);
1305 RenderRectFilledRangeH(draw_list: window->DrawList, rect: bb, col: GetColorU32(idx: ImGuiCol_PlotHistogram), x_start_norm: 0.0f, x_end_norm: fraction_not_nan, rounding: style.FrameRounding);
1306
1307 // Default displaying the fraction as percentage string, but user can override it
1308 char overlay_buf[32];
1309 if (!overlay)
1310 {
1311 ImFormatString(buf: overlay_buf, IM_ARRAYSIZE(overlay_buf), fmt: "%.0f%%", fraction * 100 + 0.01f);
1312 overlay = overlay_buf;
1313 }
1314
1315 ImVec2 overlay_size = CalcTextSize(text: overlay, NULL);
1316 if (overlay_size.x > 0.0f)
1317 RenderTextClipped(pos_min: ImVec2(ImClamp(v: fill_br.x + style.ItemSpacing.x, mn: bb.Min.x, mx: bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), pos_max: bb.Max, text: overlay, NULL, text_size_if_known: &overlay_size, align: ImVec2(0.0f, 0.5f), clip_rect: &bb);
1318}
1319
1320void ImGui::Bullet()
1321{
1322 ImGuiWindow* window = GetCurrentWindow();
1323 if (window->SkipItems)
1324 return;
1325
1326 ImGuiContext& g = *GImGui;
1327 const ImGuiStyle& style = g.Style;
1328 const float line_height = ImMax(lhs: ImMin(lhs: window->DC.CurrLineSize.y, rhs: g.FontSize + style.FramePadding.y * 2), rhs: g.FontSize);
1329 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1330 ItemSize(bb);
1331 if (!ItemAdd(bb, id: 0))
1332 {
1333 SameLine(offset_from_start_x: 0, spacing: style.FramePadding.x * 2);
1334 return;
1335 }
1336
1337 // Render and stay on same line
1338 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
1339 RenderBullet(draw_list: window->DrawList, pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), col: text_col);
1340 SameLine(offset_from_start_x: 0, spacing: style.FramePadding.x * 2.0f);
1341}
1342
1343//-------------------------------------------------------------------------
1344// [SECTION] Widgets: Low-level Layout helpers
1345//-------------------------------------------------------------------------
1346// - Spacing()
1347// - Dummy()
1348// - NewLine()
1349// - AlignTextToFramePadding()
1350// - SeparatorEx() [Internal]
1351// - Separator()
1352// - SplitterBehavior() [Internal]
1353// - ShrinkWidths() [Internal]
1354//-------------------------------------------------------------------------
1355
1356void ImGui::Spacing()
1357{
1358 ImGuiWindow* window = GetCurrentWindow();
1359 if (window->SkipItems)
1360 return;
1361 ItemSize(size: ImVec2(0, 0));
1362}
1363
1364void ImGui::Dummy(const ImVec2& size)
1365{
1366 ImGuiWindow* window = GetCurrentWindow();
1367 if (window->SkipItems)
1368 return;
1369
1370 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1371 ItemSize(size);
1372 ItemAdd(bb, id: 0);
1373}
1374
1375void ImGui::NewLine()
1376{
1377 ImGuiWindow* window = GetCurrentWindow();
1378 if (window->SkipItems)
1379 return;
1380
1381 ImGuiContext& g = *GImGui;
1382 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1383 window->DC.LayoutType = ImGuiLayoutType_Vertical;
1384 window->DC.IsSameLine = false;
1385 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.
1386 ItemSize(size: ImVec2(0, 0));
1387 else
1388 ItemSize(size: ImVec2(0.0f, g.FontSize));
1389 window->DC.LayoutType = backup_layout_type;
1390}
1391
1392void ImGui::AlignTextToFramePadding()
1393{
1394 ImGuiWindow* window = GetCurrentWindow();
1395 if (window->SkipItems)
1396 return;
1397
1398 ImGuiContext& g = *GImGui;
1399 window->DC.CurrLineSize.y = ImMax(lhs: window->DC.CurrLineSize.y, rhs: g.FontSize + g.Style.FramePadding.y * 2);
1400 window->DC.CurrLineTextBaseOffset = ImMax(lhs: window->DC.CurrLineTextBaseOffset, rhs: g.Style.FramePadding.y);
1401}
1402
1403// Horizontal/vertical separating line
1404// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1405// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
1406void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1407{
1408 ImGuiWindow* window = GetCurrentWindow();
1409 if (window->SkipItems)
1410 return;
1411
1412 ImGuiContext& g = *GImGui;
1413 IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected
1414 IM_ASSERT(thickness > 0.0f);
1415
1416 if (flags & ImGuiSeparatorFlags_Vertical)
1417 {
1418 // Vertical separator, for menu bars (use current line height).
1419 float y1 = window->DC.CursorPos.y;
1420 float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1421 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1422 ItemSize(size: ImVec2(thickness, 0.0f));
1423 if (!ItemAdd(bb, id: 0))
1424 return;
1425
1426 // Draw
1427 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator));
1428 if (g.LogEnabled)
1429 LogText(fmt: " |");
1430 }
1431 else if (flags & ImGuiSeparatorFlags_Horizontal)
1432 {
1433 // Horizontal Separator
1434 float x1 = window->DC.CursorPos.x;
1435 float x2 = window->WorkRect.Max.x;
1436
1437 // Preserve legacy behavior inside Columns()
1438 // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1439 // We currently don't need to provide the same feature for tables because tables naturally have border features.
1440 ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1441 if (columns)
1442 {
1443 x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
1444 x2 = window->Pos.x + window->Size.x;
1445 PushColumnsBackground();
1446 }
1447
1448 // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1449 // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1450 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.
1451 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1452 ItemSize(size: ImVec2(0.0f, thickness_for_layout));
1453
1454 if (ItemAdd(bb, id: 0))
1455 {
1456 // Draw
1457 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator));
1458 if (g.LogEnabled)
1459 LogRenderedText(ref_pos: &bb.Min, text: "--------------------------------\n");
1460
1461 }
1462 if (columns)
1463 {
1464 PopColumnsBackground();
1465 columns->LineMinY = window->DC.CursorPos.y;
1466 }
1467 }
1468}
1469
1470void ImGui::Separator()
1471{
1472 ImGuiContext& g = *GImGui;
1473 ImGuiWindow* window = g.CurrentWindow;
1474 if (window->SkipItems)
1475 return;
1476
1477 // Those flags should eventually be configurable by the user
1478 // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
1479 ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1480
1481 // Only applies to legacy Columns() api as they relied on Separator() a lot.
1482 if (window->DC.CurrentColumns)
1483 flags |= ImGuiSeparatorFlags_SpanAllColumns;
1484
1485 SeparatorEx(flags, thickness: 1.0f);
1486}
1487
1488void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1489{
1490 ImGuiContext& g = *GImGui;
1491 ImGuiWindow* window = g.CurrentWindow;
1492 ImGuiStyle& style = g.Style;
1493
1494 const ImVec2 label_size = CalcTextSize(text: label, text_end: label_end, hide_text_after_double_hash: false);
1495 const ImVec2 pos = window->DC.CursorPos;
1496 const ImVec2 padding = style.SeparatorTextPadding;
1497
1498 const float separator_thickness = style.SeparatorTextBorderSize;
1499 const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(lhs: label_size.y + padding.y * 2.0f, rhs: separator_thickness));
1500 const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1501 const float text_baseline_y = ImTrunc(f: (bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
1502 ItemSize(size: min_size, text_baseline_y);
1503 if (!ItemAdd(bb, id))
1504 return;
1505
1506 const float sep1_x1 = pos.x;
1507 const float sep2_x2 = bb.Max.x;
1508 const float seps_y = ImTrunc(f: (bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
1509
1510 const float label_avail_w = ImMax(lhs: 0.0f, rhs: sep2_x2 - sep1_x1 - padding.x * 2.0f);
1511 const ImVec2 label_pos(pos.x + padding.x + ImMax(lhs: 0.0f, rhs: (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN
1512
1513 // This allows using SameLine() to position something in the 'extra_w'
1514 window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1515
1516 const ImU32 separator_col = GetColorU32(idx: ImGuiCol_Separator);
1517 if (label_size.x > 0.0f)
1518 {
1519 const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1520 const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1521 if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1522 window->DrawList->AddLine(p1: ImVec2(sep1_x1, seps_y), p2: ImVec2(sep1_x2, seps_y), col: separator_col, thickness: separator_thickness);
1523 if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1524 window->DrawList->AddLine(p1: ImVec2(sep2_x1, seps_y), p2: ImVec2(sep2_x2, seps_y), col: separator_col, thickness: separator_thickness);
1525 if (g.LogEnabled)
1526 LogSetNextTextDecoration(prefix: "---", NULL);
1527 RenderTextEllipsis(draw_list: window->DrawList, pos_min: label_pos, pos_max: ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), clip_max_x: bb.Max.x, ellipsis_max_x: bb.Max.x, text: label, text_end: label_end, text_size_if_known: &label_size);
1528 }
1529 else
1530 {
1531 if (g.LogEnabled)
1532 LogText(fmt: "---");
1533 if (separator_thickness > 0.0f)
1534 window->DrawList->AddLine(p1: ImVec2(sep1_x1, seps_y), p2: ImVec2(sep2_x2, seps_y), col: separator_col, thickness: separator_thickness);
1535 }
1536}
1537
1538void ImGui::SeparatorText(const char* label)
1539{
1540 ImGuiWindow* window = GetCurrentWindow();
1541 if (window->SkipItems)
1542 return;
1543
1544 // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1545 // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1546 // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
1547 // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1548 // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1549 // and then we can turn this into a format function.
1550 SeparatorTextEx(id: 0, label, label_end: FindRenderedTextEnd(text: label), extra_w: 0.0f);
1551}
1552
1553// 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.
1554bool 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)
1555{
1556 ImGuiContext& g = *GImGui;
1557 ImGuiWindow* window = g.CurrentWindow;
1558
1559 if (!ItemAdd(bb, id, NULL, extra_flags: ImGuiItemFlags_NoNav))
1560 return false;
1561
1562 // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1563 // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1564 // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1565 ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1566#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1567 button_flags |= ImGuiButtonFlags_AllowOverlap;
1568#endif
1569
1570 bool hovered, held;
1571 ImRect bb_interact = bb;
1572 bb_interact.Expand(amount: axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1573 ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held, flags: button_flags);
1574 if (hovered)
1575 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1576
1577 if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1578 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1579
1580 ImRect bb_render = bb;
1581 if (held)
1582 {
1583 float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1584
1585 // Minimum pane size
1586 float size_1_maximum_delta = ImMax(lhs: 0.0f, rhs: *size1 - min_size1);
1587 float size_2_maximum_delta = ImMax(lhs: 0.0f, rhs: *size2 - min_size2);
1588 if (mouse_delta < -size_1_maximum_delta)
1589 mouse_delta = -size_1_maximum_delta;
1590 if (mouse_delta > size_2_maximum_delta)
1591 mouse_delta = size_2_maximum_delta;
1592
1593 // Apply resize
1594 if (mouse_delta != 0.0f)
1595 {
1596 *size1 = ImMax(lhs: *size1 + mouse_delta, rhs: min_size1);
1597 *size2 = ImMax(lhs: *size2 - mouse_delta, rhs: min_size2);
1598 bb_render.Translate(d: (axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1599 MarkItemEdited(id);
1600 }
1601 }
1602
1603 // Render at new position
1604 if (bg_col & IM_COL32_A_MASK)
1605 window->DrawList->AddRectFilled(p_min: bb_render.Min, p_max: bb_render.Max, col: bg_col, rounding: 0.0f);
1606 const ImU32 col = GetColorU32(idx: held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1607 window->DrawList->AddRectFilled(p_min: bb_render.Min, p_max: bb_render.Max, col, rounding: 0.0f);
1608
1609 return held;
1610}
1611
1612static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1613{
1614 const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1615 const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1616 if (int d = (int)(b->Width - a->Width))
1617 return d;
1618 return (b->Index - a->Index);
1619}
1620
1621// Shrink excess width from a set of item, by removing width from the larger items first.
1622// Set items Width to -1.0f to disable shrinking this item.
1623void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1624{
1625 if (count == 1)
1626 {
1627 if (items[0].Width >= 0.0f)
1628 items[0].Width = ImMax(lhs: items[0].Width - width_excess, rhs: 1.0f);
1629 return;
1630 }
1631 ImQsort(base: items, count: (size_t)count, size_of_element: sizeof(ImGuiShrinkWidthItem), compare_func: ShrinkWidthItemComparer);
1632 int count_same_width = 1;
1633 while (width_excess > 0.0f && count_same_width < count)
1634 {
1635 while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1636 count_same_width++;
1637 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);
1638 if (max_width_to_remove_per_item <= 0.0f)
1639 break;
1640 float width_to_remove_per_item = ImMin(lhs: width_excess / count_same_width, rhs: max_width_to_remove_per_item);
1641 for (int item_n = 0; item_n < count_same_width; item_n++)
1642 items[item_n].Width -= width_to_remove_per_item;
1643 width_excess -= width_to_remove_per_item * count_same_width;
1644 }
1645
1646 // Round width and redistribute remainder
1647 // 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.
1648 width_excess = 0.0f;
1649 for (int n = 0; n < count; n++)
1650 {
1651 float width_rounded = ImTrunc(f: items[n].Width);
1652 width_excess += items[n].Width - width_rounded;
1653 items[n].Width = width_rounded;
1654 }
1655 while (width_excess > 0.0f)
1656 for (int n = 0; n < count && width_excess > 0.0f; n++)
1657 {
1658 float width_to_add = ImMin(lhs: items[n].InitialWidth - items[n].Width, rhs: 1.0f);
1659 items[n].Width += width_to_add;
1660 width_excess -= width_to_add;
1661 }
1662}
1663
1664//-------------------------------------------------------------------------
1665// [SECTION] Widgets: ComboBox
1666//-------------------------------------------------------------------------
1667// - CalcMaxPopupHeightFromItemCount() [Internal]
1668// - BeginCombo()
1669// - BeginComboPopup() [Internal]
1670// - EndCombo()
1671// - BeginComboPreview() [Internal]
1672// - EndComboPreview() [Internal]
1673// - Combo()
1674//-------------------------------------------------------------------------
1675
1676static float CalcMaxPopupHeightFromItemCount(int items_count)
1677{
1678 ImGuiContext& g = *GImGui;
1679 if (items_count <= 0)
1680 return FLT_MAX;
1681 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1682}
1683
1684bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1685{
1686 ImGuiContext& g = *GImGui;
1687 ImGuiWindow* window = GetCurrentWindow();
1688
1689 ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1690 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1691 if (window->SkipItems)
1692 return false;
1693
1694 const ImGuiStyle& style = g.Style;
1695 const ImGuiID id = window->GetID(str: label);
1696 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1697 if (flags & ImGuiComboFlags_WidthFitPreview)
1698 IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
1699
1700 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1701 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
1702 const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(text: preview_value, NULL, hide_text_after_double_hash: true).x : 0.0f;
1703 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
1704 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1705 const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1706 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
1707 if (!ItemAdd(bb: total_bb, id, nav_bb: &bb))
1708 return false;
1709
1710 // Open on click
1711 bool hovered, held;
1712 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held);
1713 const ImGuiID popup_id = ImHashStr(data: "##ComboPopup", data_size: 0, seed: id);
1714 bool popup_open = IsPopupOpen(id: popup_id, popup_flags: ImGuiPopupFlags_None);
1715 if (pressed && !popup_open)
1716 {
1717 OpenPopupEx(id: popup_id, popup_flags: ImGuiPopupFlags_None);
1718 popup_open = true;
1719 }
1720
1721 // Render shape
1722 const ImU32 frame_col = GetColorU32(idx: hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1723 const float value_x2 = ImMax(lhs: bb.Min.x, rhs: bb.Max.x - arrow_size);
1724 RenderNavHighlight(bb, id);
1725 if (!(flags & ImGuiComboFlags_NoPreview))
1726 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: ImVec2(value_x2, bb.Max.y), col: frame_col, rounding: style.FrameRounding, flags: (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1727 if (!(flags & ImGuiComboFlags_NoArrowButton))
1728 {
1729 ImU32 bg_col = GetColorU32(idx: (popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1730 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
1731 window->DrawList->AddRectFilled(p_min: ImVec2(value_x2, bb.Min.y), p_max: bb.Max, col: bg_col, rounding: style.FrameRounding, flags: (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1732 if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1733 RenderArrow(draw_list: window->DrawList, pos: ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), col: text_col, dir: ImGuiDir_Down, scale: 1.0f);
1734 }
1735 RenderFrameBorder(p_min: bb.Min, p_max: bb.Max, rounding: style.FrameRounding);
1736
1737 // Custom preview
1738 if (flags & ImGuiComboFlags_CustomPreview)
1739 {
1740 g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1741 IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1742 preview_value = NULL;
1743 }
1744
1745 // Render preview and label
1746 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1747 {
1748 if (g.LogEnabled)
1749 LogSetNextTextDecoration(prefix: "{", suffix: "}");
1750 RenderTextClipped(pos_min: bb.Min + style.FramePadding, pos_max: ImVec2(value_x2, bb.Max.y), text: preview_value, NULL, NULL);
1751 }
1752 if (label_size.x > 0)
1753 RenderText(pos: ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), text: label);
1754
1755 if (!popup_open)
1756 return false;
1757
1758 g.NextWindowData.Flags = backup_next_window_data_flags;
1759 return BeginComboPopup(popup_id, bb, flags);
1760}
1761
1762bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1763{
1764 ImGuiContext& g = *GImGui;
1765 if (!IsPopupOpen(id: popup_id, popup_flags: ImGuiPopupFlags_None))
1766 {
1767 g.NextWindowData.ClearFlags();
1768 return false;
1769 }
1770
1771 // Set popup size
1772 float w = bb.GetWidth();
1773 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1774 {
1775 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(lhs: g.NextWindowData.SizeConstraintRect.Min.x, rhs: w);
1776 }
1777 else
1778 {
1779 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1780 flags |= ImGuiComboFlags_HeightRegular;
1781 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1782 int popup_max_height_in_items = -1;
1783 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
1784 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
1785 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
1786 ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
1787 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
1788 constraint_min.x = w;
1789 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
1790 constraint_max.y = CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items);
1791 SetNextWindowSizeConstraints(size_min: constraint_min, size_max: constraint_max);
1792 }
1793
1794 // This is essentially a specialized version of BeginPopupEx()
1795 char name[16];
1796 ImFormatString(buf: name, IM_ARRAYSIZE(name), fmt: "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
1797
1798 // Set position given a custom constraint (peak into expected window size so we can position it)
1799 // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1800 // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1801 if (ImGuiWindow* popup_window = FindWindowByName(name))
1802 if (popup_window->WasActive)
1803 {
1804 // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1805 ImVec2 size_expected = CalcWindowNextAutoFitSize(window: popup_window);
1806 popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1807 ImRect r_outer = GetPopupAllowedExtentRect(window: popup_window);
1808 ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos: bb.GetBL(), size: size_expected, last_dir: &popup_window->AutoPosLastDirection, r_outer, r_avoid: bb, policy: ImGuiPopupPositionPolicy_ComboBox);
1809 SetNextWindowPos(pos);
1810 }
1811
1812 // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1813 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1814 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text
1815 bool ret = Begin(name, NULL, flags: window_flags);
1816 PopStyleVar();
1817 if (!ret)
1818 {
1819 EndPopup();
1820 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1821 return false;
1822 }
1823 g.BeginComboDepth++;
1824 return true;
1825}
1826
1827void ImGui::EndCombo()
1828{
1829 ImGuiContext& g = *GImGui;
1830 EndPopup();
1831 g.BeginComboDepth--;
1832}
1833
1834// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1835// (Experimental, see GitHub issues: #1658, #4168)
1836bool ImGui::BeginComboPreview()
1837{
1838 ImGuiContext& g = *GImGui;
1839 ImGuiWindow* window = g.CurrentWindow;
1840 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1841
1842 if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
1843 return false;
1844 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?
1845 if (!window->ClipRect.Overlaps(r: preview_data->PreviewRect)) // Narrower test (optional)
1846 return false;
1847
1848 // FIXME: This could be contained in a PushWorkRect() api
1849 preview_data->BackupCursorPos = window->DC.CursorPos;
1850 preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1851 preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1852 preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1853 preview_data->BackupLayout = window->DC.LayoutType;
1854 window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1855 window->DC.CursorMaxPos = window->DC.CursorPos;
1856 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1857 window->DC.IsSameLine = false;
1858 PushClipRect(clip_rect_min: preview_data->PreviewRect.Min, clip_rect_max: preview_data->PreviewRect.Max, intersect_with_current_clip_rect: true);
1859
1860 return true;
1861}
1862
1863void ImGui::EndComboPreview()
1864{
1865 ImGuiContext& g = *GImGui;
1866 ImGuiWindow* window = g.CurrentWindow;
1867 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1868
1869 // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
1870 ImDrawList* draw_list = window->DrawList;
1871 if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
1872 if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1873 {
1874 draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
1875 draw_list->_TryMergeDrawCmds();
1876 }
1877 PopClipRect();
1878 window->DC.CursorPos = preview_data->BackupCursorPos;
1879 window->DC.CursorMaxPos = ImMax(lhs: window->DC.CursorMaxPos, rhs: preview_data->BackupCursorMaxPos);
1880 window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1881 window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1882 window->DC.LayoutType = preview_data->BackupLayout;
1883 window->DC.IsSameLine = false;
1884 preview_data->PreviewRect = ImRect();
1885}
1886
1887// Getter for the old Combo() API: const char*[]
1888static const char* Items_ArrayGetter(void* data, int idx)
1889{
1890 const char* const* items = (const char* const*)data;
1891 return items[idx];
1892}
1893
1894// Getter for the old Combo() API: "item1\0item2\0item3\0"
1895static const char* Items_SingleStringGetter(void* data, int idx)
1896{
1897 const char* items_separated_by_zeros = (const char*)data;
1898 int items_count = 0;
1899 const char* p = items_separated_by_zeros;
1900 while (*p)
1901 {
1902 if (idx == items_count)
1903 break;
1904 p += strlen(s: p) + 1;
1905 items_count++;
1906 }
1907 return *p ? p : NULL;
1908}
1909
1910// Old API, prefer using BeginCombo() nowadays if you can.
1911bool 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)
1912{
1913 ImGuiContext& g = *GImGui;
1914
1915 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1916 const char* preview_value = NULL;
1917 if (*current_item >= 0 && *current_item < items_count)
1918 preview_value = getter(user_data, *current_item);
1919
1920 // 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.
1921 if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1922 SetNextWindowSizeConstraints(size_min: ImVec2(0, 0), size_max: ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items)));
1923
1924 if (!BeginCombo(label, preview_value, flags: ImGuiComboFlags_None))
1925 return false;
1926
1927 // Display items
1928 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1929 bool value_changed = false;
1930 for (int i = 0; i < items_count; i++)
1931 {
1932 const char* item_text = getter(user_data, i);
1933 if (item_text == NULL)
1934 item_text = "*Unknown item*";
1935
1936 PushID(int_id: i);
1937 const bool item_selected = (i == *current_item);
1938 if (Selectable(label: item_text, selected: item_selected) && *current_item != i)
1939 {
1940 value_changed = true;
1941 *current_item = i;
1942 }
1943 if (item_selected)
1944 SetItemDefaultFocus();
1945 PopID();
1946 }
1947
1948 EndCombo();
1949
1950 if (value_changed)
1951 MarkItemEdited(id: g.LastItemData.ID);
1952
1953 return value_changed;
1954}
1955
1956// Combo box helper allowing to pass an array of strings.
1957bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1958{
1959 const bool value_changed = Combo(label, current_item, getter: Items_ArrayGetter, user_data: (void*)items, items_count, popup_max_height_in_items: height_in_items);
1960 return value_changed;
1961}
1962
1963// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
1964bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1965{
1966 int items_count = 0;
1967 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
1968 while (*p)
1969 {
1970 p += strlen(s: p) + 1;
1971 items_count++;
1972 }
1973 bool value_changed = Combo(label, current_item, getter: Items_SingleStringGetter, user_data: (void*)items_separated_by_zeros, items_count, popup_max_height_in_items: height_in_items);
1974 return value_changed;
1975}
1976
1977#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1978
1979struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
1980static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
1981{
1982 ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
1983 const char* s = NULL;
1984 data->OldCallback(data->UserData, idx, &s);
1985 return s;
1986}
1987
1988bool 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)
1989{
1990 ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { .UserData: user_data, .OldCallback: old_getter };
1991 return ListBox(label, current_item, getter: ImGuiGetNameFromIndexOldToNewCallback, user_data: &old_to_new_data, items_count, height_in_items);
1992}
1993bool 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)
1994{
1995 ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { .UserData: user_data, .OldCallback: old_getter };
1996 return Combo(label, current_item, getter: ImGuiGetNameFromIndexOldToNewCallback, user_data: &old_to_new_data, items_count, popup_max_height_in_items);
1997}
1998
1999#endif
2000
2001//-------------------------------------------------------------------------
2002// [SECTION] Data Type and Data Formatting Helpers [Internal]
2003//-------------------------------------------------------------------------
2004// - DataTypeGetInfo()
2005// - DataTypeFormatString()
2006// - DataTypeApplyOp()
2007// - DataTypeApplyFromText()
2008// - DataTypeCompare()
2009// - DataTypeClamp()
2010// - GetMinimumStepAtDecimalPrecision
2011// - RoundScalarWithFormat<>()
2012//-------------------------------------------------------------------------
2013
2014static const ImGuiDataTypeInfo GDataTypeInfo[] =
2015{
2016 { .Size: sizeof(char), .Name: "S8", .PrintFmt: "%d", .ScanFmt: "%d" }, // ImGuiDataType_S8
2017 { .Size: sizeof(unsigned char), .Name: "U8", .PrintFmt: "%u", .ScanFmt: "%u" },
2018 { .Size: sizeof(short), .Name: "S16", .PrintFmt: "%d", .ScanFmt: "%d" }, // ImGuiDataType_S16
2019 { .Size: sizeof(unsigned short), .Name: "U16", .PrintFmt: "%u", .ScanFmt: "%u" },
2020 { .Size: sizeof(int), .Name: "S32", .PrintFmt: "%d", .ScanFmt: "%d" }, // ImGuiDataType_S32
2021 { .Size: sizeof(unsigned int), .Name: "U32", .PrintFmt: "%u", .ScanFmt: "%u" },
2022#ifdef _MSC_VER
2023 { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64
2024 { sizeof(ImU64), "U64", "%I64u","%I64u" },
2025#else
2026 { .Size: sizeof(ImS64), .Name: "S64", .PrintFmt: "%lld", .ScanFmt: "%lld" }, // ImGuiDataType_S64
2027 { .Size: sizeof(ImU64), .Name: "U64", .PrintFmt: "%llu", .ScanFmt: "%llu" },
2028#endif
2029 { .Size: sizeof(float), .Name: "float", .PrintFmt: "%.3f",.ScanFmt: "%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg)
2030 { .Size: sizeof(double), .Name: "double",.PrintFmt: "%f", .ScanFmt: "%lf" }, // ImGuiDataType_Double
2031};
2032IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
2033
2034const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
2035{
2036 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2037 return &GDataTypeInfo[data_type];
2038}
2039
2040int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
2041{
2042 // Signedness doesn't matter when pushing integer arguments
2043 if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
2044 return ImFormatString(buf, buf_size, fmt: format, *(const ImU32*)p_data);
2045 if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2046 return ImFormatString(buf, buf_size, fmt: format, *(const ImU64*)p_data);
2047 if (data_type == ImGuiDataType_Float)
2048 return ImFormatString(buf, buf_size, fmt: format, *(const float*)p_data);
2049 if (data_type == ImGuiDataType_Double)
2050 return ImFormatString(buf, buf_size, fmt: format, *(const double*)p_data);
2051 if (data_type == ImGuiDataType_S8)
2052 return ImFormatString(buf, buf_size, fmt: format, *(const ImS8*)p_data);
2053 if (data_type == ImGuiDataType_U8)
2054 return ImFormatString(buf, buf_size, fmt: format, *(const ImU8*)p_data);
2055 if (data_type == ImGuiDataType_S16)
2056 return ImFormatString(buf, buf_size, fmt: format, *(const ImS16*)p_data);
2057 if (data_type == ImGuiDataType_U16)
2058 return ImFormatString(buf, buf_size, fmt: format, *(const ImU16*)p_data);
2059 IM_ASSERT(0);
2060 return 0;
2061}
2062
2063void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
2064{
2065 IM_ASSERT(op == '+' || op == '-');
2066 switch (data_type)
2067 {
2068 case ImGuiDataType_S8:
2069 if (op == '+') { *(ImS8*)output = ImAddClampOverflow(a: *(const ImS8*)arg1, b: *(const ImS8*)arg2, mn: IM_S8_MIN, mx: IM_S8_MAX); }
2070 if (op == '-') { *(ImS8*)output = ImSubClampOverflow(a: *(const ImS8*)arg1, b: *(const ImS8*)arg2, mn: IM_S8_MIN, mx: IM_S8_MAX); }
2071 return;
2072 case ImGuiDataType_U8:
2073 if (op == '+') { *(ImU8*)output = ImAddClampOverflow(a: *(const ImU8*)arg1, b: *(const ImU8*)arg2, mn: IM_U8_MIN, mx: IM_U8_MAX); }
2074 if (op == '-') { *(ImU8*)output = ImSubClampOverflow(a: *(const ImU8*)arg1, b: *(const ImU8*)arg2, mn: IM_U8_MIN, mx: IM_U8_MAX); }
2075 return;
2076 case ImGuiDataType_S16:
2077 if (op == '+') { *(ImS16*)output = ImAddClampOverflow(a: *(const ImS16*)arg1, b: *(const ImS16*)arg2, mn: IM_S16_MIN, mx: IM_S16_MAX); }
2078 if (op == '-') { *(ImS16*)output = ImSubClampOverflow(a: *(const ImS16*)arg1, b: *(const ImS16*)arg2, mn: IM_S16_MIN, mx: IM_S16_MAX); }
2079 return;
2080 case ImGuiDataType_U16:
2081 if (op == '+') { *(ImU16*)output = ImAddClampOverflow(a: *(const ImU16*)arg1, b: *(const ImU16*)arg2, mn: IM_U16_MIN, mx: IM_U16_MAX); }
2082 if (op == '-') { *(ImU16*)output = ImSubClampOverflow(a: *(const ImU16*)arg1, b: *(const ImU16*)arg2, mn: IM_U16_MIN, mx: IM_U16_MAX); }
2083 return;
2084 case ImGuiDataType_S32:
2085 if (op == '+') { *(ImS32*)output = ImAddClampOverflow(a: *(const ImS32*)arg1, b: *(const ImS32*)arg2, mn: IM_S32_MIN, mx: IM_S32_MAX); }
2086 if (op == '-') { *(ImS32*)output = ImSubClampOverflow(a: *(const ImS32*)arg1, b: *(const ImS32*)arg2, mn: IM_S32_MIN, mx: IM_S32_MAX); }
2087 return;
2088 case ImGuiDataType_U32:
2089 if (op == '+') { *(ImU32*)output = ImAddClampOverflow(a: *(const ImU32*)arg1, b: *(const ImU32*)arg2, mn: IM_U32_MIN, mx: IM_U32_MAX); }
2090 if (op == '-') { *(ImU32*)output = ImSubClampOverflow(a: *(const ImU32*)arg1, b: *(const ImU32*)arg2, mn: IM_U32_MIN, mx: IM_U32_MAX); }
2091 return;
2092 case ImGuiDataType_S64:
2093 if (op == '+') { *(ImS64*)output = ImAddClampOverflow(a: *(const ImS64*)arg1, b: *(const ImS64*)arg2, mn: IM_S64_MIN, mx: IM_S64_MAX); }
2094 if (op == '-') { *(ImS64*)output = ImSubClampOverflow(a: *(const ImS64*)arg1, b: *(const ImS64*)arg2, mn: IM_S64_MIN, mx: IM_S64_MAX); }
2095 return;
2096 case ImGuiDataType_U64:
2097 if (op == '+') { *(ImU64*)output = ImAddClampOverflow(a: *(const ImU64*)arg1, b: *(const ImU64*)arg2, mn: IM_U64_MIN, mx: IM_U64_MAX); }
2098 if (op == '-') { *(ImU64*)output = ImSubClampOverflow(a: *(const ImU64*)arg1, b: *(const ImU64*)arg2, mn: IM_U64_MIN, mx: IM_U64_MAX); }
2099 return;
2100 case ImGuiDataType_Float:
2101 if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
2102 if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
2103 return;
2104 case ImGuiDataType_Double:
2105 if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
2106 if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
2107 return;
2108 case ImGuiDataType_COUNT: break;
2109 }
2110 IM_ASSERT(0);
2111}
2112
2113// User can input math operators (e.g. +100) to edit a numerical values.
2114// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
2115bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format)
2116{
2117 while (ImCharIsBlankA(c: *buf))
2118 buf++;
2119 if (!buf[0])
2120 return false;
2121
2122 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2123 const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2124 ImGuiDataTypeTempStorage data_backup;
2125 memcpy(dest: &data_backup, src: p_data, n: type_info->Size);
2126
2127 // Sanitize format
2128 // - 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
2129 // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
2130 char format_sanitized[32];
2131 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2132 format = type_info->ScanFmt;
2133 else
2134 format = ImParseFormatSanitizeForScanning(fmt_in: format, fmt_out: format_sanitized, IM_ARRAYSIZE(format_sanitized));
2135
2136 // Small types need a 32-bit buffer to receive the result from scanf()
2137 int v32 = 0;
2138 if (sscanf(s: buf, format: format, type_info->Size >= 4 ? p_data : &v32) < 1)
2139 return false;
2140 if (type_info->Size < 4)
2141 {
2142 if (data_type == ImGuiDataType_S8)
2143 *(ImS8*)p_data = (ImS8)ImClamp(v: v32, mn: (int)IM_S8_MIN, mx: (int)IM_S8_MAX);
2144 else if (data_type == ImGuiDataType_U8)
2145 *(ImU8*)p_data = (ImU8)ImClamp(v: v32, mn: (int)IM_U8_MIN, mx: (int)IM_U8_MAX);
2146 else if (data_type == ImGuiDataType_S16)
2147 *(ImS16*)p_data = (ImS16)ImClamp(v: v32, mn: (int)IM_S16_MIN, mx: (int)IM_S16_MAX);
2148 else if (data_type == ImGuiDataType_U16)
2149 *(ImU16*)p_data = (ImU16)ImClamp(v: v32, mn: (int)IM_U16_MIN, mx: (int)IM_U16_MAX);
2150 else
2151 IM_ASSERT(0);
2152 }
2153
2154 return memcmp(s1: &data_backup, s2: p_data, n: type_info->Size) != 0;
2155}
2156
2157template<typename T>
2158static int DataTypeCompareT(const T* lhs, const T* rhs)
2159{
2160 if (*lhs < *rhs) return -1;
2161 if (*lhs > *rhs) return +1;
2162 return 0;
2163}
2164
2165int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2166{
2167 switch (data_type)
2168 {
2169 case ImGuiDataType_S8: return DataTypeCompareT<ImS8 >(lhs: (const ImS8* )arg_1, rhs: (const ImS8* )arg_2);
2170 case ImGuiDataType_U8: return DataTypeCompareT<ImU8 >(lhs: (const ImU8* )arg_1, rhs: (const ImU8* )arg_2);
2171 case ImGuiDataType_S16: return DataTypeCompareT<ImS16 >(lhs: (const ImS16* )arg_1, rhs: (const ImS16* )arg_2);
2172 case ImGuiDataType_U16: return DataTypeCompareT<ImU16 >(lhs: (const ImU16* )arg_1, rhs: (const ImU16* )arg_2);
2173 case ImGuiDataType_S32: return DataTypeCompareT<ImS32 >(lhs: (const ImS32* )arg_1, rhs: (const ImS32* )arg_2);
2174 case ImGuiDataType_U32: return DataTypeCompareT<ImU32 >(lhs: (const ImU32* )arg_1, rhs: (const ImU32* )arg_2);
2175 case ImGuiDataType_S64: return DataTypeCompareT<ImS64 >(lhs: (const ImS64* )arg_1, rhs: (const ImS64* )arg_2);
2176 case ImGuiDataType_U64: return DataTypeCompareT<ImU64 >(lhs: (const ImU64* )arg_1, rhs: (const ImU64* )arg_2);
2177 case ImGuiDataType_Float: return DataTypeCompareT<float >(lhs: (const float* )arg_1, rhs: (const float* )arg_2);
2178 case ImGuiDataType_Double: return DataTypeCompareT<double>(lhs: (const double*)arg_1, rhs: (const double*)arg_2);
2179 case ImGuiDataType_COUNT: break;
2180 }
2181 IM_ASSERT(0);
2182 return 0;
2183}
2184
2185template<typename T>
2186static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2187{
2188 // Clamp, both sides are optional, return true if modified
2189 if (v_min && *v < *v_min) { *v = *v_min; return true; }
2190 if (v_max && *v > *v_max) { *v = *v_max; return true; }
2191 return false;
2192}
2193
2194bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2195{
2196 switch (data_type)
2197 {
2198 case ImGuiDataType_S8: return DataTypeClampT<ImS8 >(v: (ImS8* )p_data, v_min: (const ImS8* )p_min, v_max: (const ImS8* )p_max);
2199 case ImGuiDataType_U8: return DataTypeClampT<ImU8 >(v: (ImU8* )p_data, v_min: (const ImU8* )p_min, v_max: (const ImU8* )p_max);
2200 case ImGuiDataType_S16: return DataTypeClampT<ImS16 >(v: (ImS16* )p_data, v_min: (const ImS16* )p_min, v_max: (const ImS16* )p_max);
2201 case ImGuiDataType_U16: return DataTypeClampT<ImU16 >(v: (ImU16* )p_data, v_min: (const ImU16* )p_min, v_max: (const ImU16* )p_max);
2202 case ImGuiDataType_S32: return DataTypeClampT<ImS32 >(v: (ImS32* )p_data, v_min: (const ImS32* )p_min, v_max: (const ImS32* )p_max);
2203 case ImGuiDataType_U32: return DataTypeClampT<ImU32 >(v: (ImU32* )p_data, v_min: (const ImU32* )p_min, v_max: (const ImU32* )p_max);
2204 case ImGuiDataType_S64: return DataTypeClampT<ImS64 >(v: (ImS64* )p_data, v_min: (const ImS64* )p_min, v_max: (const ImS64* )p_max);
2205 case ImGuiDataType_U64: return DataTypeClampT<ImU64 >(v: (ImU64* )p_data, v_min: (const ImU64* )p_min, v_max: (const ImU64* )p_max);
2206 case ImGuiDataType_Float: return DataTypeClampT<float >(v: (float* )p_data, v_min: (const float* )p_min, v_max: (const float* )p_max);
2207 case ImGuiDataType_Double: return DataTypeClampT<double>(v: (double*)p_data, v_min: (const double*)p_min, v_max: (const double*)p_max);
2208 case ImGuiDataType_COUNT: break;
2209 }
2210 IM_ASSERT(0);
2211 return false;
2212}
2213
2214static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2215{
2216 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 };
2217 if (decimal_precision < 0)
2218 return FLT_MIN;
2219 return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(x: 10.0f, y: (float)-decimal_precision);
2220}
2221
2222template<typename TYPE>
2223TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2224{
2225 IM_UNUSED(data_type);
2226 IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2227 const char* fmt_start = ImParseFormatFindStart(format);
2228 if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2229 return v;
2230
2231 // Sanitize format
2232 char fmt_sanitized[32];
2233 ImParseFormatSanitizeForPrinting(fmt_in: fmt_start, fmt_out: fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
2234 fmt_start = fmt_sanitized;
2235
2236 // Format value with our rounding, and read back
2237 char v_str[64];
2238 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
2239 const char* p = v_str;
2240 while (*p == ' ')
2241 p++;
2242 v = (TYPE)ImAtof(p);
2243
2244 return v;
2245}
2246
2247//-------------------------------------------------------------------------
2248// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2249//-------------------------------------------------------------------------
2250// - DragBehaviorT<>() [Internal]
2251// - DragBehavior() [Internal]
2252// - DragScalar()
2253// - DragScalarN()
2254// - DragFloat()
2255// - DragFloat2()
2256// - DragFloat3()
2257// - DragFloat4()
2258// - DragFloatRange2()
2259// - DragInt()
2260// - DragInt2()
2261// - DragInt3()
2262// - DragInt4()
2263// - DragIntRange2()
2264//-------------------------------------------------------------------------
2265
2266// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2267template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2268bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2269{
2270 ImGuiContext& g = *GImGui;
2271 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2272 const bool is_clamped = (v_min < v_max);
2273 const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2274 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2275
2276 // Default tweak speed
2277 if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
2278 v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2279
2280 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2281 float adjust_delta = 0.0f;
2282 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(button: 0, lock_threshold: g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2283 {
2284 adjust_delta = g.IO.MouseDelta[axis];
2285 if (g.IO.KeyAlt)
2286 adjust_delta *= 1.0f / 100.0f;
2287 if (g.IO.KeyShift)
2288 adjust_delta *= 10.0f;
2289 }
2290 else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
2291 {
2292 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, default_value: 3) : 0;
2293 const bool tweak_slow = IsKeyDown(key: (g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
2294 const bool tweak_fast = IsKeyDown(key: (g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
2295 const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f;
2296 adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
2297 v_speed = ImMax(lhs: v_speed, rhs: GetMinimumStepAtDecimalPrecision(decimal_precision));
2298 }
2299 adjust_delta *= v_speed;
2300
2301 // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2302 if (axis == ImGuiAxis_Y)
2303 adjust_delta = -adjust_delta;
2304
2305 // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2306 if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2307 adjust_delta /= (float)(v_max - v_min);
2308
2309 // Clear current value on activation
2310 // 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.
2311 bool is_just_activated = g.ActiveIdIsJustActivated;
2312 bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
2313 if (is_just_activated || is_already_past_limits_and_pushing_outward)
2314 {
2315 g.DragCurrentAccum = 0.0f;
2316 g.DragCurrentAccumDirty = false;
2317 }
2318 else if (adjust_delta != 0.0f)
2319 {
2320 g.DragCurrentAccum += adjust_delta;
2321 g.DragCurrentAccumDirty = true;
2322 }
2323
2324 if (!g.DragCurrentAccumDirty)
2325 return false;
2326
2327 TYPE v_cur = *v;
2328 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2329
2330 float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2331 const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2332 if (is_logarithmic)
2333 {
2334 // 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.
2335 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, default_value: 3) : 1;
2336 logarithmic_zero_epsilon = ImPow(x: 0.1f, y: (float)decimal_precision);
2337
2338 // Convert to parametric space, apply delta, convert back
2339 float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2340 float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2341 v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2342 v_old_ref_for_accum_remainder = v_old_parametric;
2343 }
2344 else
2345 {
2346 v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2347 }
2348
2349 // Round to user desired precision based on format string
2350 if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2351 v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
2352
2353 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2354 g.DragCurrentAccumDirty = false;
2355 if (is_logarithmic)
2356 {
2357 // Convert to parametric space, apply delta, convert back
2358 float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2359 g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2360 }
2361 else
2362 {
2363 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2364 }
2365
2366 // Lose zero sign for float/double
2367 if (v_cur == (TYPE)-0)
2368 v_cur = (TYPE)0;
2369
2370 // Clamp values (+ handle overflow/wrap-around for integer types)
2371 if (*v != v_cur && is_clamped)
2372 {
2373 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2374 v_cur = v_min;
2375 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2376 v_cur = v_max;
2377 }
2378
2379 // Apply result
2380 if (*v == v_cur)
2381 return false;
2382 *v = v_cur;
2383 return true;
2384}
2385
2386bool 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)
2387{
2388 // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2389 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.");
2390
2391 ImGuiContext& g = *GImGui;
2392 if (g.ActiveId == id)
2393 {
2394 // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
2395 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2396 ClearActiveID();
2397 else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2398 ClearActiveID();
2399 }
2400 if (g.ActiveId != id)
2401 return false;
2402 if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2403 return false;
2404
2405 switch (data_type)
2406 {
2407 case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(data_type: ImGuiDataType_S32, v: &v32, v_speed, v_min: p_min ? *(const ImS8*) p_min : IM_S8_MIN, v_max: p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
2408 case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(data_type: ImGuiDataType_U32, v: &v32, v_speed, v_min: p_min ? *(const ImU8*) p_min : IM_U8_MIN, v_max: p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
2409 case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(data_type: ImGuiDataType_S32, v: &v32, v_speed, v_min: p_min ? *(const ImS16*)p_min : IM_S16_MIN, v_max: p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
2410 case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(data_type: ImGuiDataType_U32, v: &v32, v_speed, v_min: p_min ? *(const ImU16*)p_min : IM_U16_MIN, v_max: p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
2411 case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, v: (ImS32*)p_v, v_speed, v_min: p_min ? *(const ImS32* )p_min : IM_S32_MIN, v_max: p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags);
2412 case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, v: (ImU32*)p_v, v_speed, v_min: p_min ? *(const ImU32* )p_min : IM_U32_MIN, v_max: p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags);
2413 case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, v: (ImS64*)p_v, v_speed, v_min: p_min ? *(const ImS64* )p_min : IM_S64_MIN, v_max: p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags);
2414 case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, v: (ImU64*)p_v, v_speed, v_min: p_min ? *(const ImU64* )p_min : IM_U64_MIN, v_max: p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags);
2415 case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, v: (float*)p_v, v_speed, v_min: p_min ? *(const float* )p_min : -FLT_MAX, v_max: p_max ? *(const float* )p_max : FLT_MAX, format, flags);
2416 case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, v: (double*)p_v, v_speed, v_min: p_min ? *(const double*)p_min : -DBL_MAX, v_max: p_max ? *(const double*)p_max : DBL_MAX, format, flags);
2417 case ImGuiDataType_COUNT: break;
2418 }
2419 IM_ASSERT(0);
2420 return false;
2421}
2422
2423// 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.
2424// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
2425bool 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)
2426{
2427 ImGuiWindow* window = GetCurrentWindow();
2428 if (window->SkipItems)
2429 return false;
2430
2431 ImGuiContext& g = *GImGui;
2432 const ImGuiStyle& style = g.Style;
2433 const ImGuiID id = window->GetID(str: label);
2434 const float w = CalcItemWidth();
2435
2436 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
2437 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2438 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));
2439
2440 const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2441 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
2442 if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb, extra_flags: temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
2443 return false;
2444
2445 // Default format string when passing NULL
2446 if (format == NULL)
2447 format = DataTypeGetInfo(data_type)->PrintFmt;
2448
2449 const bool hovered = ItemHoverable(bb: frame_bb, id, item_flags: g.LastItemData.InFlags);
2450 bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2451 if (!temp_input_is_active)
2452 {
2453 // Tabbing or CTRL-clicking on Drag turns it into an InputText
2454 const bool clicked = hovered && IsMouseClicked(button: 0, owner_id: id);
2455 const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(key: ImGuiKey_MouseLeft, owner_id: id));
2456 const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
2457 if (make_active && (clicked || double_clicked))
2458 SetKeyOwner(key: ImGuiKey_MouseLeft, owner_id: id);
2459 if (make_active && temp_input_allowed)
2460 if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
2461 temp_input_is_active = true;
2462
2463 // (Optional) simple click (without moving) turns Drag into an InputText
2464 if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2465 if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(button: 0, lock_threshold: g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2466 {
2467 g.NavActivateId = id;
2468 g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
2469 temp_input_is_active = true;
2470 }
2471
2472 if (make_active && !temp_input_is_active)
2473 {
2474 SetActiveID(id, window);
2475 SetFocusID(id, window);
2476 FocusWindow(window);
2477 g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2478 }
2479 }
2480
2481 if (temp_input_is_active)
2482 {
2483 // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
2484 const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, arg_1: p_min, arg_2: p_max) < 0);
2485 return TempInputScalar(bb: frame_bb, id, label, data_type, p_data, format, p_clamp_min: is_clamp_input ? p_min : NULL, p_clamp_max: is_clamp_input ? p_max : NULL);
2486 }
2487
2488 // Draw frame
2489 const ImU32 frame_col = GetColorU32(idx: g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2490 RenderNavHighlight(bb: frame_bb, id);
2491 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: frame_col, border: true, rounding: style.FrameRounding);
2492
2493 // Drag behavior
2494 const bool value_changed = DragBehavior(id, data_type, p_v: p_data, v_speed, p_min, p_max, format, flags);
2495 if (value_changed)
2496 MarkItemEdited(id);
2497
2498 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2499 char value_buf[64];
2500 const char* value_buf_end = value_buf + DataTypeFormatString(buf: value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
2501 if (g.LogEnabled)
2502 LogSetNextTextDecoration(prefix: "{", suffix: "}");
2503 RenderTextClipped(pos_min: frame_bb.Min, pos_max: frame_bb.Max, text: value_buf, text_end: value_buf_end, NULL, align: ImVec2(0.5f, 0.5f));
2504
2505 if (label_size.x > 0.0f)
2506 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
2507
2508 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
2509 return value_changed;
2510}
2511
2512bool 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)
2513{
2514 ImGuiWindow* window = GetCurrentWindow();
2515 if (window->SkipItems)
2516 return false;
2517
2518 ImGuiContext& g = *GImGui;
2519 bool value_changed = false;
2520 BeginGroup();
2521 PushID(str_id: label);
2522 PushMultiItemsWidths(components, width_full: CalcItemWidth());
2523 size_t type_size = GDataTypeInfo[data_type].Size;
2524 for (int i = 0; i < components; i++)
2525 {
2526 PushID(int_id: i);
2527 if (i > 0)
2528 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2529 value_changed |= DragScalar(label: "", data_type, p_data, v_speed, p_min, p_max, format, flags);
2530 PopID();
2531 PopItemWidth();
2532 p_data = (void*)((char*)p_data + type_size);
2533 }
2534 PopID();
2535
2536 const char* label_end = FindRenderedTextEnd(text: label);
2537 if (label != label_end)
2538 {
2539 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2540 TextEx(text: label, text_end: label_end);
2541 }
2542
2543 EndGroup();
2544 return value_changed;
2545}
2546
2547bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2548{
2549 return DragScalar(label, data_type: ImGuiDataType_Float, p_data: v, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2550}
2551
2552bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2553{
2554 return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 2, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2555}
2556
2557bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2558{
2559 return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 3, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2560}
2561
2562bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2563{
2564 return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 4, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2565}
2566
2567// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2568bool 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)
2569{
2570 ImGuiWindow* window = GetCurrentWindow();
2571 if (window->SkipItems)
2572 return false;
2573
2574 ImGuiContext& g = *GImGui;
2575 PushID(str_id: label);
2576 BeginGroup();
2577 PushMultiItemsWidths(components: 2, width_full: CalcItemWidth());
2578
2579 float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2580 float min_max = (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max);
2581 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2582 bool value_changed = DragScalar(label: "##min", data_type: ImGuiDataType_Float, p_data: v_current_min, v_speed, p_min: &min_min, p_max: &min_max, format, flags: min_flags);
2583 PopItemWidth();
2584 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2585
2586 float max_min = (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min);
2587 float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2588 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2589 value_changed |= DragScalar(label: "##max", data_type: ImGuiDataType_Float, p_data: v_current_max, v_speed, p_min: &max_min, p_max: &max_max, format: format_max ? format_max : format, flags: max_flags);
2590 PopItemWidth();
2591 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2592
2593 TextEx(text: label, text_end: FindRenderedTextEnd(text: label));
2594 EndGroup();
2595 PopID();
2596
2597 return value_changed;
2598}
2599
2600// NB: v_speed is float to allow adjusting the drag speed with more precision
2601bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2602{
2603 return DragScalar(label, data_type: ImGuiDataType_S32, p_data: v, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2604}
2605
2606bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2607{
2608 return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 2, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2609}
2610
2611bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2612{
2613 return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 3, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2614}
2615
2616bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2617{
2618 return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 4, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2619}
2620
2621// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2622bool 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)
2623{
2624 ImGuiWindow* window = GetCurrentWindow();
2625 if (window->SkipItems)
2626 return false;
2627
2628 ImGuiContext& g = *GImGui;
2629 PushID(str_id: label);
2630 BeginGroup();
2631 PushMultiItemsWidths(components: 2, width_full: CalcItemWidth());
2632
2633 int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2634 int min_max = (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max);
2635 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2636 bool value_changed = DragInt(label: "##min", v: v_current_min, v_speed, v_min: min_min, v_max: min_max, format, flags: min_flags);
2637 PopItemWidth();
2638 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2639
2640 int max_min = (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min);
2641 int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2642 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2643 value_changed |= DragInt(label: "##max", v: v_current_max, v_speed, v_min: max_min, v_max: max_max, format: format_max ? format_max : format, flags: max_flags);
2644 PopItemWidth();
2645 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2646
2647 TextEx(text: label, text_end: FindRenderedTextEnd(text: label));
2648 EndGroup();
2649 PopID();
2650
2651 return value_changed;
2652}
2653
2654//-------------------------------------------------------------------------
2655// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2656//-------------------------------------------------------------------------
2657// - ScaleRatioFromValueT<> [Internal]
2658// - ScaleValueFromRatioT<> [Internal]
2659// - SliderBehaviorT<>() [Internal]
2660// - SliderBehavior() [Internal]
2661// - SliderScalar()
2662// - SliderScalarN()
2663// - SliderFloat()
2664// - SliderFloat2()
2665// - SliderFloat3()
2666// - SliderFloat4()
2667// - SliderAngle()
2668// - SliderInt()
2669// - SliderInt2()
2670// - SliderInt3()
2671// - SliderInt4()
2672// - VSliderScalar()
2673// - VSliderFloat()
2674// - VSliderInt()
2675//-------------------------------------------------------------------------
2676
2677// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2678template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2679float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2680{
2681 if (v_min == v_max)
2682 return 0.0f;
2683 IM_UNUSED(data_type);
2684
2685 const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2686 if (is_logarithmic)
2687 {
2688 bool flipped = v_max < v_min;
2689
2690 if (flipped) // Handle the case where the range is backwards
2691 ImSwap(v_min, v_max);
2692
2693 // Fudge min/max to avoid getting close to log(0)
2694 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2695 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2696
2697 // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2698 if ((v_min == 0.0f) && (v_max < 0.0f))
2699 v_min_fudged = -logarithmic_zero_epsilon;
2700 else if ((v_max == 0.0f) && (v_min < 0.0f))
2701 v_max_fudged = -logarithmic_zero_epsilon;
2702
2703 float result;
2704 if (v_clamped <= v_min_fudged)
2705 result = 0.0f; // Workaround for values that are in-range but below our fudge
2706 else if (v_clamped >= v_max_fudged)
2707 result = 1.0f; // Workaround for values that are in-range but above our fudge
2708 else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
2709 {
2710 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)
2711 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2712 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2713 if (v == 0.0f)
2714 result = zero_point_center; // Special case for exactly zero
2715 else if (v < 0.0f)
2716 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
2717 else
2718 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));
2719 }
2720 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2721 result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
2722 else
2723 result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
2724
2725 return flipped ? (1.0f - result) : result;
2726 }
2727 else
2728 {
2729 // Linear slider
2730 return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
2731 }
2732}
2733
2734// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
2735template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2736TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2737{
2738 // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
2739 // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
2740 if (t <= 0.0f || v_min == v_max)
2741 return v_min;
2742 if (t >= 1.0f)
2743 return v_max;
2744
2745 TYPE result = (TYPE)0;
2746 if (is_logarithmic)
2747 {
2748 // Fudge min/max to avoid getting silly results close to zero
2749 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2750 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2751
2752 const bool flipped = v_max < v_min; // Check if range is "backwards"
2753 if (flipped)
2754 ImSwap(v_min_fudged, v_max_fudged);
2755
2756 // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2757 if ((v_max == 0.0f) && (v_min < 0.0f))
2758 v_max_fudged = -logarithmic_zero_epsilon;
2759
2760 float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
2761
2762 if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
2763 {
2764 float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs(x: (float)v_max - (float)v_min); // The zero point in parametric space
2765 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2766 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2767 if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
2768 result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
2769 else if (