1// dear imgui, v1.90.0
2// (widgets code)
3
4/*
5
6Index of this file:
7
8// [SECTION] Forward Declarations
9// [SECTION] Widgets: Text, etc.
10// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
11// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
12// [SECTION] Widgets: ComboBox
13// [SECTION] Data Type and Data Formatting Helpers
14// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
15// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
16// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
17// [SECTION] Widgets: InputText, InputTextMultiline
18// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
19// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
20// [SECTION] Widgets: Selectable
21// [SECTION] Widgets: Typing-Select support
22// [SECTION] Widgets: Multi-Select support
23// [SECTION] Widgets: ListBox
24// [SECTION] Widgets: PlotLines, PlotHistogram
25// [SECTION] Widgets: Value helpers
26// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
27// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
28// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
29// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
30
31*/
32
33#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
34#define _CRT_SECURE_NO_WARNINGS
35#endif
36
37#ifndef IMGUI_DEFINE_MATH_OPERATORS
38#define IMGUI_DEFINE_MATH_OPERATORS
39#endif
40
41#include "imgui.h"
42#ifndef IMGUI_DISABLE
43#include "imgui_internal.h"
44
45// System includes
46#include <stdint.h> // intptr_t
47
48//-------------------------------------------------------------------------
49// Warnings
50//-------------------------------------------------------------------------
51
52// Visual Studio warnings
53#ifdef _MSC_VER
54#pragma warning (disable: 4127) // condition expression is constant
55#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
56#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
57#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
58#endif
59#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
60#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
61#endif
62
63// Clang/GCC warnings with -Weverything
64#if defined(__clang__)
65#if __has_warning("-Wunknown-warning-option")
66#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
67#endif
68#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
69#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
70#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
71#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
72#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
73#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0
74#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
75#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
76#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
77#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
78#elif defined(__GNUC__)
79#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
80#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
81#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
82#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
83#endif
84
85//-------------------------------------------------------------------------
86// Data
87//-------------------------------------------------------------------------
88
89// Widgets
90static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
91static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags.
92
93// Those MIN/MAX values are not define because we need to point to them
94static const signed char IM_S8_MIN = -128;
95static const signed char IM_S8_MAX = 127;
96static const unsigned char IM_U8_MIN = 0;
97static const unsigned char IM_U8_MAX = 0xFF;
98static const signed short IM_S16_MIN = -32768;
99static const signed short IM_S16_MAX = 32767;
100static const unsigned short IM_U16_MIN = 0;
101static const unsigned short IM_U16_MAX = 0xFFFF;
102static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
103static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
104static const ImU32 IM_U32_MIN = 0;
105static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
106#ifdef LLONG_MIN
107static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
108static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
109#else
110static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
111static const ImS64 IM_S64_MAX = 9223372036854775807LL;
112#endif
113static const ImU64 IM_U64_MIN = 0;
114#ifdef ULLONG_MAX
115static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
116#else
117static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
118#endif
119
120//-------------------------------------------------------------------------
121// [SECTION] Forward Declarations
122//-------------------------------------------------------------------------
123
124// For InputTextEx()
125static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
126static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
127static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
128
129//-------------------------------------------------------------------------
130// [SECTION] Widgets: Text, etc.
131//-------------------------------------------------------------------------
132// - TextEx() [Internal]
133// - TextUnformatted()
134// - Text()
135// - TextV()
136// - TextColored()
137// - TextColoredV()
138// - TextDisabled()
139// - TextDisabledV()
140// - TextWrapped()
141// - TextWrappedV()
142// - LabelText()
143// - LabelTextV()
144// - BulletText()
145// - BulletTextV()
146//-------------------------------------------------------------------------
147
148void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
149{
150 ImGuiWindow* window = GetCurrentWindow();
151 if (window->SkipItems)
152 return;
153 ImGuiContext& g = *GImGui;
154
155 // Accept null ranges
156 if (text == text_end)
157 text = text_end = "";
158
159 // Calculate length
160 const char* text_begin = text;
161 if (text_end == NULL)
162 text_end = text + strlen(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
480bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
481{
482 ImGuiContext& g = *GImGui;
483 ImGuiWindow* window = GetCurrentWindow();
484
485 // Default only reacts to left mouse button
486 if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
487 flags |= ImGuiButtonFlags_MouseButtonDefault_;
488
489 // Default behavior requires click + release inside bounding box
490 if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
491 flags |= ImGuiButtonFlags_PressedOnDefault_;
492
493 // Default behavior inherited from item flags
494 // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
495 ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags);
496 if (flags & ImGuiButtonFlags_AllowOverlap)
497 item_flags |= ImGuiItemFlags_AllowOverlap;
498 if (flags & ImGuiButtonFlags_Repeat)
499 item_flags |= ImGuiItemFlags_ButtonRepeat;
500
501 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
502 const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;
503 if (flatten_hovered_children)
504 g.HoveredWindow = window;
505
506#ifdef IMGUI_ENABLE_TEST_ENGINE
507 // Alternate registration spot, for when caller didn't use ItemAdd()
508 if (id != 0 && g.LastItemData.ID != id)
509 IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
510#endif
511
512 bool pressed = false;
513 bool hovered = ItemHoverable(bb, id, item_flags);
514
515 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
516 if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
517 if (IsItemHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
518 {
519 hovered = true;
520 SetHoveredID(id);
521 if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
522 {
523 pressed = true;
524 g.DragDropHoldJustPressedId = id;
525 FocusWindow(window);
526 }
527 }
528
529 if (flatten_hovered_children)
530 g.HoveredWindow = backup_hovered_window;
531
532 // Mouse handling
533 const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
534 if (hovered)
535 {
536 // Poll mouse buttons
537 // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
538 // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
539 int mouse_button_clicked = -1;
540 int mouse_button_released = -1;
541 for (int button = 0; button < 3; button++)
542 if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
543 {
544 if (IsMouseClicked(button, owner_id: test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
545 if (IsMouseReleased(button, owner_id: test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
546 }
547
548 // Process initial action
549 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
550 {
551 if (mouse_button_clicked != -1 && g.ActiveId != id)
552 {
553 if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
554 SetKeyOwner(key: MouseButtonToKey(button: mouse_button_clicked), owner_id: id);
555 if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
556 {
557 SetActiveID(id, window);
558 g.ActiveIdMouseButton = mouse_button_clicked;
559 if (!(flags & ImGuiButtonFlags_NoNavFocus))
560 SetFocusID(id, window);
561 FocusWindow(window);
562 }
563 if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
564 {
565 pressed = true;
566 if (flags & ImGuiButtonFlags_NoHoldingActiveId)
567 ClearActiveID();
568 else
569 SetActiveID(id, window); // Hold on ID
570 if (!(flags & ImGuiButtonFlags_NoNavFocus))
571 SetFocusID(id, window);
572 g.ActiveIdMouseButton = mouse_button_clicked;
573 FocusWindow(window);
574 }
575 }
576 if (flags & ImGuiButtonFlags_PressedOnRelease)
577 {
578 if (mouse_button_released != -1)
579 {
580 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
581 if (!has_repeated_at_least_once)
582 pressed = true;
583 if (!(flags & ImGuiButtonFlags_NoNavFocus))
584 SetFocusID(id, window);
585 ClearActiveID();
586 }
587 }
588
589 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
590 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
591 if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
592 if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(button: g.ActiveIdMouseButton, owner_id: test_owner_id, flags: ImGuiInputFlags_Repeat))
593 pressed = true;
594 }
595
596 if (pressed)
597 g.NavDisableHighlight = true;
598 }
599
600 // Gamepad/Keyboard navigation
601 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
602 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
603 if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
604 hovered = true;
605 if (g.NavActivateDownId == id)
606 {
607 bool nav_activated_by_code = (g.NavActivateId == id);
608 bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
609 if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
610 {
611 // Avoid pressing multiple keys from triggering excessive amount of repeat events
612 const ImGuiKeyData* key1 = GetKeyData(key: ImGuiKey_Space);
613 const ImGuiKeyData* key2 = GetKeyData(key: ImGuiKey_Enter);
614 const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
615 const float t1 = ImMax(lhs: ImMax(lhs: key1->DownDuration, rhs: key2->DownDuration), rhs: key3->DownDuration);
616 nav_activated_by_inputs = CalcTypematicRepeatAmount(t0: t1 - g.IO.DeltaTime, t1, repeat_delay: g.IO.KeyRepeatDelay, repeat_rate: g.IO.KeyRepeatRate) > 0;
617 }
618 if (nav_activated_by_code || nav_activated_by_inputs)
619 {
620 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
621 pressed = true;
622 SetActiveID(id, window);
623 g.ActiveIdSource = g.NavInputSource;
624 if (!(flags & ImGuiButtonFlags_NoNavFocus))
625 SetFocusID(id, window);
626 }
627 }
628
629 // Process while held
630 bool held = false;
631 if (g.ActiveId == id)
632 {
633 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
634 {
635 if (g.ActiveIdIsJustActivated)
636 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
637
638 const int mouse_button = g.ActiveIdMouseButton;
639 if (mouse_button == -1)
640 {
641 // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
642 ClearActiveID();
643 }
644 else if (IsMouseDown(button: mouse_button, owner_id: test_owner_id))
645 {
646 held = true;
647 }
648 else
649 {
650 bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
651 bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
652 if ((release_in || release_anywhere) && !g.DragDropActive)
653 {
654 // Report as pressed when releasing the mouse (this is the most common path)
655 bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
656 bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
657 bool is_button_avail_or_owned = TestKeyOwner(key: MouseButtonToKey(button: mouse_button), owner_id: test_owner_id);
658 if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
659 pressed = true;
660 }
661 ClearActiveID();
662 }
663 if (!(flags & ImGuiButtonFlags_NoNavFocus))
664 g.NavDisableHighlight = true;
665 }
666 else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
667 {
668 // When activated using Nav, we hold on the ActiveID until activation button is released
669 if (g.NavActivateDownId != id)
670 ClearActiveID();
671 }
672 if (pressed)
673 g.ActiveIdHasBeenPressedBefore = true;
674 }
675
676 if (out_hovered) *out_hovered = hovered;
677 if (out_held) *out_held = held;
678
679 return pressed;
680}
681
682bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
683{
684 ImGuiWindow* window = GetCurrentWindow();
685 if (window->SkipItems)
686 return false;
687
688 ImGuiContext& g = *GImGui;
689 const ImGuiStyle& style = g.Style;
690 const ImGuiID id = window->GetID(str: label);
691 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
692
693 ImVec2 pos = window->DC.CursorPos;
694 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)
695 pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
696 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);
697
698 const ImRect bb(pos, pos + size);
699 ItemSize(size, text_baseline_y: style.FramePadding.y);
700 if (!ItemAdd(bb, id))
701 return false;
702
703 bool hovered, held;
704 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
705
706 // Render
707 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
708 RenderNavHighlight(bb, id);
709 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: style.FrameRounding);
710
711 if (g.LogEnabled)
712 LogSetNextTextDecoration(prefix: "[", suffix: "]");
713 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);
714
715 // Automatically close popups
716 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
717 // CloseCurrentPopup();
718
719 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
720 return pressed;
721}
722
723bool ImGui::Button(const char* label, const ImVec2& size_arg)
724{
725 return ButtonEx(label, size_arg, flags: ImGuiButtonFlags_None);
726}
727
728// Small buttons fits within text without additional vertical spacing.
729bool ImGui::SmallButton(const char* label)
730{
731 ImGuiContext& g = *GImGui;
732 float backup_padding_y = g.Style.FramePadding.y;
733 g.Style.FramePadding.y = 0.0f;
734 bool pressed = ButtonEx(label, size_arg: ImVec2(0, 0), flags: ImGuiButtonFlags_AlignTextBaseLine);
735 g.Style.FramePadding.y = backup_padding_y;
736 return pressed;
737}
738
739// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
740// 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)
741bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
742{
743 ImGuiContext& g = *GImGui;
744 ImGuiWindow* window = GetCurrentWindow();
745 if (window->SkipItems)
746 return false;
747
748 // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
749 IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
750
751 const ImGuiID id = window->GetID(str: str_id);
752 ImVec2 size = CalcItemSize(size: size_arg, default_w: 0.0f, default_h: 0.0f);
753 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
754 ItemSize(size);
755 if (!ItemAdd(bb, id))
756 return false;
757
758 bool hovered, held;
759 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
760
761 IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
762 return pressed;
763}
764
765bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
766{
767 ImGuiContext& g = *GImGui;
768 ImGuiWindow* window = GetCurrentWindow();
769 if (window->SkipItems)
770 return false;
771
772 const ImGuiID id = window->GetID(str: str_id);
773 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
774 const float default_size = GetFrameHeight();
775 ItemSize(size, text_baseline_y: (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
776 if (!ItemAdd(bb, id))
777 return false;
778
779 bool hovered, held;
780 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
781
782 // Render
783 const ImU32 bg_col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
784 const ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
785 RenderNavHighlight(bb, id);
786 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: bg_col, border: true, rounding: g.Style.FrameRounding);
787 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);
788
789 IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
790 return pressed;
791}
792
793bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
794{
795 float sz = GetFrameHeight();
796 return ArrowButtonEx(str_id, dir, size: ImVec2(sz, sz), flags: ImGuiButtonFlags_None);
797}
798
799// Button to close a window
800bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
801{
802 ImGuiContext& g = *GImGui;
803 ImGuiWindow* window = g.CurrentWindow;
804
805 // 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)
806 // 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?
807 const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
808 ImRect bb_interact = bb;
809 const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
810 if (area_to_visible_ratio < 1.5f)
811 bb_interact.Expand(amount: ImTrunc(v: bb_interact.GetSize() * -0.25f));
812
813 // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
814 // (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).
815 bool is_clipped = !ItemAdd(bb: bb_interact, id);
816
817 bool hovered, held;
818 bool pressed = ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held);
819 if (is_clipped)
820 return pressed;
821
822 // Render
823 // FIXME: Clarify this mess
824 ImU32 col = GetColorU32(idx: held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
825 ImVec2 center = bb.GetCenter();
826 if (hovered)
827 window->DrawList->AddCircleFilled(center, radius: ImMax(lhs: 2.0f, rhs: g.FontSize * 0.5f + 1.0f), col);
828
829 float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
830 ImU32 cross_col = GetColorU32(idx: ImGuiCol_Text);
831 center -= ImVec2(0.5f, 0.5f);
832 window->DrawList->AddLine(p1: center + ImVec2(+cross_extent, +cross_extent), p2: center + ImVec2(-cross_extent, -cross_extent), col: cross_col, thickness: 1.0f);
833 window->DrawList->AddLine(p1: center + ImVec2(+cross_extent, -cross_extent), p2: center + ImVec2(-cross_extent, +cross_extent), col: cross_col, thickness: 1.0f);
834
835 return pressed;
836}
837
838bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
839{
840 ImGuiContext& g = *GImGui;
841 ImGuiWindow* window = g.CurrentWindow;
842
843 ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
844 bool is_clipped = !ItemAdd(bb, id);
845 bool hovered, held;
846 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_None);
847 if (is_clipped)
848 return pressed;
849
850 // Render
851 ImU32 bg_col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
852 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
853 if (hovered || held)
854 window->DrawList->AddCircleFilled(center: bb.GetCenter() + ImVec2(0.0f, -0.5f), radius: g.FontSize * 0.5f + 1.0f, col: bg_col);
855 RenderArrow(draw_list: window->DrawList, pos: bb.Min, col: text_col, dir: window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, scale: 1.0f);
856
857 // Switch to moving the window after mouse is moved beyond the initial drag threshold
858 if (IsItemActive() && IsMouseDragging(button: 0))
859 StartMouseMovingWindow(window);
860
861 return pressed;
862}
863
864ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
865{
866 return window->GetID(str: axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
867}
868
869// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
870ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
871{
872 const ImRect outer_rect = window->Rect();
873 const ImRect inner_rect = window->InnerRect;
874 const float border_size = window->WindowBorderSize;
875 const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
876 IM_ASSERT(scrollbar_size > 0.0f);
877 if (axis == ImGuiAxis_X)
878 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);
879 else
880 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);
881}
882
883void ImGui::Scrollbar(ImGuiAxis axis)
884{
885 ImGuiContext& g = *GImGui;
886 ImGuiWindow* window = g.CurrentWindow;
887 const ImGuiID id = GetWindowScrollbarID(window, axis);
888
889 // Calculate scrollbar bounding box
890 ImRect bb = GetWindowScrollbarRect(window, axis);
891 ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
892 if (axis == ImGuiAxis_X)
893 {
894 rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
895 if (!window->ScrollbarY)
896 rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
897 }
898 else
899 {
900 if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
901 rounding_corners |= ImDrawFlags_RoundCornersTopRight;
902 if (!window->ScrollbarX)
903 rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
904 }
905 float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
906 float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
907 ImS64 scroll = (ImS64)window->Scroll[axis];
908 ScrollbarEx(bb, id, axis, p_scroll_v: &scroll, avail_v: (ImS64)size_avail, contents_v: (ImS64)size_contents, flags: rounding_corners);
909 window->Scroll[axis] = (float)scroll;
910}
911
912// Vertical/Horizontal scrollbar
913// The entire piece of code below is rather confusing because:
914// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
915// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
916// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
917// Still, the code should probably be made simpler..
918bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_avail_v, ImS64 size_contents_v, ImDrawFlags flags)
919{
920 ImGuiContext& g = *GImGui;
921 ImGuiWindow* window = g.CurrentWindow;
922 if (window->SkipItems)
923 return false;
924
925 const float bb_frame_width = bb_frame.GetWidth();
926 const float bb_frame_height = bb_frame.GetHeight();
927 if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
928 return false;
929
930 // 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)
931 float alpha = 1.0f;
932 if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
933 alpha = ImSaturate(f: (bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
934 if (alpha <= 0.0f)
935 return false;
936
937 const ImGuiStyle& style = g.Style;
938 const bool allow_interaction = (alpha >= 1.0f);
939
940 ImRect bb = bb_frame;
941 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)));
942
943 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
944 const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
945
946 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
947 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
948 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.
949 const ImS64 win_size_v = ImMax(lhs: ImMax(lhs: size_contents_v, rhs: size_avail_v), rhs: (ImS64)1);
950 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);
951 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
952
953 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
954 bool held = false;
955 bool hovered = false;
956 ItemAdd(bb: bb_frame, id, NULL, extra_flags: ImGuiItemFlags_NoNav);
957 ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_NoNavFocus);
958
959 const ImS64 scroll_max = ImMax(lhs: (ImS64)1, rhs: size_contents_v - size_avail_v);
960 float scroll_ratio = ImSaturate(f: (float)*p_scroll_v / (float)scroll_max);
961 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
962 if (held && allow_interaction && grab_h_norm < 1.0f)
963 {
964 const float scrollbar_pos_v = bb.Min[axis];
965 const float mouse_pos_v = g.IO.MousePos[axis];
966
967 // Click position in scrollbar normalized space (0.0f->1.0f)
968 const float clicked_v_norm = ImSaturate(f: (mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
969 SetHoveredID(id);
970
971 bool seek_absolute = false;
972 if (g.ActiveIdIsJustActivated)
973 {
974 // On initial click calculate the distance between mouse and the center of the grab
975 seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
976 if (seek_absolute)
977 g.ScrollbarClickDeltaToGrabCenter = 0.0f;
978 else
979 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
980 }
981
982 // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
983 // 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
984 const float scroll_v_norm = ImSaturate(f: (clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
985 *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
986
987 // Update values for rendering
988 scroll_ratio = ImSaturate(f: (float)*p_scroll_v / (float)scroll_max);
989 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
990
991 // Update distance to grab now that we have seeked and saturated
992 if (seek_absolute)
993 g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
994 }
995
996 // Render
997 const ImU32 bg_col = GetColorU32(idx: ImGuiCol_ScrollbarBg);
998 const ImU32 grab_col = GetColorU32(idx: held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha_mul: alpha);
999 window->DrawList->AddRectFilled(p_min: bb_frame.Min, p_max: bb_frame.Max, col: bg_col, rounding: window->WindowRounding, flags);
1000 ImRect grab_rect;
1001 if (axis == ImGuiAxis_X)
1002 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);
1003 else
1004 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);
1005 window->DrawList->AddRectFilled(p_min: grab_rect.Min, p_max: grab_rect.Max, col: grab_col, rounding: style.ScrollbarRounding);
1006
1007 return held;
1008}
1009
1010void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
1011{
1012 ImGuiWindow* window = GetCurrentWindow();
1013 if (window->SkipItems)
1014 return;
1015
1016 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1017 if (border_col.w > 0.0f)
1018 bb.Max += ImVec2(2, 2);
1019 ItemSize(bb);
1020 if (!ItemAdd(bb, id: 0))
1021 return;
1022
1023 if (border_col.w > 0.0f)
1024 {
1025 window->DrawList->AddRect(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(col: border_col), rounding: 0.0f);
1026 window->DrawList->AddImage(user_texture_id, p_min: bb.Min + ImVec2(1, 1), p_max: bb.Max - ImVec2(1, 1), uv_min: uv0, uv_max: uv1, col: GetColorU32(col: tint_col));
1027 }
1028 else
1029 {
1030 window->DrawList->AddImage(user_texture_id, p_min: bb.Min, p_max: bb.Max, uv_min: uv0, uv_max: uv1, col: GetColorU32(col: tint_col));
1031 }
1032}
1033
1034// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
1035// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
1036bool 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)
1037{
1038 ImGuiContext& g = *GImGui;
1039 ImGuiWindow* window = GetCurrentWindow();
1040 if (window->SkipItems)
1041 return false;
1042
1043 const ImVec2 padding = g.Style.FramePadding;
1044 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1045 ItemSize(bb);
1046 if (!ItemAdd(bb, id))
1047 return false;
1048
1049 bool hovered, held;
1050 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
1051
1052 // Render
1053 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1054 RenderNavHighlight(bb, id);
1055 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));
1056 if (bg_col.w > 0.0f)
1057 window->DrawList->AddRectFilled(p_min: bb.Min + padding, p_max: bb.Max - padding, col: GetColorU32(col: bg_col));
1058 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));
1059
1060 return pressed;
1061}
1062
1063// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
1064bool 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)
1065{
1066 ImGuiContext& g = *GImGui;
1067 ImGuiWindow* window = g.CurrentWindow;
1068 if (window->SkipItems)
1069 return false;
1070
1071 return ImageButtonEx(id: window->GetID(str: str_id), texture_id: user_texture_id, image_size, uv0, uv1, bg_col, tint_col);
1072}
1073
1074#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1075// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
1076// - 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)
1077// - new ImageButton() always use style.FramePadding Old ImageButton() had an override argument.
1078// If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions.
1079bool 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)
1080{
1081 ImGuiContext& g = *GImGui;
1082 ImGuiWindow* window = g.CurrentWindow;
1083 if (window->SkipItems)
1084 return false;
1085
1086 // Default to using texture ID as ID. User can still push string/integer prefixes.
1087 PushID(ptr_id: (void*)(intptr_t)user_texture_id);
1088 const ImGuiID id = window->GetID(str: "#image");
1089 PopID();
1090
1091 if (frame_padding >= 0)
1092 PushStyleVar(idx: ImGuiStyleVar_FramePadding, val: ImVec2((float)frame_padding, (float)frame_padding));
1093 bool ret = ImageButtonEx(id, texture_id: user_texture_id, image_size: size, uv0, uv1, bg_col, tint_col);
1094 if (frame_padding >= 0)
1095 PopStyleVar();
1096 return ret;
1097}
1098#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1099
1100bool ImGui::Checkbox(const char* label, bool* v)
1101{
1102 ImGuiWindow* window = GetCurrentWindow();
1103 if (window->SkipItems)
1104 return false;
1105
1106 ImGuiContext& g = *GImGui;
1107 const ImGuiStyle& style = g.Style;
1108 const ImGuiID id = window->GetID(str: label);
1109 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
1110
1111 const float square_sz = GetFrameHeight();
1112 const ImVec2 pos = window->DC.CursorPos;
1113 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));
1114 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
1115 if (!ItemAdd(bb: total_bb, id))
1116 {
1117 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1118 return false;
1119 }
1120
1121 bool hovered, held;
1122 bool pressed = ButtonBehavior(bb: total_bb, id, out_hovered: &hovered, out_held: &held);
1123 if (pressed)
1124 {
1125 *v = !(*v);
1126 MarkItemEdited(id);
1127 }
1128
1129 const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1130 RenderNavHighlight(bb: total_bb, id);
1131 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);
1132 ImU32 check_col = GetColorU32(idx: ImGuiCol_CheckMark);
1133 bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
1134 if (mixed_value)
1135 {
1136 // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1137 // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1138 ImVec2 pad(ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 3.6f)));
1139 window->DrawList->AddRectFilled(p_min: check_bb.Min + pad, p_max: check_bb.Max - pad, col: check_col, rounding: style.FrameRounding);
1140 }
1141 else if (*v)
1142 {
1143 const float pad = ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 6.0f));
1144 RenderCheckMark(draw_list: window->DrawList, pos: check_bb.Min + ImVec2(pad, pad), col: check_col, sz: square_sz - pad * 2.0f);
1145 }
1146
1147 ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1148 if (g.LogEnabled)
1149 LogRenderedText(ref_pos: &label_pos, text: mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1150 if (label_size.x > 0.0f)
1151 RenderText(pos: label_pos, text: label);
1152
1153 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1154 return pressed;
1155}
1156
1157template<typename T>
1158bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1159{
1160 bool all_on = (*flags & flags_value) == flags_value;
1161 bool any_on = (*flags & flags_value) != 0;
1162 bool pressed;
1163 if (!all_on && any_on)
1164 {
1165 ImGuiContext& g = *GImGui;
1166 g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
1167 pressed = Checkbox(label, v: &all_on);
1168 }
1169 else
1170 {
1171 pressed = Checkbox(label, v: &all_on);
1172
1173 }
1174 if (pressed)
1175 {
1176 if (all_on)
1177 *flags |= flags_value;
1178 else
1179 *flags &= ~flags_value;
1180 }
1181 return pressed;
1182}
1183
1184bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1185{
1186 return CheckboxFlagsT(label, flags, flags_value);
1187}
1188
1189bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1190{
1191 return CheckboxFlagsT(label, flags, flags_value);
1192}
1193
1194bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1195{
1196 return CheckboxFlagsT(label, flags, flags_value);
1197}
1198
1199bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1200{
1201 return CheckboxFlagsT(label, flags, flags_value);
1202}
1203
1204bool ImGui::RadioButton(const char* label, bool active)
1205{
1206 ImGuiWindow* window = GetCurrentWindow();
1207 if (window->SkipItems)
1208 return false;
1209
1210 ImGuiContext& g = *GImGui;
1211 const ImGuiStyle& style = g.Style;
1212 const ImGuiID id = window->GetID(str: label);
1213 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
1214
1215 const float square_sz = GetFrameHeight();
1216 const ImVec2 pos = window->DC.CursorPos;
1217 const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1218 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));
1219 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
1220 if (!ItemAdd(bb: total_bb, id))
1221 return false;
1222
1223 ImVec2 center = check_bb.GetCenter();
1224 center.x = IM_ROUND(center.x);
1225 center.y = IM_ROUND(center.y);
1226 const float radius = (square_sz - 1.0f) * 0.5f;
1227
1228 bool hovered, held;
1229 bool pressed = ButtonBehavior(bb: total_bb, id, out_hovered: &hovered, out_held: &held);
1230 if (pressed)
1231 MarkItemEdited(id);
1232
1233 RenderNavHighlight(bb: total_bb, id);
1234 const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1235 window->DrawList->AddCircleFilled(center, radius, col: GetColorU32(idx: (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segments: num_segment);
1236 if (active)
1237 {
1238 const float pad = ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 6.0f));
1239 window->DrawList->AddCircleFilled(center, radius: radius - pad, col: GetColorU32(idx: ImGuiCol_CheckMark));
1240 }
1241
1242 if (style.FrameBorderSize > 0.0f)
1243 {
1244 window->DrawList->AddCircle(center: center + ImVec2(1, 1), radius, col: GetColorU32(idx: ImGuiCol_BorderShadow), num_segments: num_segment, thickness: style.FrameBorderSize);
1245 window->DrawList->AddCircle(center, radius, col: GetColorU32(idx: ImGuiCol_Border), num_segments: num_segment, thickness: style.FrameBorderSize);
1246 }
1247
1248 ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1249 if (g.LogEnabled)
1250 LogRenderedText(ref_pos: &label_pos, text: active ? "(x)" : "( )");
1251 if (label_size.x > 0.0f)
1252 RenderText(pos: label_pos, text: label);
1253
1254 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1255 return pressed;
1256}
1257
1258// 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..
1259bool ImGui::RadioButton(const char* label, int* v, int v_button)
1260{
1261 const bool pressed = RadioButton(label, active: *v == v_button);
1262 if (pressed)
1263 *v = v_button;
1264 return pressed;
1265}
1266
1267// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1268void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1269{
1270 ImGuiWindow* window = GetCurrentWindow();
1271 if (window->SkipItems)
1272 return;
1273
1274 ImGuiContext& g = *GImGui;
1275 const ImGuiStyle& style = g.Style;
1276
1277 ImVec2 pos = window->DC.CursorPos;
1278 ImVec2 size = CalcItemSize(size: size_arg, default_w: CalcItemWidth(), default_h: g.FontSize + style.FramePadding.y * 2.0f);
1279 ImRect bb(pos, pos + size);
1280 ItemSize(size, text_baseline_y: style.FramePadding.y);
1281 if (!ItemAdd(bb, id: 0))
1282 return;
1283
1284 // Render
1285 fraction = ImSaturate(f: fraction);
1286 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: GetColorU32(idx: ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding);
1287 bb.Expand(amount: ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1288 const ImVec2 fill_br = ImVec2(ImLerp(a: bb.Min.x, b: bb.Max.x, t: fraction), bb.Max.y);
1289 RenderRectFilledRangeH(draw_list: window->DrawList, rect: bb, col: GetColorU32(idx: ImGuiCol_PlotHistogram), x_start_norm: 0.0f, x_end_norm: fraction, rounding: style.FrameRounding);
1290
1291 // Default displaying the fraction as percentage string, but user can override it
1292 char overlay_buf[32];
1293 if (!overlay)
1294 {
1295 ImFormatString(buf: overlay_buf, IM_ARRAYSIZE(overlay_buf), fmt: "%.0f%%", fraction * 100 + 0.01f);
1296 overlay = overlay_buf;
1297 }
1298
1299 ImVec2 overlay_size = CalcTextSize(text: overlay, NULL);
1300 if (overlay_size.x > 0.0f)
1301 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);
1302}
1303
1304void ImGui::Bullet()
1305{
1306 ImGuiWindow* window = GetCurrentWindow();
1307 if (window->SkipItems)
1308 return;
1309
1310 ImGuiContext& g = *GImGui;
1311 const ImGuiStyle& style = g.Style;
1312 const float line_height = ImMax(lhs: ImMin(lhs: window->DC.CurrLineSize.y, rhs: g.FontSize + style.FramePadding.y * 2), rhs: g.FontSize);
1313 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1314 ItemSize(bb);
1315 if (!ItemAdd(bb, id: 0))
1316 {
1317 SameLine(offset_from_start_x: 0, spacing: style.FramePadding.x * 2);
1318 return;
1319 }
1320
1321 // Render and stay on same line
1322 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
1323 RenderBullet(draw_list: window->DrawList, pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), col: text_col);
1324 SameLine(offset_from_start_x: 0, spacing: style.FramePadding.x * 2.0f);
1325}
1326
1327//-------------------------------------------------------------------------
1328// [SECTION] Widgets: Low-level Layout helpers
1329//-------------------------------------------------------------------------
1330// - Spacing()
1331// - Dummy()
1332// - NewLine()
1333// - AlignTextToFramePadding()
1334// - SeparatorEx() [Internal]
1335// - Separator()
1336// - SplitterBehavior() [Internal]
1337// - ShrinkWidths() [Internal]
1338//-------------------------------------------------------------------------
1339
1340void ImGui::Spacing()
1341{
1342 ImGuiWindow* window = GetCurrentWindow();
1343 if (window->SkipItems)
1344 return;
1345 ItemSize(size: ImVec2(0, 0));
1346}
1347
1348void ImGui::Dummy(const ImVec2& size)
1349{
1350 ImGuiWindow* window = GetCurrentWindow();
1351 if (window->SkipItems)
1352 return;
1353
1354 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1355 ItemSize(size);
1356 ItemAdd(bb, id: 0);
1357}
1358
1359void ImGui::NewLine()
1360{
1361 ImGuiWindow* window = GetCurrentWindow();
1362 if (window->SkipItems)
1363 return;
1364
1365 ImGuiContext& g = *GImGui;
1366 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1367 window->DC.LayoutType = ImGuiLayoutType_Vertical;
1368 window->DC.IsSameLine = false;
1369 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.
1370 ItemSize(size: ImVec2(0, 0));
1371 else
1372 ItemSize(size: ImVec2(0.0f, g.FontSize));
1373 window->DC.LayoutType = backup_layout_type;
1374}
1375
1376void ImGui::AlignTextToFramePadding()
1377{
1378 ImGuiWindow* window = GetCurrentWindow();
1379 if (window->SkipItems)
1380 return;
1381
1382 ImGuiContext& g = *GImGui;
1383 window->DC.CurrLineSize.y = ImMax(lhs: window->DC.CurrLineSize.y, rhs: g.FontSize + g.Style.FramePadding.y * 2);
1384 window->DC.CurrLineTextBaseOffset = ImMax(lhs: window->DC.CurrLineTextBaseOffset, rhs: g.Style.FramePadding.y);
1385}
1386
1387// Horizontal/vertical separating line
1388// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1389// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
1390void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1391{
1392 ImGuiWindow* window = GetCurrentWindow();
1393 if (window->SkipItems)
1394 return;
1395
1396 ImGuiContext& g = *GImGui;
1397 IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected
1398 IM_ASSERT(thickness > 0.0f);
1399
1400 if (flags & ImGuiSeparatorFlags_Vertical)
1401 {
1402 // Vertical separator, for menu bars (use current line height).
1403 float y1 = window->DC.CursorPos.y;
1404 float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1405 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1406 ItemSize(size: ImVec2(thickness, 0.0f));
1407 if (!ItemAdd(bb, id: 0))
1408 return;
1409
1410 // Draw
1411 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator));
1412 if (g.LogEnabled)
1413 LogText(fmt: " |");
1414 }
1415 else if (flags & ImGuiSeparatorFlags_Horizontal)
1416 {
1417 // Horizontal Separator
1418 float x1 = window->DC.CursorPos.x;
1419 float x2 = window->WorkRect.Max.x;
1420
1421 // Preserve legacy behavior inside Columns()
1422 // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1423 // We currently don't need to provide the same feature for tables because tables naturally have border features.
1424 ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1425 if (columns)
1426 {
1427 x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
1428 x2 = window->Pos.x + window->Size.x;
1429 PushColumnsBackground();
1430 }
1431
1432 // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1433 // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1434 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.
1435 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1436 ItemSize(size: ImVec2(0.0f, thickness_for_layout));
1437
1438 if (ItemAdd(bb, id: 0))
1439 {
1440 // Draw
1441 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator));
1442 if (g.LogEnabled)
1443 LogRenderedText(ref_pos: &bb.Min, text: "--------------------------------\n");
1444
1445 }
1446 if (columns)
1447 {
1448 PopColumnsBackground();
1449 columns->LineMinY = window->DC.CursorPos.y;
1450 }
1451 }
1452}
1453
1454void ImGui::Separator()
1455{
1456 ImGuiContext& g = *GImGui;
1457 ImGuiWindow* window = g.CurrentWindow;
1458 if (window->SkipItems)
1459 return;
1460
1461 // Those flags should eventually be configurable by the user
1462 // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
1463 ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1464
1465 // Only applies to legacy Columns() api as they relied on Separator() a lot.
1466 if (window->DC.CurrentColumns)
1467 flags |= ImGuiSeparatorFlags_SpanAllColumns;
1468
1469 SeparatorEx(flags, thickness: 1.0f);
1470}
1471
1472void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1473{
1474 ImGuiContext& g = *GImGui;
1475 ImGuiWindow* window = g.CurrentWindow;
1476 ImGuiStyle& style = g.Style;
1477
1478 const ImVec2 label_size = CalcTextSize(text: label, text_end: label_end, hide_text_after_double_hash: false);
1479 const ImVec2 pos = window->DC.CursorPos;
1480 const ImVec2 padding = style.SeparatorTextPadding;
1481
1482 const float separator_thickness = style.SeparatorTextBorderSize;
1483 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));
1484 const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1485 const float text_baseline_y = ImTrunc(f: (bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f));
1486 ItemSize(size: min_size, text_baseline_y);
1487 if (!ItemAdd(bb, id))
1488 return;
1489
1490 const float sep1_x1 = pos.x;
1491 const float sep2_x2 = bb.Max.x;
1492 const float seps_y = ImTrunc(f: (bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
1493
1494 const float label_avail_w = ImMax(lhs: 0.0f, rhs: sep2_x2 - sep1_x1 - padding.x * 2.0f);
1495 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
1496
1497 // This allows using SameLine() to position something in the 'extra_w'
1498 window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1499
1500 const ImU32 separator_col = GetColorU32(idx: ImGuiCol_Separator);
1501 if (label_size.x > 0.0f)
1502 {
1503 const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1504 const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1505 if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1506 window->DrawList->AddLine(p1: ImVec2(sep1_x1, seps_y), p2: ImVec2(sep1_x2, seps_y), col: separator_col, thickness: separator_thickness);
1507 if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1508 window->DrawList->AddLine(p1: ImVec2(sep2_x1, seps_y), p2: ImVec2(sep2_x2, seps_y), col: separator_col, thickness: separator_thickness);
1509 if (g.LogEnabled)
1510 LogSetNextTextDecoration(prefix: "---", NULL);
1511 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);
1512 }
1513 else
1514 {
1515 if (g.LogEnabled)
1516 LogText(fmt: "---");
1517 if (separator_thickness > 0.0f)
1518 window->DrawList->AddLine(p1: ImVec2(sep1_x1, seps_y), p2: ImVec2(sep2_x2, seps_y), col: separator_col, thickness: separator_thickness);
1519 }
1520}
1521
1522void ImGui::SeparatorText(const char* label)
1523{
1524 ImGuiWindow* window = GetCurrentWindow();
1525 if (window->SkipItems)
1526 return;
1527
1528 // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1529 // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1530 // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
1531 // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1532 // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1533 // and then we can turn this into a format function.
1534 SeparatorTextEx(id: 0, label, label_end: FindRenderedTextEnd(text: label), extra_w: 0.0f);
1535}
1536
1537// 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.
1538bool 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)
1539{
1540 ImGuiContext& g = *GImGui;
1541 ImGuiWindow* window = g.CurrentWindow;
1542
1543 if (!ItemAdd(bb, id, NULL, extra_flags: ImGuiItemFlags_NoNav))
1544 return false;
1545
1546 // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1547 // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1548 // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1549 ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1550#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1551 button_flags |= ImGuiButtonFlags_AllowOverlap;
1552#endif
1553
1554 bool hovered, held;
1555 ImRect bb_interact = bb;
1556 bb_interact.Expand(amount: axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1557 ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held, flags: button_flags);
1558 if (hovered)
1559 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1560
1561 if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1562 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1563
1564 ImRect bb_render = bb;
1565 if (held)
1566 {
1567 float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1568
1569 // Minimum pane size
1570 float size_1_maximum_delta = ImMax(lhs: 0.0f, rhs: *size1 - min_size1);
1571 float size_2_maximum_delta = ImMax(lhs: 0.0f, rhs: *size2 - min_size2);
1572 if (mouse_delta < -size_1_maximum_delta)
1573 mouse_delta = -size_1_maximum_delta;
1574 if (mouse_delta > size_2_maximum_delta)
1575 mouse_delta = size_2_maximum_delta;
1576
1577 // Apply resize
1578 if (mouse_delta != 0.0f)
1579 {
1580 *size1 = ImMax(lhs: *size1 + mouse_delta, rhs: min_size1);
1581 *size2 = ImMax(lhs: *size2 - mouse_delta, rhs: min_size2);
1582 bb_render.Translate(d: (axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1583 MarkItemEdited(id);
1584 }
1585 }
1586
1587 // Render at new position
1588 if (bg_col & IM_COL32_A_MASK)
1589 window->DrawList->AddRectFilled(p_min: bb_render.Min, p_max: bb_render.Max, col: bg_col, rounding: 0.0f);
1590 const ImU32 col = GetColorU32(idx: held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1591 window->DrawList->AddRectFilled(p_min: bb_render.Min, p_max: bb_render.Max, col, rounding: 0.0f);
1592
1593 return held;
1594}
1595
1596static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1597{
1598 const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1599 const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1600 if (int d = (int)(b->Width - a->Width))
1601 return d;
1602 return (b->Index - a->Index);
1603}
1604
1605// Shrink excess width from a set of item, by removing width from the larger items first.
1606// Set items Width to -1.0f to disable shrinking this item.
1607void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1608{
1609 if (count == 1)
1610 {
1611 if (items[0].Width >= 0.0f)
1612 items[0].Width = ImMax(lhs: items[0].Width - width_excess, rhs: 1.0f);
1613 return;
1614 }
1615 ImQsort(base: items, count: (size_t)count, size_of_element: sizeof(ImGuiShrinkWidthItem), compare_func: ShrinkWidthItemComparer);
1616 int count_same_width = 1;
1617 while (width_excess > 0.0f && count_same_width < count)
1618 {
1619 while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1620 count_same_width++;
1621 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);
1622 if (max_width_to_remove_per_item <= 0.0f)
1623 break;
1624 float width_to_remove_per_item = ImMin(lhs: width_excess / count_same_width, rhs: max_width_to_remove_per_item);
1625 for (int item_n = 0; item_n < count_same_width; item_n++)
1626 items[item_n].Width -= width_to_remove_per_item;
1627 width_excess -= width_to_remove_per_item * count_same_width;
1628 }
1629
1630 // Round width and redistribute remainder
1631 // 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.
1632 width_excess = 0.0f;
1633 for (int n = 0; n < count; n++)
1634 {
1635 float width_rounded = ImTrunc(f: items[n].Width);
1636 width_excess += items[n].Width - width_rounded;
1637 items[n].Width = width_rounded;
1638 }
1639 while (width_excess > 0.0f)
1640 for (int n = 0; n < count && width_excess > 0.0f; n++)
1641 {
1642 float width_to_add = ImMin(lhs: items[n].InitialWidth - items[n].Width, rhs: 1.0f);
1643 items[n].Width += width_to_add;
1644 width_excess -= width_to_add;
1645 }
1646}
1647
1648//-------------------------------------------------------------------------
1649// [SECTION] Widgets: ComboBox
1650//-------------------------------------------------------------------------
1651// - CalcMaxPopupHeightFromItemCount() [Internal]
1652// - BeginCombo()
1653// - BeginComboPopup() [Internal]
1654// - EndCombo()
1655// - BeginComboPreview() [Internal]
1656// - EndComboPreview() [Internal]
1657// - Combo()
1658//-------------------------------------------------------------------------
1659
1660static float CalcMaxPopupHeightFromItemCount(int items_count)
1661{
1662 ImGuiContext& g = *GImGui;
1663 if (items_count <= 0)
1664 return FLT_MAX;
1665 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1666}
1667
1668bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1669{
1670 ImGuiContext& g = *GImGui;
1671 ImGuiWindow* window = GetCurrentWindow();
1672
1673 ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1674 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1675 if (window->SkipItems)
1676 return false;
1677
1678 const ImGuiStyle& style = g.Style;
1679 const ImGuiID id = window->GetID(str: label);
1680 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1681 if (flags & ImGuiComboFlags_WidthFitPreview)
1682 IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | ImGuiComboFlags_CustomPreview)) == 0);
1683
1684 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1685 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
1686 const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(text: preview_value, NULL, hide_text_after_double_hash: true).x : 0.0f;
1687 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
1688 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1689 const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1690 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
1691 if (!ItemAdd(bb: total_bb, id, nav_bb: &bb))
1692 return false;
1693
1694 // Open on click
1695 bool hovered, held;
1696 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held);
1697 const ImGuiID popup_id = ImHashStr(data: "##ComboPopup", data_size: 0, seed: id);
1698 bool popup_open = IsPopupOpen(id: popup_id, popup_flags: ImGuiPopupFlags_None);
1699 if (pressed && !popup_open)
1700 {
1701 OpenPopupEx(id: popup_id, popup_flags: ImGuiPopupFlags_None);
1702 popup_open = true;
1703 }
1704
1705 // Render shape
1706 const ImU32 frame_col = GetColorU32(idx: hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1707 const float value_x2 = ImMax(lhs: bb.Min.x, rhs: bb.Max.x - arrow_size);
1708 RenderNavHighlight(bb, id);
1709 if (!(flags & ImGuiComboFlags_NoPreview))
1710 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);
1711 if (!(flags & ImGuiComboFlags_NoArrowButton))
1712 {
1713 ImU32 bg_col = GetColorU32(idx: (popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1714 ImU32 text_col = GetColorU32(idx: ImGuiCol_Text);
1715 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);
1716 if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1717 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);
1718 }
1719 RenderFrameBorder(p_min: bb.Min, p_max: bb.Max, rounding: style.FrameRounding);
1720
1721 // Custom preview
1722 if (flags & ImGuiComboFlags_CustomPreview)
1723 {
1724 g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1725 IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1726 preview_value = NULL;
1727 }
1728
1729 // Render preview and label
1730 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1731 {
1732 if (g.LogEnabled)
1733 LogSetNextTextDecoration(prefix: "{", suffix: "}");
1734 RenderTextClipped(pos_min: bb.Min + style.FramePadding, pos_max: ImVec2(value_x2, bb.Max.y), text: preview_value, NULL, NULL);
1735 }
1736 if (label_size.x > 0)
1737 RenderText(pos: ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), text: label);
1738
1739 if (!popup_open)
1740 return false;
1741
1742 g.NextWindowData.Flags = backup_next_window_data_flags;
1743 return BeginComboPopup(popup_id, bb, flags);
1744}
1745
1746bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1747{
1748 ImGuiContext& g = *GImGui;
1749 if (!IsPopupOpen(id: popup_id, popup_flags: ImGuiPopupFlags_None))
1750 {
1751 g.NextWindowData.ClearFlags();
1752 return false;
1753 }
1754
1755 // Set popup size
1756 float w = bb.GetWidth();
1757 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1758 {
1759 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(lhs: g.NextWindowData.SizeConstraintRect.Min.x, rhs: w);
1760 }
1761 else
1762 {
1763 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1764 flags |= ImGuiComboFlags_HeightRegular;
1765 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1766 int popup_max_height_in_items = -1;
1767 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
1768 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
1769 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
1770 ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
1771 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
1772 constraint_min.x = w;
1773 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
1774 constraint_max.y = CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items);
1775 SetNextWindowSizeConstraints(size_min: constraint_min, size_max: constraint_max);
1776 }
1777
1778 // This is essentially a specialized version of BeginPopupEx()
1779 char name[16];
1780 ImFormatString(buf: name, IM_ARRAYSIZE(name), fmt: "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
1781
1782 // Set position given a custom constraint (peak into expected window size so we can position it)
1783 // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1784 // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1785 if (ImGuiWindow* popup_window = FindWindowByName(name))
1786 if (popup_window->WasActive)
1787 {
1788 // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1789 ImVec2 size_expected = CalcWindowNextAutoFitSize(window: popup_window);
1790 popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1791 ImRect r_outer = GetPopupAllowedExtentRect(window: popup_window);
1792 ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos: bb.GetBL(), size: size_expected, last_dir: &popup_window->AutoPosLastDirection, r_outer, r_avoid: bb, policy: ImGuiPopupPositionPolicy_ComboBox);
1793 SetNextWindowPos(pos);
1794 }
1795
1796 // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1797 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1798 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text
1799 bool ret = Begin(name, NULL, flags: window_flags);
1800 PopStyleVar();
1801 if (!ret)
1802 {
1803 EndPopup();
1804 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1805 return false;
1806 }
1807 return true;
1808}
1809
1810void ImGui::EndCombo()
1811{
1812 EndPopup();
1813}
1814
1815// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1816// (Experimental, see GitHub issues: #1658, #4168)
1817bool ImGui::BeginComboPreview()
1818{
1819 ImGuiContext& g = *GImGui;
1820 ImGuiWindow* window = g.CurrentWindow;
1821 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1822
1823 if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
1824 return false;
1825 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?
1826 if (!window->ClipRect.Overlaps(r: preview_data->PreviewRect)) // Narrower test (optional)
1827 return false;
1828
1829 // FIXME: This could be contained in a PushWorkRect() api
1830 preview_data->BackupCursorPos = window->DC.CursorPos;
1831 preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1832 preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1833 preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1834 preview_data->BackupLayout = window->DC.LayoutType;
1835 window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1836 window->DC.CursorMaxPos = window->DC.CursorPos;
1837 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1838 window->DC.IsSameLine = false;
1839 PushClipRect(clip_rect_min: preview_data->PreviewRect.Min, clip_rect_max: preview_data->PreviewRect.Max, intersect_with_current_clip_rect: true);
1840
1841 return true;
1842}
1843
1844void ImGui::EndComboPreview()
1845{
1846 ImGuiContext& g = *GImGui;
1847 ImGuiWindow* window = g.CurrentWindow;
1848 ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1849
1850 // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
1851 ImDrawList* draw_list = window->DrawList;
1852 if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
1853 if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1854 {
1855 draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
1856 draw_list->_TryMergeDrawCmds();
1857 }
1858 PopClipRect();
1859 window->DC.CursorPos = preview_data->BackupCursorPos;
1860 window->DC.CursorMaxPos = ImMax(lhs: window->DC.CursorMaxPos, rhs: preview_data->BackupCursorMaxPos);
1861 window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1862 window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1863 window->DC.LayoutType = preview_data->BackupLayout;
1864 window->DC.IsSameLine = false;
1865 preview_data->PreviewRect = ImRect();
1866}
1867
1868// Getter for the old Combo() API: const char*[]
1869static const char* Items_ArrayGetter(void* data, int idx)
1870{
1871 const char* const* items = (const char* const*)data;
1872 return items[idx];
1873}
1874
1875// Getter for the old Combo() API: "item1\0item2\0item3\0"
1876static const char* Items_SingleStringGetter(void* data, int idx)
1877{
1878 const char* items_separated_by_zeros = (const char*)data;
1879 int items_count = 0;
1880 const char* p = items_separated_by_zeros;
1881 while (*p)
1882 {
1883 if (idx == items_count)
1884 break;
1885 p += strlen(s: p) + 1;
1886 items_count++;
1887 }
1888 return *p ? p : NULL;
1889}
1890
1891// Old API, prefer using BeginCombo() nowadays if you can.
1892bool 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)
1893{
1894 ImGuiContext& g = *GImGui;
1895
1896 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1897 const char* preview_value = NULL;
1898 if (*current_item >= 0 && *current_item < items_count)
1899 preview_value = getter(user_data, *current_item);
1900
1901 // 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.
1902 if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1903 SetNextWindowSizeConstraints(size_min: ImVec2(0, 0), size_max: ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items)));
1904
1905 if (!BeginCombo(label, preview_value, flags: ImGuiComboFlags_None))
1906 return false;
1907
1908 // Display items
1909 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1910 bool value_changed = false;
1911 for (int i = 0; i < items_count; i++)
1912 {
1913 const char* item_text = getter(user_data, i);
1914 if (item_text == NULL)
1915 item_text = "*Unknown item*";
1916
1917 PushID(int_id: i);
1918 const bool item_selected = (i == *current_item);
1919 if (Selectable(label: item_text, selected: item_selected) && *current_item != i)
1920 {
1921 value_changed = true;
1922 *current_item = i;
1923 }
1924 if (item_selected)
1925 SetItemDefaultFocus();
1926 PopID();
1927 }
1928
1929 EndCombo();
1930
1931 if (value_changed)
1932 MarkItemEdited(id: g.LastItemData.ID);
1933
1934 return value_changed;
1935}
1936
1937// Combo box helper allowing to pass an array of strings.
1938bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1939{
1940 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);
1941 return value_changed;
1942}
1943
1944// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
1945bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1946{
1947 int items_count = 0;
1948 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
1949 while (*p)
1950 {
1951 p += strlen(s: p) + 1;
1952 items_count++;
1953 }
1954 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);
1955 return value_changed;
1956}
1957
1958#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1959
1960struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
1961static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
1962{
1963 ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
1964 const char* s = NULL;
1965 data->OldCallback(data->UserData, idx, &s);
1966 return s;
1967}
1968
1969bool 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)
1970{
1971 ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { .UserData: user_data, .OldCallback: old_getter };
1972 return ListBox(label, current_item, getter: ImGuiGetNameFromIndexOldToNewCallback, user_data: &old_to_new_data, items_count, height_in_items);
1973}
1974bool 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)
1975{
1976 ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { .UserData: user_data, .OldCallback: old_getter };
1977 return Combo(label, current_item, getter: ImGuiGetNameFromIndexOldToNewCallback, user_data: &old_to_new_data, items_count, popup_max_height_in_items);
1978}
1979
1980#endif
1981
1982//-------------------------------------------------------------------------
1983// [SECTION] Data Type and Data Formatting Helpers [Internal]
1984//-------------------------------------------------------------------------
1985// - DataTypeGetInfo()
1986// - DataTypeFormatString()
1987// - DataTypeApplyOp()
1988// - DataTypeApplyOpFromText()
1989// - DataTypeCompare()
1990// - DataTypeClamp()
1991// - GetMinimumStepAtDecimalPrecision
1992// - RoundScalarWithFormat<>()
1993//-------------------------------------------------------------------------
1994
1995static const ImGuiDataTypeInfo GDataTypeInfo[] =
1996{
1997 { .Size: sizeof(char), .Name: "S8", .PrintFmt: "%d", .ScanFmt: "%d" }, // ImGuiDataType_S8
1998 { .Size: sizeof(unsigned char), .Name: "U8", .PrintFmt: "%u", .ScanFmt: "%u" },
1999 { .Size: sizeof(short), .Name: "S16", .PrintFmt: "%d", .ScanFmt: "%d" }, // ImGuiDataType_S16
2000 { .Size: sizeof(unsigned short), .Name: "U16", .PrintFmt: "%u", .ScanFmt: "%u" },
2001 { .Size: sizeof(int), .Name: "S32", .PrintFmt: "%d", .ScanFmt: "%d" }, // ImGuiDataType_S32
2002 { .Size: sizeof(unsigned int), .Name: "U32", .PrintFmt: "%u", .ScanFmt: "%u" },
2003#ifdef _MSC_VER
2004 { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64
2005 { sizeof(ImU64), "U64", "%I64u","%I64u" },
2006#else
2007 { .Size: sizeof(ImS64), .Name: "S64", .PrintFmt: "%lld", .ScanFmt: "%lld" }, // ImGuiDataType_S64
2008 { .Size: sizeof(ImU64), .Name: "U64", .PrintFmt: "%llu", .ScanFmt: "%llu" },
2009#endif
2010 { .Size: sizeof(float), .Name: "float", .PrintFmt: "%.3f",.ScanFmt: "%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg)
2011 { .Size: sizeof(double), .Name: "double",.PrintFmt: "%f", .ScanFmt: "%lf" }, // ImGuiDataType_Double
2012};
2013IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
2014
2015const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
2016{
2017 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2018 return &GDataTypeInfo[data_type];
2019}
2020
2021int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
2022{
2023 // Signedness doesn't matter when pushing integer arguments
2024 if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
2025 return ImFormatString(buf, buf_size, fmt: format, *(const ImU32*)p_data);
2026 if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2027 return ImFormatString(buf, buf_size, fmt: format, *(const ImU64*)p_data);
2028 if (data_type == ImGuiDataType_Float)
2029 return ImFormatString(buf, buf_size, fmt: format, *(const float*)p_data);
2030 if (data_type == ImGuiDataType_Double)
2031 return ImFormatString(buf, buf_size, fmt: format, *(const double*)p_data);
2032 if (data_type == ImGuiDataType_S8)
2033 return ImFormatString(buf, buf_size, fmt: format, *(const ImS8*)p_data);
2034 if (data_type == ImGuiDataType_U8)
2035 return ImFormatString(buf, buf_size, fmt: format, *(const ImU8*)p_data);
2036 if (data_type == ImGuiDataType_S16)
2037 return ImFormatString(buf, buf_size, fmt: format, *(const ImS16*)p_data);
2038 if (data_type == ImGuiDataType_U16)
2039 return ImFormatString(buf, buf_size, fmt: format, *(const ImU16*)p_data);
2040 IM_ASSERT(0);
2041 return 0;
2042}
2043
2044void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
2045{
2046 IM_ASSERT(op == '+' || op == '-');
2047 switch (data_type)
2048 {
2049 case ImGuiDataType_S8:
2050 if (op == '+') { *(ImS8*)output = ImAddClampOverflow(a: *(const ImS8*)arg1, b: *(const ImS8*)arg2, mn: IM_S8_MIN, mx: IM_S8_MAX); }
2051 if (op == '-') { *(ImS8*)output = ImSubClampOverflow(a: *(const ImS8*)arg1, b: *(const ImS8*)arg2, mn: IM_S8_MIN, mx: IM_S8_MAX); }
2052 return;
2053 case ImGuiDataType_U8:
2054 if (op == '+') { *(ImU8*)output = ImAddClampOverflow(a: *(const ImU8*)arg1, b: *(const ImU8*)arg2, mn: IM_U8_MIN, mx: IM_U8_MAX); }
2055 if (op == '-') { *(ImU8*)output = ImSubClampOverflow(a: *(const ImU8*)arg1, b: *(const ImU8*)arg2, mn: IM_U8_MIN, mx: IM_U8_MAX); }
2056 return;
2057 case ImGuiDataType_S16:
2058 if (op == '+') { *(ImS16*)output = ImAddClampOverflow(a: *(const ImS16*)arg1, b: *(const ImS16*)arg2, mn: IM_S16_MIN, mx: IM_S16_MAX); }
2059 if (op == '-') { *(ImS16*)output = ImSubClampOverflow(a: *(const ImS16*)arg1, b: *(const ImS16*)arg2, mn: IM_S16_MIN, mx: IM_S16_MAX); }
2060 return;
2061 case ImGuiDataType_U16:
2062 if (op == '+') { *(ImU16*)output = ImAddClampOverflow(a: *(const ImU16*)arg1, b: *(const ImU16*)arg2, mn: IM_U16_MIN, mx: IM_U16_MAX); }
2063 if (op == '-') { *(ImU16*)output = ImSubClampOverflow(a: *(const ImU16*)arg1, b: *(const ImU16*)arg2, mn: IM_U16_MIN, mx: IM_U16_MAX); }
2064 return;
2065 case ImGuiDataType_S32:
2066 if (op == '+') { *(ImS32*)output = ImAddClampOverflow(a: *(const ImS32*)arg1, b: *(const ImS32*)arg2, mn: IM_S32_MIN, mx: IM_S32_MAX); }
2067 if (op == '-') { *(ImS32*)output = ImSubClampOverflow(a: *(const ImS32*)arg1, b: *(const ImS32*)arg2, mn: IM_S32_MIN, mx: IM_S32_MAX); }
2068 return;
2069 case ImGuiDataType_U32:
2070 if (op == '+') { *(ImU32*)output = ImAddClampOverflow(a: *(const ImU32*)arg1, b: *(const ImU32*)arg2, mn: IM_U32_MIN, mx: IM_U32_MAX); }
2071 if (op == '-') { *(ImU32*)output = ImSubClampOverflow(a: *(const ImU32*)arg1, b: *(const ImU32*)arg2, mn: IM_U32_MIN, mx: IM_U32_MAX); }
2072 return;
2073 case ImGuiDataType_S64:
2074 if (op == '+') { *(ImS64*)output = ImAddClampOverflow(a: *(const ImS64*)arg1, b: *(const ImS64*)arg2, mn: IM_S64_MIN, mx: IM_S64_MAX); }
2075 if (op == '-') { *(ImS64*)output = ImSubClampOverflow(a: *(const ImS64*)arg1, b: *(const ImS64*)arg2, mn: IM_S64_MIN, mx: IM_S64_MAX); }
2076 return;
2077 case ImGuiDataType_U64:
2078 if (op == '+') { *(ImU64*)output = ImAddClampOverflow(a: *(const ImU64*)arg1, b: *(const ImU64*)arg2, mn: IM_U64_MIN, mx: IM_U64_MAX); }
2079 if (op == '-') { *(ImU64*)output = ImSubClampOverflow(a: *(const ImU64*)arg1, b: *(const ImU64*)arg2, mn: IM_U64_MIN, mx: IM_U64_MAX); }
2080 return;
2081 case ImGuiDataType_Float:
2082 if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
2083 if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
2084 return;
2085 case ImGuiDataType_Double:
2086 if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
2087 if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
2088 return;
2089 case ImGuiDataType_COUNT: break;
2090 }
2091 IM_ASSERT(0);
2092}
2093
2094// User can input math operators (e.g. +100) to edit a numerical values.
2095// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
2096bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format)
2097{
2098 while (ImCharIsBlankA(c: *buf))
2099 buf++;
2100 if (!buf[0])
2101 return false;
2102
2103 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2104 const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2105 ImGuiDataTypeTempStorage data_backup;
2106 memcpy(dest: &data_backup, src: p_data, n: type_info->Size);
2107
2108 // Sanitize format
2109 // - 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
2110 // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
2111 char format_sanitized[32];
2112 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2113 format = type_info->ScanFmt;
2114 else
2115 format = ImParseFormatSanitizeForScanning(fmt_in: format, fmt_out: format_sanitized, IM_ARRAYSIZE(format_sanitized));
2116
2117 // Small types need a 32-bit buffer to receive the result from scanf()
2118 int v32 = 0;
2119 if (sscanf(s: buf, format: format, type_info->Size >= 4 ? p_data : &v32) < 1)
2120 return false;
2121 if (type_info->Size < 4)
2122 {
2123 if (data_type == ImGuiDataType_S8)
2124 *(ImS8*)p_data = (ImS8)ImClamp(v: v32, mn: (int)IM_S8_MIN, mx: (int)IM_S8_MAX);
2125 else if (data_type == ImGuiDataType_U8)
2126 *(ImU8*)p_data = (ImU8)ImClamp(v: v32, mn: (int)IM_U8_MIN, mx: (int)IM_U8_MAX);
2127 else if (data_type == ImGuiDataType_S16)
2128 *(ImS16*)p_data = (ImS16)ImClamp(v: v32, mn: (int)IM_S16_MIN, mx: (int)IM_S16_MAX);
2129 else if (data_type == ImGuiDataType_U16)
2130 *(ImU16*)p_data = (ImU16)ImClamp(v: v32, mn: (int)IM_U16_MIN, mx: (int)IM_U16_MAX);
2131 else
2132 IM_ASSERT(0);
2133 }
2134
2135 return memcmp(s1: &data_backup, s2: p_data, n: type_info->Size) != 0;
2136}
2137
2138template<typename T>
2139static int DataTypeCompareT(const T* lhs, const T* rhs)
2140{
2141 if (*lhs < *rhs) return -1;
2142 if (*lhs > *rhs) return +1;
2143 return 0;
2144}
2145
2146int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2147{
2148 switch (data_type)
2149 {
2150 case ImGuiDataType_S8: return DataTypeCompareT<ImS8 >(lhs: (const ImS8* )arg_1, rhs: (const ImS8* )arg_2);
2151 case ImGuiDataType_U8: return DataTypeCompareT<ImU8 >(lhs: (const ImU8* )arg_1, rhs: (const ImU8* )arg_2);
2152 case ImGuiDataType_S16: return DataTypeCompareT<ImS16 >(lhs: (const ImS16* )arg_1, rhs: (const ImS16* )arg_2);
2153 case ImGuiDataType_U16: return DataTypeCompareT<ImU16 >(lhs: (const ImU16* )arg_1, rhs: (const ImU16* )arg_2);
2154 case ImGuiDataType_S32: return DataTypeCompareT<ImS32 >(lhs: (const ImS32* )arg_1, rhs: (const ImS32* )arg_2);
2155 case ImGuiDataType_U32: return DataTypeCompareT<ImU32 >(lhs: (const ImU32* )arg_1, rhs: (const ImU32* )arg_2);
2156 case ImGuiDataType_S64: return DataTypeCompareT<ImS64 >(lhs: (const ImS64* )arg_1, rhs: (const ImS64* )arg_2);
2157 case ImGuiDataType_U64: return DataTypeCompareT<ImU64 >(lhs: (const ImU64* )arg_1, rhs: (const ImU64* )arg_2);
2158 case ImGuiDataType_Float: return DataTypeCompareT<float >(lhs: (const float* )arg_1, rhs: (const float* )arg_2);
2159 case ImGuiDataType_Double: return DataTypeCompareT<double>(lhs: (const double*)arg_1, rhs: (const double*)arg_2);
2160 case ImGuiDataType_COUNT: break;
2161 }
2162 IM_ASSERT(0);
2163 return 0;
2164}
2165
2166template<typename T>
2167static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2168{
2169 // Clamp, both sides are optional, return true if modified
2170 if (v_min && *v < *v_min) { *v = *v_min; return true; }
2171 if (v_max && *v > *v_max) { *v = *v_max; return true; }
2172 return false;
2173}
2174
2175bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2176{
2177 switch (data_type)
2178 {
2179 case ImGuiDataType_S8: return DataTypeClampT<ImS8 >(v: (ImS8* )p_data, v_min: (const ImS8* )p_min, v_max: (const ImS8* )p_max);
2180 case ImGuiDataType_U8: return DataTypeClampT<ImU8 >(v: (ImU8* )p_data, v_min: (const ImU8* )p_min, v_max: (const ImU8* )p_max);
2181 case ImGuiDataType_S16: return DataTypeClampT<ImS16 >(v: (ImS16* )p_data, v_min: (const ImS16* )p_min, v_max: (const ImS16* )p_max);
2182 case ImGuiDataType_U16: return DataTypeClampT<ImU16 >(v: (ImU16* )p_data, v_min: (const ImU16* )p_min, v_max: (const ImU16* )p_max);
2183 case ImGuiDataType_S32: return DataTypeClampT<ImS32 >(v: (ImS32* )p_data, v_min: (const ImS32* )p_min, v_max: (const ImS32* )p_max);
2184 case ImGuiDataType_U32: return DataTypeClampT<ImU32 >(v: (ImU32* )p_data, v_min: (const ImU32* )p_min, v_max: (const ImU32* )p_max);
2185 case ImGuiDataType_S64: return DataTypeClampT<ImS64 >(v: (ImS64* )p_data, v_min: (const ImS64* )p_min, v_max: (const ImS64* )p_max);
2186 case ImGuiDataType_U64: return DataTypeClampT<ImU64 >(v: (ImU64* )p_data, v_min: (const ImU64* )p_min, v_max: (const ImU64* )p_max);
2187 case ImGuiDataType_Float: return DataTypeClampT<float >(v: (float* )p_data, v_min: (const float* )p_min, v_max: (const float* )p_max);
2188 case ImGuiDataType_Double: return DataTypeClampT<double>(v: (double*)p_data, v_min: (const double*)p_min, v_max: (const double*)p_max);
2189 case ImGuiDataType_COUNT: break;
2190 }
2191 IM_ASSERT(0);
2192 return false;
2193}
2194
2195static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2196{
2197 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 };
2198 if (decimal_precision < 0)
2199 return FLT_MIN;
2200 return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(x: 10.0f, y: (float)-decimal_precision);
2201}
2202
2203template<typename TYPE>
2204TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2205{
2206 IM_UNUSED(data_type);
2207 IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2208 const char* fmt_start = ImParseFormatFindStart(format);
2209 if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2210 return v;
2211
2212 // Sanitize format
2213 char fmt_sanitized[32];
2214 ImParseFormatSanitizeForPrinting(fmt_in: fmt_start, fmt_out: fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
2215 fmt_start = fmt_sanitized;
2216
2217 // Format value with our rounding, and read back
2218 char v_str[64];
2219 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
2220 const char* p = v_str;
2221 while (*p == ' ')
2222 p++;
2223 v = (TYPE)ImAtof(p);
2224
2225 return v;
2226}
2227
2228//-------------------------------------------------------------------------
2229// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2230//-------------------------------------------------------------------------
2231// - DragBehaviorT<>() [Internal]
2232// - DragBehavior() [Internal]
2233// - DragScalar()
2234// - DragScalarN()
2235// - DragFloat()
2236// - DragFloat2()
2237// - DragFloat3()
2238// - DragFloat4()
2239// - DragFloatRange2()
2240// - DragInt()
2241// - DragInt2()
2242// - DragInt3()
2243// - DragInt4()
2244// - DragIntRange2()
2245//-------------------------------------------------------------------------
2246
2247// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2248template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2249bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2250{
2251 ImGuiContext& g = *GImGui;
2252 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2253 const bool is_clamped = (v_min < v_max);
2254 const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2255 const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2256
2257 // Default tweak speed
2258 if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
2259 v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2260
2261 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2262 float adjust_delta = 0.0f;
2263 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(button: 0, lock_threshold: g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2264 {
2265 adjust_delta = g.IO.MouseDelta[axis];
2266 if (g.IO.KeyAlt)
2267 adjust_delta *= 1.0f / 100.0f;
2268 if (g.IO.KeyShift)
2269 adjust_delta *= 10.0f;
2270 }
2271 else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
2272 {
2273 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, default_value: 3) : 0;
2274 const bool tweak_slow = IsKeyDown(key: (g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
2275 const bool tweak_fast = IsKeyDown(key: (g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
2276 const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f;
2277 adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
2278 v_speed = ImMax(lhs: v_speed, rhs: GetMinimumStepAtDecimalPrecision(decimal_precision));
2279 }
2280 adjust_delta *= v_speed;
2281
2282 // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2283 if (axis == ImGuiAxis_Y)
2284 adjust_delta = -adjust_delta;
2285
2286 // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2287 if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2288 adjust_delta /= (float)(v_max - v_min);
2289
2290 // Clear current value on activation
2291 // 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.
2292 bool is_just_activated = g.ActiveIdIsJustActivated;
2293 bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
2294 if (is_just_activated || is_already_past_limits_and_pushing_outward)
2295 {
2296 g.DragCurrentAccum = 0.0f;
2297 g.DragCurrentAccumDirty = false;
2298 }
2299 else if (adjust_delta != 0.0f)
2300 {
2301 g.DragCurrentAccum += adjust_delta;
2302 g.DragCurrentAccumDirty = true;
2303 }
2304
2305 if (!g.DragCurrentAccumDirty)
2306 return false;
2307
2308 TYPE v_cur = *v;
2309 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2310
2311 float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2312 const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2313 if (is_logarithmic)
2314 {
2315 // 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.
2316 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, default_value: 3) : 1;
2317 logarithmic_zero_epsilon = ImPow(x: 0.1f, y: (float)decimal_precision);
2318
2319 // Convert to parametric space, apply delta, convert back
2320 float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2321 float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2322 v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2323 v_old_ref_for_accum_remainder = v_old_parametric;
2324 }
2325 else
2326 {
2327 v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2328 }
2329
2330 // Round to user desired precision based on format string
2331 if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2332 v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
2333
2334 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2335 g.DragCurrentAccumDirty = false;
2336 if (is_logarithmic)
2337 {
2338 // Convert to parametric space, apply delta, convert back
2339 float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2340 g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2341 }
2342 else
2343 {
2344 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2345 }
2346
2347 // Lose zero sign for float/double
2348 if (v_cur == (TYPE)-0)
2349 v_cur = (TYPE)0;
2350
2351 // Clamp values (+ handle overflow/wrap-around for integer types)
2352 if (*v != v_cur && is_clamped)
2353 {
2354 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2355 v_cur = v_min;
2356 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2357 v_cur = v_max;
2358 }
2359
2360 // Apply result
2361 if (*v == v_cur)
2362 return false;
2363 *v = v_cur;
2364 return true;
2365}
2366
2367bool 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)
2368{
2369 // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2370 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.");
2371
2372 ImGuiContext& g = *GImGui;
2373 if (g.ActiveId == id)
2374 {
2375 // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
2376 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2377 ClearActiveID();
2378 else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2379 ClearActiveID();
2380 }
2381 if (g.ActiveId != id)
2382 return false;
2383 if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2384 return false;
2385
2386 switch (data_type)
2387 {
2388 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; }
2389 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; }
2390 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; }
2391 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; }
2392 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);
2393 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);
2394 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);
2395 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);
2396 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);
2397 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);
2398 case ImGuiDataType_COUNT: break;
2399 }
2400 IM_ASSERT(0);
2401 return false;
2402}
2403
2404// 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.
2405// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
2406bool 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)
2407{
2408 ImGuiWindow* window = GetCurrentWindow();
2409 if (window->SkipItems)
2410 return false;
2411
2412 ImGuiContext& g = *GImGui;
2413 const ImGuiStyle& style = g.Style;
2414 const ImGuiID id = window->GetID(str: label);
2415 const float w = CalcItemWidth();
2416
2417 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
2418 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2419 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));
2420
2421 const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2422 ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y);
2423 if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb, extra_flags: temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
2424 return false;
2425
2426 // Default format string when passing NULL
2427 if (format == NULL)
2428 format = DataTypeGetInfo(data_type)->PrintFmt;
2429
2430 const bool hovered = ItemHoverable(bb: frame_bb, id, item_flags: g.LastItemData.InFlags);
2431 bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2432 if (!temp_input_is_active)
2433 {
2434 // Tabbing or CTRL-clicking on Drag turns it into an InputText
2435 const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
2436 const bool clicked = hovered && IsMouseClicked(button: 0, owner_id: id);
2437 const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(key: ImGuiKey_MouseLeft, owner_id: id));
2438 const bool make_active = (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id);
2439 if (make_active && (clicked || double_clicked))
2440 SetKeyOwner(key: ImGuiKey_MouseLeft, owner_id: id);
2441 if (make_active && temp_input_allowed)
2442 if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
2443 temp_input_is_active = true;
2444
2445 // (Optional) simple click (without moving) turns Drag into an InputText
2446 if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2447 if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(button: 0, lock_threshold: g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2448 {
2449 g.NavActivateId = id;
2450 g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
2451 temp_input_is_active = true;
2452 }
2453
2454 if (make_active && !temp_input_is_active)
2455 {
2456 SetActiveID(id, window);
2457 SetFocusID(id, window);
2458 FocusWindow(window);
2459 g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2460 }
2461 }
2462
2463 if (temp_input_is_active)
2464 {
2465 // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
2466 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);
2467 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);
2468 }
2469
2470 // Draw frame
2471 const ImU32 frame_col = GetColorU32(idx: g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2472 RenderNavHighlight(bb: frame_bb, id);
2473 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: frame_col, border: true, rounding: style.FrameRounding);
2474
2475 // Drag behavior
2476 const bool value_changed = DragBehavior(id, data_type, p_v: p_data, v_speed, p_min, p_max, format, flags);
2477 if (value_changed)
2478 MarkItemEdited(id);
2479
2480 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2481 char value_buf[64];
2482 const char* value_buf_end = value_buf + DataTypeFormatString(buf: value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
2483 if (g.LogEnabled)
2484 LogSetNextTextDecoration(prefix: "{", suffix: "}");
2485 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));
2486
2487 if (label_size.x > 0.0f)
2488 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
2489
2490 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
2491 return value_changed;
2492}
2493
2494bool 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)
2495{
2496 ImGuiWindow* window = GetCurrentWindow();
2497 if (window->SkipItems)
2498 return false;
2499
2500 ImGuiContext& g = *GImGui;
2501 bool value_changed = false;
2502 BeginGroup();
2503 PushID(str_id: label);
2504 PushMultiItemsWidths(components, width_full: CalcItemWidth());
2505 size_t type_size = GDataTypeInfo[data_type].Size;
2506 for (int i = 0; i < components; i++)
2507 {
2508 PushID(int_id: i);
2509 if (i > 0)
2510 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2511 value_changed |= DragScalar(label: "", data_type, p_data, v_speed, p_min, p_max, format, flags);
2512 PopID();
2513 PopItemWidth();
2514 p_data = (void*)((char*)p_data + type_size);
2515 }
2516 PopID();
2517
2518 const char* label_end = FindRenderedTextEnd(text: label);
2519 if (label != label_end)
2520 {
2521 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2522 TextEx(text: label, text_end: label_end);
2523 }
2524
2525 EndGroup();
2526 return value_changed;
2527}
2528
2529bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2530{
2531 return DragScalar(label, data_type: ImGuiDataType_Float, p_data: v, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2532}
2533
2534bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2535{
2536 return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 2, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2537}
2538
2539bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2540{
2541 return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 3, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2542}
2543
2544bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2545{
2546 return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 4, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2547}
2548
2549// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2550bool 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)
2551{
2552 ImGuiWindow* window = GetCurrentWindow();
2553 if (window->SkipItems)
2554 return false;
2555
2556 ImGuiContext& g = *GImGui;
2557 PushID(str_id: label);
2558 BeginGroup();
2559 PushMultiItemsWidths(components: 2, width_full: CalcItemWidth());
2560
2561 float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2562 float min_max = (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max);
2563 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2564 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);
2565 PopItemWidth();
2566 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2567
2568 float max_min = (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min);
2569 float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2570 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2571 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);
2572 PopItemWidth();
2573 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2574
2575 TextEx(text: label, text_end: FindRenderedTextEnd(text: label));
2576 EndGroup();
2577 PopID();
2578
2579 return value_changed;
2580}
2581
2582// NB: v_speed is float to allow adjusting the drag speed with more precision
2583bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2584{
2585 return DragScalar(label, data_type: ImGuiDataType_S32, p_data: v, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2586}
2587
2588bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2589{
2590 return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 2, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2591}
2592
2593bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2594{
2595 return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 3, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2596}
2597
2598bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2599{
2600 return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 4, v_speed, p_min: &v_min, p_max: &v_max, format, flags);
2601}
2602
2603// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2604bool 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)
2605{
2606 ImGuiWindow* window = GetCurrentWindow();
2607 if (window->SkipItems)
2608 return false;
2609
2610 ImGuiContext& g = *GImGui;
2611 PushID(str_id: label);
2612 BeginGroup();
2613 PushMultiItemsWidths(components: 2, width_full: CalcItemWidth());
2614
2615 int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2616 int min_max = (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max);
2617 ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2618 bool value_changed = DragInt(label: "##min", v: v_current_min, v_speed, v_min: min_min, v_max: min_max, format, flags: min_flags);
2619 PopItemWidth();
2620 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2621
2622 int max_min = (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min);
2623 int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2624 ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2625 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);
2626 PopItemWidth();
2627 SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x);
2628
2629 TextEx(text: label, text_end: FindRenderedTextEnd(text: label));
2630 EndGroup();
2631 PopID();
2632
2633 return value_changed;
2634}
2635
2636//-------------------------------------------------------------------------
2637// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2638//-------------------------------------------------------------------------
2639// - ScaleRatioFromValueT<> [Internal]
2640// - ScaleValueFromRatioT<> [Internal]
2641// - SliderBehaviorT<>() [Internal]
2642// - SliderBehavior() [Internal]
2643// - SliderScalar()
2644// - SliderScalarN()
2645// - SliderFloat()
2646// - SliderFloat2()
2647// - SliderFloat3()
2648// - SliderFloat4()
2649// - SliderAngle()
2650// - SliderInt()
2651// - SliderInt2()
2652// - SliderInt3()
2653// - SliderInt4()
2654// - VSliderScalar()
2655// - VSliderFloat()
2656// - VSliderInt()
2657//-------------------------------------------------------------------------
2658
2659// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2660template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2661float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2662{
2663 if (v_min == v_max)
2664 return 0.0f;
2665 IM_UNUSED(data_type);
2666
2667 const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2668 if (is_logarithmic)
2669 {
2670 bool flipped = v_max < v_min;
2671
2672 if (flipped) // Handle the case where the range is backwards
2673 ImSwap(v_min, v_max);
2674
2675 // Fudge min/max to avoid getting close to log(0)
2676 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2677 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2678
2679 // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2680 if ((v_min == 0.0f) && (v_max < 0.0f))
2681 v_min_fudged = -logarithmic_zero_epsilon;
2682 else if ((v_max == 0.0f) && (v_min < 0.0f))
2683 v_max_fudged = -logarithmic_zero_epsilon;
2684
2685 float result;
2686 if (v_clamped <= v_min_fudged)
2687 result = 0.0f; // Workaround for values that are in-range but below our fudge
2688 else if (v_clamped >= v_max_fudged)
2689 result = 1.0f; // Workaround for values that are in-range but above our fudge
2690 else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
2691 {
2692 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)
2693 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2694 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2695 if (v == 0.0f)
2696 result = zero_point_center; // Special case for exactly zero
2697 else if (v < 0.0f)
2698 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
2699 else
2700 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));
2701 }
2702 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2703 result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
2704 else
2705 result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
2706
2707 return flipped ? (1.0f - result) : result;
2708 }
2709 else
2710 {
2711 // Linear slider
2712 return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
2713 }
2714}
2715
2716// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
2717template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2718TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2719{
2720 // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
2721 // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
2722 if (t <= 0.0f || v_min == v_max)
2723 return v_min;
2724 if (t >= 1.0f)
2725 return v_max;
2726
2727 TYPE result = (TYPE)0;
2728 if (is_logarithmic)
2729 {
2730 // Fudge min/max to avoid getting silly results close to zero
2731 FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2732 FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2733
2734 const bool flipped = v_max < v_min; // Check if range is "backwards"
2735 if (flipped)
2736 ImSwap(v_min_fudged, v_max_fudged);
2737
2738 // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2739 if ((v_max == 0.0f) && (v_min < 0.0f))
2740 v_max_fudged = -logarithmic_zero_epsilon;
2741
2742 float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
2743
2744 if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
2745 {
2746 float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs(x: (float)v_max - (float)v_min); // The zero point in parametric space
2747 float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2748 float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2749 if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
2750 result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
2751 else if (t_with_flip < zero_point_center)
2752 result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
2753 else
2754 result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
2755 }
2756 else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2757 result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
2758 else
2759 result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)<