1// dear imgui, v1.66b
2// (widgets code)
3
4/*
5
6Index of this file:
7
8// [SECTION] Forward Declarations
9// [SECTION] Widgets: Text, etc.
10// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
11// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
12// [SECTION] Widgets: ComboBox
13// [SECTION] Data Type and Data Formatting Helpers
14// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
15// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
16// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
17// [SECTION] Widgets: InputText, InputTextMultiline
18// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
19// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
20// [SECTION] Widgets: Selectable
21// [SECTION] Widgets: ListBox
22// [SECTION] Widgets: PlotLines, PlotHistogram
23// [SECTION] Widgets: Value helpers
24// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
25
26*/
27
28#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
29#define _CRT_SECURE_NO_WARNINGS
30#endif
31
32#include "imgui.h"
33#ifndef IMGUI_DEFINE_MATH_OPERATORS
34#define IMGUI_DEFINE_MATH_OPERATORS
35#endif
36#include "imgui_internal.h"
37
38#include <ctype.h> // toupper, isprint
39#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
40#include <stddef.h> // intptr_t
41#else
42#include <stdint.h> // intptr_t
43#endif
44
45// Visual Studio warnings
46#ifdef _MSC_VER
47#pragma warning (disable: 4127) // condition expression is constant
48#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
49#endif
50
51// Clang/GCC warnings with -Weverything
52#ifdef __clang__
53#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.
54#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
55#elif defined(__GNUC__)
56#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
57#if __GNUC__ >= 8
58#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
59#endif
60#endif
61
62//-------------------------------------------------------------------------
63// Data
64//-------------------------------------------------------------------------
65
66// Those MIN/MAX values are not define because we need to point to them
67static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
68static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
69static const ImU32 IM_U32_MIN = 0;
70static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
71#ifdef LLONG_MIN
72static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
73static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
74#else
75static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
76static const ImS64 IM_S64_MAX = 9223372036854775807LL;
77#endif
78static const ImU64 IM_U64_MIN = 0;
79#ifdef ULLONG_MAX
80static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
81#else
82static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
83#endif
84
85//-------------------------------------------------------------------------
86// [SECTION] Forward Declarations
87//-------------------------------------------------------------------------
88
89// Data Type helpers
90static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
91static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
92static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
93
94// For InputTextEx()
95static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
96static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
97static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
98
99//-------------------------------------------------------------------------
100// [SECTION] Widgets: Text, etc.
101//-------------------------------------------------------------------------
102// - TextUnformatted()
103// - Text()
104// - TextV()
105// - TextColored()
106// - TextColoredV()
107// - TextDisabled()
108// - TextDisabledV()
109// - TextWrapped()
110// - TextWrappedV()
111// - LabelText()
112// - LabelTextV()
113// - BulletText()
114// - BulletTextV()
115//-------------------------------------------------------------------------
116
117void ImGui::TextUnformatted(const char* text, const char* text_end)
118{
119 ImGuiWindow* window = GetCurrentWindow();
120 if (window->SkipItems)
121 return;
122
123 ImGuiContext& g = *GImGui;
124 IM_ASSERT(text != NULL);
125 const char* text_begin = text;
126 if (text_end == NULL)
127 text_end = text + strlen(s: text); // FIXME-OPT
128
129 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
130 const float wrap_pos_x = window->DC.TextWrapPos;
131 const bool wrap_enabled = wrap_pos_x >= 0.0f;
132 if (text_end - text > 2000 && !wrap_enabled)
133 {
134 // Long text!
135 // Perform manual coarse clipping to optimize for long multi-line text
136 // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
137 // - 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.
138 // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
139 const char* line = text;
140 const float line_height = GetTextLineHeight();
141 const ImRect clip_rect = window->ClipRect;
142 ImVec2 text_size(0,0);
143
144 if (text_pos.y <= clip_rect.Max.y)
145 {
146 ImVec2 pos = text_pos;
147
148 // Lines to skip (can't skip when logging text)
149 if (!g.LogEnabled)
150 {
151 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
152 if (lines_skippable > 0)
153 {
154 int lines_skipped = 0;
155 while (line < text_end && lines_skipped < lines_skippable)
156 {
157 const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line);
158 if (!line_end)
159 line_end = text_end;
160 line = line_end + 1;
161 lines_skipped++;
162 }
163 pos.y += lines_skipped * line_height;
164 }
165 }
166
167 // Lines to render
168 if (line < text_end)
169 {
170 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
171 while (line < text_end)
172 {
173 if (IsClippedEx(bb: line_rect, id: 0, clip_even_when_logged: false))
174 break;
175
176 const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line);
177 if (!line_end)
178 line_end = text_end;
179 const ImVec2 line_size = CalcTextSize(text: line, text_end: line_end, hide_text_after_double_hash: false);
180 text_size.x = ImMax(lhs: text_size.x, rhs: line_size.x);
181 RenderText(pos, text: line, text_end: line_end, hide_text_after_hash: false);
182 line = line_end + 1;
183 line_rect.Min.y += line_height;
184 line_rect.Max.y += line_height;
185 pos.y += line_height;
186 }
187
188 // Count remaining lines
189 int lines_skipped = 0;
190 while (line < text_end)
191 {
192 const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line);
193 if (!line_end)
194 line_end = text_end;
195 line = line_end + 1;
196 lines_skipped++;
197 }
198 pos.y += lines_skipped * line_height;
199 }
200
201 text_size.y += (pos - text_pos).y;
202 }
203
204 ImRect bb(text_pos, text_pos + text_size);
205 ItemSize(bb);
206 ItemAdd(bb, id: 0);
207 }
208 else
209 {
210 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(pos: window->DC.CursorPos, wrap_pos_x) : 0.0f;
211 const ImVec2 text_size = CalcTextSize(text: text_begin, text_end, hide_text_after_double_hash: false, wrap_width);
212
213 // Account of baseline offset
214 ImRect bb(text_pos, text_pos + text_size);
215 ItemSize(size: text_size);
216 if (!ItemAdd(bb, id: 0))
217 return;
218
219 // Render (we don't hide text after ## in this end-user function)
220 RenderTextWrapped(pos: bb.Min, text: text_begin, text_end, wrap_width);
221 }
222}
223
224void ImGui::Text(const char* fmt, ...)
225{
226 va_list args;
227 va_start(args, fmt);
228 TextV(fmt, args);
229 va_end(args);
230}
231
232void ImGui::TextV(const char* fmt, va_list args)
233{
234 ImGuiWindow* window = GetCurrentWindow();
235 if (window->SkipItems)
236 return;
237
238 ImGuiContext& g = *GImGui;
239 const char* text_end = g.TempBuffer + ImFormatStringV(buf: g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
240 TextUnformatted(text: g.TempBuffer, text_end);
241}
242
243void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
244{
245 va_list args;
246 va_start(args, fmt);
247 TextColoredV(col, fmt, args);
248 va_end(args);
249}
250
251void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
252{
253 PushStyleColor(idx: ImGuiCol_Text, col);
254 TextV(fmt, args);
255 PopStyleColor();
256}
257
258void ImGui::TextDisabled(const char* fmt, ...)
259{
260 va_list args;
261 va_start(args, fmt);
262 TextDisabledV(fmt, args);
263 va_end(args);
264}
265
266void ImGui::TextDisabledV(const char* fmt, va_list args)
267{
268 PushStyleColor(idx: ImGuiCol_Text, col: GImGui->Style.Colors[ImGuiCol_TextDisabled]);
269 TextV(fmt, args);
270 PopStyleColor();
271}
272
273void ImGui::TextWrapped(const char* fmt, ...)
274{
275 va_list args;
276 va_start(args, fmt);
277 TextWrappedV(fmt, args);
278 va_end(args);
279}
280
281void ImGui::TextWrappedV(const char* fmt, va_list args)
282{
283 bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
284 if (need_wrap) PushTextWrapPos(wrap_pos_x: 0.0f);
285 TextV(fmt, args);
286 if (need_wrap) PopTextWrapPos();
287}
288
289void ImGui::LabelText(const char* label, const char* fmt, ...)
290{
291 va_list args;
292 va_start(args, fmt);
293 LabelTextV(label, fmt, args);
294 va_end(args);
295}
296
297// Add a label+text combo aligned to other label+value widgets
298void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
299{
300 ImGuiWindow* window = GetCurrentWindow();
301 if (window->SkipItems)
302 return;
303
304 ImGuiContext& g = *GImGui;
305 const ImGuiStyle& style = g.Style;
306 const float w = CalcItemWidth();
307
308 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
309 const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
310 const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
311 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
312 if (!ItemAdd(bb: total_bb, id: 0))
313 return;
314
315 // Render
316 const char* value_text_begin = &g.TempBuffer[0];
317 const char* value_text_end = value_text_begin + ImFormatStringV(buf: g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
318 RenderTextClipped(pos_min: value_bb.Min, pos_max: value_bb.Max, text: value_text_begin, text_end: value_text_end, NULL, align: ImVec2(0.0f,0.5f));
319 if (label_size.x > 0.0f)
320 RenderText(pos: ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), text: label);
321}
322
323void ImGui::BulletText(const char* fmt, ...)
324{
325 va_list args;
326 va_start(args, fmt);
327 BulletTextV(fmt, args);
328 va_end(args);
329}
330
331// Text with a little bullet aligned to the typical tree node.
332void ImGui::BulletTextV(const char* fmt, va_list args)
333{
334 ImGuiWindow* window = GetCurrentWindow();
335 if (window->SkipItems)
336 return;
337
338 ImGuiContext& g = *GImGui;
339 const ImGuiStyle& style = g.Style;
340
341 const char* text_begin = g.TempBuffer;
342 const char* text_end = text_begin + ImFormatStringV(buf: g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
343 const ImVec2 label_size = CalcTextSize(text: text_begin, text_end, hide_text_after_double_hash: false);
344 const float text_base_offset_y = ImMax(lhs: 0.0f, rhs: window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
345 const float line_height = ImMax(lhs: ImMin(lhs: window->DC.CurrentLineSize.y, rhs: g.FontSize + g.Style.FramePadding.y*2), rhs: g.FontSize);
346 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(lhs: line_height, rhs: label_size.y))); // Empty text doesn't add padding
347 ItemSize(bb);
348 if (!ItemAdd(bb, id: 0))
349 return;
350
351 // Render
352 RenderBullet(pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
353 RenderText(pos: bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text: text_begin, text_end, hide_text_after_hash: false);
354}
355
356//-------------------------------------------------------------------------
357// [SECTION] Widgets: Main
358//-------------------------------------------------------------------------
359// - ButtonBehavior() [Internal]
360// - Button()
361// - SmallButton()
362// - InvisibleButton()
363// - ArrowButton()
364// - CloseButton() [Internal]
365// - CollapseButton() [Internal]
366// - Scrollbar() [Internal]
367// - Image()
368// - ImageButton()
369// - Checkbox()
370// - CheckboxFlags()
371// - RadioButton()
372// - ProgressBar()
373// - Bullet()
374//-------------------------------------------------------------------------
375
376bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
377{
378 ImGuiContext& g = *GImGui;
379 ImGuiWindow* window = GetCurrentWindow();
380
381 if (flags & ImGuiButtonFlags_Disabled)
382 {
383 if (out_hovered) *out_hovered = false;
384 if (out_held) *out_held = false;
385 if (g.ActiveId == id) ClearActiveID();
386 return false;
387 }
388
389 // Default behavior requires click+release on same spot
390 if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
391 flags |= ImGuiButtonFlags_PressedOnClickRelease;
392
393 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
394 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
395 g.HoveredWindow = window;
396
397 bool pressed = false;
398 bool hovered = ItemHoverable(bb, id);
399
400 // Drag source doesn't report as hovered
401 if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
402 hovered = false;
403
404 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
405 if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
406 if (IsItemHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
407 {
408 hovered = true;
409 SetHoveredID(id);
410 if (CalcTypematicPressedRepeatAmount(t: g.HoveredIdTimer + 0.0001f, t_prev: g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, repeat_delay: 0.01f, repeat_rate: 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
411 {
412 pressed = true;
413 FocusWindow(window);
414 }
415 }
416
417 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
418 g.HoveredWindow = backup_hovered_window;
419
420 // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
421 if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
422 hovered = false;
423
424 // Mouse
425 if (hovered)
426 {
427 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
428 {
429 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
430 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
431 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
432 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
433 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
434 // FIXME-NAV: We don't honor those different behaviors.
435 if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
436 {
437 SetActiveID(id, window);
438 if (!(flags & ImGuiButtonFlags_NoNavFocus))
439 SetFocusID(id, window);
440 FocusWindow(window);
441 }
442 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
443 {
444 pressed = true;
445 if (flags & ImGuiButtonFlags_NoHoldingActiveID)
446 ClearActiveID();
447 else
448 SetActiveID(id, window); // Hold on ID
449 FocusWindow(window);
450 }
451 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
452 {
453 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
454 pressed = true;
455 ClearActiveID();
456 }
457
458 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
459 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
460 if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(button: 0, repeat: true))
461 pressed = true;
462 }
463
464 if (pressed)
465 g.NavDisableHighlight = true;
466 }
467
468 // Gamepad/Keyboard navigation
469 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
470 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
471 hovered = true;
472
473 if (g.NavActivateDownId == id)
474 {
475 bool nav_activated_by_code = (g.NavActivateId == id);
476 bool nav_activated_by_inputs = IsNavInputPressed(n: ImGuiNavInput_Activate, mode: (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
477 if (nav_activated_by_code || nav_activated_by_inputs)
478 pressed = true;
479 if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
480 {
481 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
482 g.NavActivateId = id; // This is so SetActiveId assign a Nav source
483 SetActiveID(id, window);
484 if (!(flags & ImGuiButtonFlags_NoNavFocus))
485 SetFocusID(id, window);
486 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
487 }
488 }
489
490 bool held = false;
491 if (g.ActiveId == id)
492 {
493 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
494 {
495 if (g.ActiveIdIsJustActivated)
496 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
497 if (g.IO.MouseDown[0])
498 {
499 held = true;
500 }
501 else
502 {
503 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
504 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
505 if (!g.DragDropActive)
506 pressed = true;
507 ClearActiveID();
508 }
509 if (!(flags & ImGuiButtonFlags_NoNavFocus))
510 g.NavDisableHighlight = true;
511 }
512 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
513 {
514 if (g.NavActivateDownId != id)
515 ClearActiveID();
516 }
517 }
518
519 if (out_hovered) *out_hovered = hovered;
520 if (out_held) *out_held = held;
521
522 return pressed;
523}
524
525bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
526{
527 ImGuiWindow* window = GetCurrentWindow();
528 if (window->SkipItems)
529 return false;
530
531 ImGuiContext& g = *GImGui;
532 const ImGuiStyle& style = g.Style;
533 const ImGuiID id = window->GetID(str: label);
534 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
535
536 ImVec2 pos = window->DC.CursorPos;
537 if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // 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)
538 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
539 ImVec2 size = CalcItemSize(size: size_arg, default_x: label_size.x + style.FramePadding.x * 2.0f, default_y: label_size.y + style.FramePadding.y * 2.0f);
540
541 const ImRect bb(pos, pos + size);
542 ItemSize(bb, text_offset_y: style.FramePadding.y);
543 if (!ItemAdd(bb, id))
544 return false;
545
546 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
547 flags |= ImGuiButtonFlags_Repeat;
548 bool hovered, held;
549 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
550 if (pressed)
551 MarkItemEdited(id);
552
553 // Render
554 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
555 RenderNavHighlight(bb, id);
556 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: style.FrameRounding);
557 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);
558
559 // Automatically close popups
560 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
561 // CloseCurrentPopup();
562
563 return pressed;
564}
565
566bool ImGui::Button(const char* label, const ImVec2& size_arg)
567{
568 return ButtonEx(label, size_arg, flags: 0);
569}
570
571// Small buttons fits within text without additional vertical spacing.
572bool ImGui::SmallButton(const char* label)
573{
574 ImGuiContext& g = *GImGui;
575 float backup_padding_y = g.Style.FramePadding.y;
576 g.Style.FramePadding.y = 0.0f;
577 bool pressed = ButtonEx(label, size_arg: ImVec2(0, 0), flags: ImGuiButtonFlags_AlignTextBaseLine);
578 g.Style.FramePadding.y = backup_padding_y;
579 return pressed;
580}
581
582// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
583// 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)
584bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
585{
586 ImGuiWindow* window = GetCurrentWindow();
587 if (window->SkipItems)
588 return false;
589
590 // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
591 IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
592
593 const ImGuiID id = window->GetID(str: str_id);
594 ImVec2 size = CalcItemSize(size: size_arg, default_x: 0.0f, default_y: 0.0f);
595 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
596 ItemSize(bb);
597 if (!ItemAdd(bb, id))
598 return false;
599
600 bool hovered, held;
601 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held);
602
603 return pressed;
604}
605
606bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
607{
608 ImGuiWindow* window = GetCurrentWindow();
609 if (window->SkipItems)
610 return false;
611
612 ImGuiContext& g = *GImGui;
613 const ImGuiID id = window->GetID(str: str_id);
614 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
615 const float default_size = GetFrameHeight();
616 ItemSize(bb, text_offset_y: (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
617 if (!ItemAdd(bb, id))
618 return false;
619
620 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
621 flags |= ImGuiButtonFlags_Repeat;
622
623 bool hovered, held;
624 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags);
625
626 // Render
627 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
628 RenderNavHighlight(bb, id);
629 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: g.Style.FrameRounding);
630 RenderArrow(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)), dir);
631
632 return pressed;
633}
634
635bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
636{
637 float sz = GetFrameHeight();
638 return ArrowButtonEx(str_id, dir, size: ImVec2(sz, sz), flags: 0);
639}
640
641// Button to close a window
642bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
643{
644 ImGuiContext& g = *GImGui;
645 ImGuiWindow* window = g.CurrentWindow;
646
647 // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
648 // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
649 const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
650 bool is_clipped = !ItemAdd(bb, id);
651
652 bool hovered, held;
653 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held);
654 if (is_clipped)
655 return pressed;
656
657 // Render
658 ImVec2 center = bb.GetCenter();
659 if (hovered)
660 window->DrawList->AddCircleFilled(centre: center, radius: ImMax(lhs: 2.0f, rhs: radius), col: GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), num_segments: 9);
661
662 float cross_extent = (radius * 0.7071f) - 1.0f;
663 ImU32 cross_col = GetColorU32(idx: ImGuiCol_Text);
664 center -= ImVec2(0.5f, 0.5f);
665 window->DrawList->AddLine(a: center + ImVec2(+cross_extent,+cross_extent), b: center + ImVec2(-cross_extent,-cross_extent), col: cross_col, thickness: 1.0f);
666 window->DrawList->AddLine(a: center + ImVec2(+cross_extent,-cross_extent), b: center + ImVec2(-cross_extent,+cross_extent), col: cross_col, thickness: 1.0f);
667
668 return pressed;
669}
670
671bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
672{
673 ImGuiContext& g = *GImGui;
674 ImGuiWindow* window = g.CurrentWindow;
675
676 ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
677 ItemAdd(bb, id);
678 bool hovered, held;
679 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_None);
680
681 ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
682 if (hovered || held)
683 window->DrawList->AddCircleFilled(centre: bb.GetCenter() + ImVec2(0.0f, -0.5f), radius: g.FontSize * 0.5f + 1.0f, col, num_segments: 9);
684 RenderArrow(pos: bb.Min + g.Style.FramePadding, dir: window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, scale: 1.0f);
685
686 // Switch to moving the window after mouse is moved beyond the initial drag threshold
687 if (IsItemActive() && IsMouseDragging())
688 StartMouseMovingWindow(window);
689
690 return pressed;
691}
692
693// Vertical/Horizontal scrollbar
694// The entire piece of code below is rather confusing because:
695// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
696// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
697// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
698void ImGui::Scrollbar(ImGuiLayoutType direction)
699{
700 ImGuiContext& g = *GImGui;
701 ImGuiWindow* window = g.CurrentWindow;
702
703 const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
704 const ImGuiStyle& style = g.Style;
705 const ImGuiID id = window->GetID(str: horizontal ? "#SCROLLX" : "#SCROLLY");
706
707 // Render background
708 bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
709 float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
710 const ImRect window_rect = window->Rect();
711 const float border_size = window->WindowBorderSize;
712 ImRect bb = horizontal
713 ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size)
714 : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size);
715 if (!horizontal)
716 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
717 if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
718 return;
719
720 int window_rounding_corners;
721 if (horizontal)
722 window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
723 else
724 window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
725 window->DrawList->AddRectFilled(a: bb.Min, b: bb.Max, col: GetColorU32(idx: ImGuiCol_ScrollbarBg), rounding: window->WindowRounding, rounding_corners_flags: window_rounding_corners);
726 bb.Expand(amount: ImVec2(-ImClamp(v: (float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), mn: 0.0f, mx: 3.0f), -ImClamp(v: (float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), mn: 0.0f, mx: 3.0f)));
727
728 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
729 float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
730 float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
731 float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
732 float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
733
734 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
735 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
736 IM_ASSERT(ImMax(win_size_contents_v, win_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.
737 const float win_size_v = ImMax(lhs: ImMax(lhs: win_size_contents_v, rhs: win_size_avail_v), rhs: 1.0f);
738 const float grab_h_pixels = ImClamp(v: scrollbar_size_v * (win_size_avail_v / win_size_v), mn: style.GrabMinSize, mx: scrollbar_size_v);
739 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
740
741 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
742 bool held = false;
743 bool hovered = false;
744 const bool previously_held = (g.ActiveId == id);
745 ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_NoNavFocus);
746
747 float scroll_max = ImMax(lhs: 1.0f, rhs: win_size_contents_v - win_size_avail_v);
748 float scroll_ratio = ImSaturate(f: scroll_v / scroll_max);
749 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
750 if (held && grab_h_norm < 1.0f)
751 {
752 float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
753 float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
754 float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
755
756 // Click position in scrollbar normalized space (0.0f->1.0f)
757 const float clicked_v_norm = ImSaturate(f: (mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
758 SetHoveredID(id);
759
760 bool seek_absolute = false;
761 if (!previously_held)
762 {
763 // On initial click calculate the distance between mouse and the center of the grab
764 if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
765 {
766 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
767 }
768 else
769 {
770 seek_absolute = true;
771 *click_delta_to_grab_center_v = 0.0f;
772 }
773 }
774
775 // Apply scroll
776 // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
777 const float scroll_v_norm = ImSaturate(f: (clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));
778 scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
779 if (horizontal)
780 window->Scroll.x = scroll_v;
781 else
782 window->Scroll.y = scroll_v;
783
784 // Update values for rendering
785 scroll_ratio = ImSaturate(f: scroll_v / scroll_max);
786 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
787
788 // Update distance to grab now that we have seeked and saturated
789 if (seek_absolute)
790 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
791 }
792
793 // Render
794 const ImU32 grab_col = GetColorU32(idx: held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
795 ImRect grab_rect;
796 if (horizontal)
797 grab_rect = ImRect(ImLerp(a: bb.Min.x, b: bb.Max.x, t: grab_v_norm), bb.Min.y, ImMin(lhs: ImLerp(a: bb.Min.x, b: bb.Max.x, t: grab_v_norm) + grab_h_pixels, rhs: window_rect.Max.x), bb.Max.y);
798 else
799 grab_rect = ImRect(bb.Min.x, ImLerp(a: bb.Min.y, b: bb.Max.y, t: grab_v_norm), bb.Max.x, ImMin(lhs: ImLerp(a: bb.Min.y, b: bb.Max.y, t: grab_v_norm) + grab_h_pixels, rhs: window_rect.Max.y));
800 window->DrawList->AddRectFilled(a: grab_rect.Min, b: grab_rect.Max, col: grab_col, rounding: style.ScrollbarRounding);
801}
802
803void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
804{
805 ImGuiWindow* window = GetCurrentWindow();
806 if (window->SkipItems)
807 return;
808
809 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
810 if (border_col.w > 0.0f)
811 bb.Max += ImVec2(2, 2);
812 ItemSize(bb);
813 if (!ItemAdd(bb, id: 0))
814 return;
815
816 if (border_col.w > 0.0f)
817 {
818 window->DrawList->AddRect(a: bb.Min, b: bb.Max, col: GetColorU32(col: border_col), rounding: 0.0f);
819 window->DrawList->AddImage(user_texture_id, a: bb.Min + ImVec2(1, 1), b: bb.Max - ImVec2(1, 1), uv_a: uv0, uv_b: uv1, col: GetColorU32(col: tint_col));
820 }
821 else
822 {
823 window->DrawList->AddImage(user_texture_id, a: bb.Min, b: bb.Max, uv_a: uv0, uv_b: uv1, col: GetColorU32(col: tint_col));
824 }
825}
826
827// frame_padding < 0: uses FramePadding from style (default)
828// frame_padding = 0: no framing
829// frame_padding > 0: set framing size
830// The color used are the button colors.
831bool 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)
832{
833 ImGuiWindow* window = GetCurrentWindow();
834 if (window->SkipItems)
835 return false;
836
837 ImGuiContext& g = *GImGui;
838 const ImGuiStyle& style = g.Style;
839
840 // Default to using texture ID as ID. User can still push string/integer prefixes.
841 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
842 PushID(ptr_id: (void*)(intptr_t)user_texture_id);
843 const ImGuiID id = window->GetID(str: "#image");
844 PopID();
845
846 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
847 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
848 const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
849 ItemSize(bb);
850 if (!ItemAdd(bb, id))
851 return false;
852
853 bool hovered, held;
854 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held);
855
856 // Render
857 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
858 RenderNavHighlight(bb, id);
859 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: style.FrameRounding));
860 if (bg_col.w > 0.0f)
861 window->DrawList->AddRectFilled(a: image_bb.Min, b: image_bb.Max, col: GetColorU32(col: bg_col));
862 window->DrawList->AddImage(user_texture_id, a: image_bb.Min, b: image_bb.Max, uv_a: uv0, uv_b: uv1, col: GetColorU32(col: tint_col));
863
864 return pressed;
865}
866
867bool ImGui::Checkbox(const char* label, bool* v)
868{
869 ImGuiWindow* window = GetCurrentWindow();
870 if (window->SkipItems)
871 return false;
872
873 ImGuiContext& g = *GImGui;
874 const ImGuiStyle& style = g.Style;
875 const ImGuiID id = window->GetID(str: label);
876 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
877
878 const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice
879 ItemSize(bb: check_bb, text_offset_y: style.FramePadding.y);
880
881 ImRect total_bb = check_bb;
882 if (label_size.x > 0)
883 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
884 const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
885 if (label_size.x > 0)
886 {
887 ItemSize(size: ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), text_offset_y: style.FramePadding.y);
888 total_bb = ImRect(ImMin(lhs: check_bb.Min, rhs: text_bb.Min), ImMax(lhs: check_bb.Max, rhs: text_bb.Max));
889 }
890
891 if (!ItemAdd(bb: total_bb, id))
892 return false;
893
894 bool hovered, held;
895 bool pressed = ButtonBehavior(bb: total_bb, id, out_hovered: &hovered, out_held: &held);
896 if (pressed)
897 {
898 *v = !(*v);
899 MarkItemEdited(id);
900 }
901
902 RenderNavHighlight(bb: total_bb, id);
903 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);
904 if (*v)
905 {
906 const float check_sz = ImMin(lhs: check_bb.GetWidth(), rhs: check_bb.GetHeight());
907 const float pad = ImMax(lhs: 1.0f, rhs: (float)(int)(check_sz / 6.0f));
908 RenderCheckMark(pos: check_bb.Min + ImVec2(pad,pad), col: GetColorU32(idx: ImGuiCol_CheckMark), sz: check_bb.GetWidth() - pad*2.0f);
909 }
910
911 if (g.LogEnabled)
912 LogRenderedText(ref_pos: &text_bb.Min, text: *v ? "[x]" : "[ ]");
913 if (label_size.x > 0.0f)
914 RenderText(pos: text_bb.Min, text: label);
915
916 return pressed;
917}
918
919bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
920{
921 bool v = ((*flags & flags_value) == flags_value);
922 bool pressed = Checkbox(label, v: &v);
923 if (pressed)
924 {
925 if (v)
926 *flags |= flags_value;
927 else
928 *flags &= ~flags_value;
929 }
930
931 return pressed;
932}
933
934bool ImGui::RadioButton(const char* label, bool active)
935{
936 ImGuiWindow* window = GetCurrentWindow();
937 if (window->SkipItems)
938 return false;
939
940 ImGuiContext& g = *GImGui;
941 const ImGuiStyle& style = g.Style;
942 const ImGuiID id = window->GetID(str: label);
943 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
944
945 const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1));
946 ItemSize(bb: check_bb, text_offset_y: style.FramePadding.y);
947
948 ImRect total_bb = check_bb;
949 if (label_size.x > 0)
950 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
951 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
952 if (label_size.x > 0)
953 {
954 ItemSize(size: ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), text_offset_y: style.FramePadding.y);
955 total_bb.Add(r: text_bb);
956 }
957
958 if (!ItemAdd(bb: total_bb, id))
959 return false;
960
961 ImVec2 center = check_bb.GetCenter();
962 center.x = (float)(int)center.x + 0.5f;
963 center.y = (float)(int)center.y + 0.5f;
964 const float radius = check_bb.GetHeight() * 0.5f;
965
966 bool hovered, held;
967 bool pressed = ButtonBehavior(bb: total_bb, id, out_hovered: &hovered, out_held: &held);
968 if (pressed)
969 MarkItemEdited(id);
970
971 RenderNavHighlight(bb: total_bb, id);
972 window->DrawList->AddCircleFilled(centre: center, radius, col: GetColorU32(idx: (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segments: 16);
973 if (active)
974 {
975 const float check_sz = ImMin(lhs: check_bb.GetWidth(), rhs: check_bb.GetHeight());
976 const float pad = ImMax(lhs: 1.0f, rhs: (float)(int)(check_sz / 6.0f));
977 window->DrawList->AddCircleFilled(centre: center, radius: radius-pad, col: GetColorU32(idx: ImGuiCol_CheckMark), num_segments: 16);
978 }
979
980 if (style.FrameBorderSize > 0.0f)
981 {
982 window->DrawList->AddCircle(centre: center+ImVec2(1,1), radius, col: GetColorU32(idx: ImGuiCol_BorderShadow), num_segments: 16, thickness: style.FrameBorderSize);
983 window->DrawList->AddCircle(centre: center, radius, col: GetColorU32(idx: ImGuiCol_Border), num_segments: 16, thickness: style.FrameBorderSize);
984 }
985
986 if (g.LogEnabled)
987 LogRenderedText(ref_pos: &text_bb.Min, text: active ? "(x)" : "( )");
988 if (label_size.x > 0.0f)
989 RenderText(pos: text_bb.Min, text: label);
990
991 return pressed;
992}
993
994bool ImGui::RadioButton(const char* label, int* v, int v_button)
995{
996 const bool pressed = RadioButton(label, active: *v == v_button);
997 if (pressed)
998 *v = v_button;
999 return pressed;
1000}
1001
1002// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1003void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1004{
1005 ImGuiWindow* window = GetCurrentWindow();
1006 if (window->SkipItems)
1007 return;
1008
1009 ImGuiContext& g = *GImGui;
1010 const ImGuiStyle& style = g.Style;
1011
1012 ImVec2 pos = window->DC.CursorPos;
1013 ImRect bb(pos, pos + CalcItemSize(size: size_arg, default_x: CalcItemWidth(), default_y: g.FontSize + style.FramePadding.y*2.0f));
1014 ItemSize(bb, text_offset_y: style.FramePadding.y);
1015 if (!ItemAdd(bb, id: 0))
1016 return;
1017
1018 // Render
1019 fraction = ImSaturate(f: fraction);
1020 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: GetColorU32(idx: ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding);
1021 bb.Expand(amount: ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1022 const ImVec2 fill_br = ImVec2(ImLerp(a: bb.Min.x, b: bb.Max.x, t: fraction), bb.Max.y);
1023 RenderRectFilledRangeH(draw_list: window->DrawList, rect: bb, col: GetColorU32(idx: ImGuiCol_PlotHistogram), x_start_norm: 0.0f, x_end_norm: fraction, rounding: style.FrameRounding);
1024
1025 // Default displaying the fraction as percentage string, but user can override it
1026 char overlay_buf[32];
1027 if (!overlay)
1028 {
1029 ImFormatString(buf: overlay_buf, IM_ARRAYSIZE(overlay_buf), fmt: "%.0f%%", fraction*100+0.01f);
1030 overlay = overlay_buf;
1031 }
1032
1033 ImVec2 overlay_size = CalcTextSize(text: overlay, NULL);
1034 if (overlay_size.x > 0.0f)
1035 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);
1036}
1037
1038void ImGui::Bullet()
1039{
1040 ImGuiWindow* window = GetCurrentWindow();
1041 if (window->SkipItems)
1042 return;
1043
1044 ImGuiContext& g = *GImGui;
1045 const ImGuiStyle& style = g.Style;
1046 const float line_height = ImMax(lhs: ImMin(lhs: window->DC.CurrentLineSize.y, rhs: g.FontSize + g.Style.FramePadding.y*2), rhs: g.FontSize);
1047 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1048 ItemSize(bb);
1049 if (!ItemAdd(bb, id: 0))
1050 {
1051 SameLine(pos_x: 0, spacing_w: style.FramePadding.x*2);
1052 return;
1053 }
1054
1055 // Render and stay on same line
1056 RenderBullet(pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
1057 SameLine(pos_x: 0, spacing_w: style.FramePadding.x*2);
1058}
1059
1060//-------------------------------------------------------------------------
1061// [SECTION] Widgets: Low-level Layout helpers
1062//-------------------------------------------------------------------------
1063// - Spacing()
1064// - Dummy()
1065// - NewLine()
1066// - AlignTextToFramePadding()
1067// - Separator()
1068// - VerticalSeparator() [Internal]
1069// - SplitterBehavior() [Internal]
1070//-------------------------------------------------------------------------
1071
1072void ImGui::Spacing()
1073{
1074 ImGuiWindow* window = GetCurrentWindow();
1075 if (window->SkipItems)
1076 return;
1077 ItemSize(size: ImVec2(0,0));
1078}
1079
1080void ImGui::Dummy(const ImVec2& size)
1081{
1082 ImGuiWindow* window = GetCurrentWindow();
1083 if (window->SkipItems)
1084 return;
1085
1086 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1087 ItemSize(bb);
1088 ItemAdd(bb, id: 0);
1089}
1090
1091void ImGui::NewLine()
1092{
1093 ImGuiWindow* window = GetCurrentWindow();
1094 if (window->SkipItems)
1095 return;
1096
1097 ImGuiContext& g = *GImGui;
1098 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1099 window->DC.LayoutType = ImGuiLayoutType_Vertical;
1100 if (window->DC.CurrentLineSize.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.
1101 ItemSize(size: ImVec2(0,0));
1102 else
1103 ItemSize(size: ImVec2(0.0f, g.FontSize));
1104 window->DC.LayoutType = backup_layout_type;
1105}
1106
1107void ImGui::AlignTextToFramePadding()
1108{
1109 ImGuiWindow* window = GetCurrentWindow();
1110 if (window->SkipItems)
1111 return;
1112
1113 ImGuiContext& g = *GImGui;
1114 window->DC.CurrentLineSize.y = ImMax(lhs: window->DC.CurrentLineSize.y, rhs: g.FontSize + g.Style.FramePadding.y * 2);
1115 window->DC.CurrentLineTextBaseOffset = ImMax(lhs: window->DC.CurrentLineTextBaseOffset, rhs: g.Style.FramePadding.y);
1116}
1117
1118// Horizontal/vertical separating line
1119void ImGui::Separator()
1120{
1121 ImGuiWindow* window = GetCurrentWindow();
1122 if (window->SkipItems)
1123 return;
1124 ImGuiContext& g = *GImGui;
1125
1126 // Those flags should eventually be overridable by the user
1127 ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1128 IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected
1129 if (flags & ImGuiSeparatorFlags_Vertical)
1130 {
1131 VerticalSeparator();
1132 return;
1133 }
1134
1135 // Horizontal Separator
1136 if (window->DC.ColumnsSet)
1137 PopClipRect();
1138
1139 float x1 = window->Pos.x;
1140 float x2 = window->Pos.x + window->Size.x;
1141 if (!window->DC.GroupStack.empty())
1142 x1 += window->DC.Indent.x;
1143
1144 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
1145 ItemSize(size: ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
1146 if (!ItemAdd(bb, id: 0))
1147 {
1148 if (window->DC.ColumnsSet)
1149 PushColumnClipRect();
1150 return;
1151 }
1152
1153 window->DrawList->AddLine(a: bb.Min, b: ImVec2(bb.Max.x,bb.Min.y), col: GetColorU32(idx: ImGuiCol_Separator));
1154
1155 if (g.LogEnabled)
1156 LogRenderedText(ref_pos: &bb.Min, text: "--------------------------------");
1157
1158 if (window->DC.ColumnsSet)
1159 {
1160 PushColumnClipRect();
1161 window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
1162 }
1163}
1164
1165void ImGui::VerticalSeparator()
1166{
1167 ImGuiWindow* window = GetCurrentWindow();
1168 if (window->SkipItems)
1169 return;
1170 ImGuiContext& g = *GImGui;
1171
1172 float y1 = window->DC.CursorPos.y;
1173 float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;
1174 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
1175 ItemSize(size: ImVec2(bb.GetWidth(), 0.0f));
1176 if (!ItemAdd(bb, id: 0))
1177 return;
1178
1179 window->DrawList->AddLine(a: ImVec2(bb.Min.x, bb.Min.y), b: ImVec2(bb.Min.x, bb.Max.y), col: GetColorU32(idx: ImGuiCol_Separator));
1180 if (g.LogEnabled)
1181 LogText(fmt: " |");
1182}
1183
1184// 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.
1185bool 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)
1186{
1187 ImGuiContext& g = *GImGui;
1188 ImGuiWindow* window = g.CurrentWindow;
1189
1190 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
1191 window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
1192 bool item_add = ItemAdd(bb, id);
1193 window->DC.ItemFlags = item_flags_backup;
1194 if (!item_add)
1195 return false;
1196
1197 bool hovered, held;
1198 ImRect bb_interact = bb;
1199 bb_interact.Expand(amount: axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1200 ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
1201 if (g.ActiveId != id)
1202 SetItemAllowOverlap();
1203
1204 if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1205 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1206
1207 ImRect bb_render = bb;
1208 if (held)
1209 {
1210 ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
1211 float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
1212
1213 // Minimum pane size
1214 float size_1_maximum_delta = ImMax(lhs: 0.0f, rhs: *size1 - min_size1);
1215 float size_2_maximum_delta = ImMax(lhs: 0.0f, rhs: *size2 - min_size2);
1216 if (mouse_delta < -size_1_maximum_delta)
1217 mouse_delta = -size_1_maximum_delta;
1218 if (mouse_delta > size_2_maximum_delta)
1219 mouse_delta = size_2_maximum_delta;
1220
1221 // Apply resize
1222 if (mouse_delta != 0.0f)
1223 {
1224 if (mouse_delta < 0.0f)
1225 IM_ASSERT(*size1 + mouse_delta >= min_size1);
1226 if (mouse_delta > 0.0f)
1227 IM_ASSERT(*size2 - mouse_delta >= min_size2);
1228 *size1 += mouse_delta;
1229 *size2 -= mouse_delta;
1230 bb_render.Translate(d: (axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1231 MarkItemEdited(id);
1232 }
1233 }
1234
1235 // Render
1236 const ImU32 col = GetColorU32(idx: held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1237 window->DrawList->AddRectFilled(a: bb_render.Min, b: bb_render.Max, col, rounding: g.Style.FrameRounding);
1238
1239 return held;
1240}
1241
1242
1243//-------------------------------------------------------------------------
1244// [SECTION] Widgets: ComboBox
1245//-------------------------------------------------------------------------
1246// - BeginCombo()
1247// - EndCombo()
1248// - Combo()
1249//-------------------------------------------------------------------------
1250
1251static float CalcMaxPopupHeightFromItemCount(int items_count)
1252{
1253 ImGuiContext& g = *GImGui;
1254 if (items_count <= 0)
1255 return FLT_MAX;
1256 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1257}
1258
1259bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1260{
1261 // Always consume the SetNextWindowSizeConstraint() call in our early return paths
1262 ImGuiContext& g = *GImGui;
1263 ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
1264 g.NextWindowData.SizeConstraintCond = 0;
1265
1266 ImGuiWindow* window = GetCurrentWindow();
1267 if (window->SkipItems)
1268 return false;
1269
1270 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1271
1272 const ImGuiStyle& style = g.Style;
1273 const ImGuiID id = window->GetID(str: label);
1274
1275 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1276 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
1277 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
1278 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
1279 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));
1280 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
1281 if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb))
1282 return false;
1283
1284 bool hovered, held;
1285 bool pressed = ButtonBehavior(bb: frame_bb, id, out_hovered: &hovered, out_held: &held);
1286 bool popup_open = IsPopupOpen(id);
1287
1288 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
1289 const ImU32 frame_col = GetColorU32(idx: hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1290 RenderNavHighlight(bb: frame_bb, id);
1291 if (!(flags & ImGuiComboFlags_NoPreview))
1292 window->DrawList->AddRectFilled(a: frame_bb.Min, b: ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), col: frame_col, rounding: style.FrameRounding, rounding_corners_flags: ImDrawCornerFlags_Left);
1293 if (!(flags & ImGuiComboFlags_NoArrowButton))
1294 {
1295 window->DrawList->AddRectFilled(a: ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), b: frame_bb.Max, col: GetColorU32(idx: (popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), rounding: style.FrameRounding, rounding_corners_flags: (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
1296 RenderArrow(pos: ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), dir: ImGuiDir_Down);
1297 }
1298 RenderFrameBorder(p_min: frame_bb.Min, p_max: frame_bb.Max, rounding: style.FrameRounding);
1299 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1300 RenderTextClipped(pos_min: frame_bb.Min + style.FramePadding, pos_max: value_bb.Max, text: preview_value, NULL, NULL, align: ImVec2(0.0f,0.0f));
1301 if (label_size.x > 0)
1302 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
1303
1304 if ((pressed || g.NavActivateId == id) && !popup_open)
1305 {
1306 if (window->DC.NavLayerCurrent == 0)
1307 window->NavLastIds[0] = id;
1308 OpenPopupEx(id);
1309 popup_open = true;
1310 }
1311
1312 if (!popup_open)
1313 return false;
1314
1315 if (backup_next_window_size_constraint)
1316 {
1317 g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
1318 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(lhs: g.NextWindowData.SizeConstraintRect.Min.x, rhs: w);
1319 }
1320 else
1321 {
1322 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1323 flags |= ImGuiComboFlags_HeightRegular;
1324 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1325 int popup_max_height_in_items = -1;
1326 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
1327 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
1328 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
1329 SetNextWindowSizeConstraints(size_min: ImVec2(w, 0.0f), size_max: ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items)));
1330 }
1331
1332 char name[16];
1333 ImFormatString(buf: name, IM_ARRAYSIZE(name), fmt: "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
1334
1335 // Peak into expected window size so we can position it
1336 if (ImGuiWindow* popup_window = FindWindowByName(name))
1337 if (popup_window->WasActive)
1338 {
1339 ImVec2 size_expected = CalcWindowExpectedSize(window: popup_window);
1340 if (flags & ImGuiComboFlags_PopupAlignLeft)
1341 popup_window->AutoPosLastDirection = ImGuiDir_Left;
1342 ImRect r_outer = GetWindowAllowedExtentRect(window: popup_window);
1343 ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos: frame_bb.GetBL(), size: size_expected, last_dir: &popup_window->AutoPosLastDirection, r_outer, r_avoid: frame_bb, policy: ImGuiPopupPositionPolicy_ComboBox);
1344 SetNextWindowPos(pos);
1345 }
1346
1347 // Horizontally align ourselves with the framed text
1348 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
1349 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: ImVec2(style.FramePadding.x, style.WindowPadding.y));
1350 bool ret = Begin(name, NULL, flags: window_flags);
1351 PopStyleVar();
1352 if (!ret)
1353 {
1354 EndPopup();
1355 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1356 return false;
1357 }
1358 return true;
1359}
1360
1361void ImGui::EndCombo()
1362{
1363 EndPopup();
1364}
1365
1366// Getter for the old Combo() API: const char*[]
1367static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
1368{
1369 const char* const* items = (const char* const*)data;
1370 if (out_text)
1371 *out_text = items[idx];
1372 return true;
1373}
1374
1375// Getter for the old Combo() API: "item1\0item2\0item3\0"
1376static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
1377{
1378 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
1379 const char* items_separated_by_zeros = (const char*)data;
1380 int items_count = 0;
1381 const char* p = items_separated_by_zeros;
1382 while (*p)
1383 {
1384 if (idx == items_count)
1385 break;
1386 p += strlen(s: p) + 1;
1387 items_count++;
1388 }
1389 if (!*p)
1390 return false;
1391 if (out_text)
1392 *out_text = p;
1393 return true;
1394}
1395
1396// Old API, prefer using BeginCombo() nowadays if you can.
1397bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
1398{
1399 ImGuiContext& g = *GImGui;
1400
1401 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1402 const char* preview_value = NULL;
1403 if (*current_item >= 0 && *current_item < items_count)
1404 items_getter(data, *current_item, &preview_value);
1405
1406 // 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.
1407 if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
1408 SetNextWindowSizeConstraints(size_min: ImVec2(0,0), size_max: ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items)));
1409
1410 if (!BeginCombo(label, preview_value, flags: ImGuiComboFlags_None))
1411 return false;
1412
1413 // Display items
1414 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1415 bool value_changed = false;
1416 for (int i = 0; i < items_count; i++)
1417 {
1418 PushID(ptr_id: (void*)(intptr_t)i);
1419 const bool item_selected = (i == *current_item);
1420 const char* item_text;
1421 if (!items_getter(data, i, &item_text))
1422 item_text = "*Unknown item*";
1423 if (Selectable(label: item_text, selected: item_selected))
1424 {
1425 value_changed = true;
1426 *current_item = i;
1427 }
1428 if (item_selected)
1429 SetItemDefaultFocus();
1430 PopID();
1431 }
1432
1433 EndCombo();
1434 return value_changed;
1435}
1436
1437// Combo box helper allowing to pass an array of strings.
1438bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1439{
1440 const bool value_changed = Combo(label, current_item, items_getter: Items_ArrayGetter, data: (void*)items, items_count, popup_max_height_in_items: height_in_items);
1441 return value_changed;
1442}
1443
1444// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
1445bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1446{
1447 int items_count = 0;
1448 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
1449 while (*p)
1450 {
1451 p += strlen(s: p) + 1;
1452 items_count++;
1453 }
1454 bool value_changed = Combo(label, current_item, items_getter: Items_SingleStringGetter, data: (void*)items_separated_by_zeros, items_count, popup_max_height_in_items: height_in_items);
1455 return value_changed;
1456}
1457
1458//-------------------------------------------------------------------------
1459// [SECTION] Data Type and Data Formatting Helpers [Internal]
1460//-------------------------------------------------------------------------
1461// - PatchFormatStringFloatToInt()
1462// - DataTypeFormatString()
1463// - DataTypeApplyOp()
1464// - DataTypeApplyOpFromText()
1465// - GetMinimumStepAtDecimalPrecision
1466// - RoundScalarWithFormat<>()
1467//-------------------------------------------------------------------------
1468
1469struct ImGuiDataTypeInfo
1470{
1471 size_t Size;
1472 const char* PrintFmt; // Unused
1473 const char* ScanFmt;
1474};
1475
1476static const ImGuiDataTypeInfo GDataTypeInfo[] =
1477{
1478 { .Size: sizeof(int), .PrintFmt: "%d", .ScanFmt: "%d" },
1479 { .Size: sizeof(unsigned int), .PrintFmt: "%u", .ScanFmt: "%u" },
1480#ifdef _MSC_VER
1481 { sizeof(ImS64), "%I64d","%I64d" },
1482 { sizeof(ImU64), "%I64u","%I64u" },
1483#else
1484 { .Size: sizeof(ImS64), .PrintFmt: "%lld", .ScanFmt: "%lld" },
1485 { .Size: sizeof(ImU64), .PrintFmt: "%llu", .ScanFmt: "%llu" },
1486#endif
1487 { .Size: sizeof(float), .PrintFmt: "%f", .ScanFmt: "%f" }, // float are promoted to double in va_arg
1488 { .Size: sizeof(double), .PrintFmt: "%f", .ScanFmt: "%lf" },
1489};
1490IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1491
1492// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
1493// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
1494// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
1495static const char* PatchFormatStringFloatToInt(const char* fmt)
1496{
1497 if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
1498 return "%d";
1499 const char* fmt_start = ImParseFormatFindStart(format: fmt); // Find % (if any, and ignore %%)
1500 const char* fmt_end = ImParseFormatFindEnd(format: fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
1501 if (fmt_end > fmt_start && fmt_end[-1] == 'f')
1502 {
1503#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1504 if (fmt_start == fmt && fmt_end[0] == 0)
1505 return "%d";
1506 ImGuiContext& g = *GImGui;
1507 ImFormatString(buf: g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt: "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
1508 return g.TempBuffer;
1509#else
1510 IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
1511#endif
1512 }
1513 return fmt;
1514}
1515
1516static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
1517{
1518 if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument
1519 return ImFormatString(buf, buf_size, fmt: format, *(const ImU32*)data_ptr);
1520 if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument
1521 return ImFormatString(buf, buf_size, fmt: format, *(const ImU64*)data_ptr);
1522 if (data_type == ImGuiDataType_Float)
1523 return ImFormatString(buf, buf_size, fmt: format, *(const float*)data_ptr);
1524 if (data_type == ImGuiDataType_Double)
1525 return ImFormatString(buf, buf_size, fmt: format, *(const double*)data_ptr);
1526 IM_ASSERT(0);
1527 return 0;
1528}
1529
1530// FIXME: Adding support for clamping on boundaries of the data type would be nice.
1531static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
1532{
1533 IM_ASSERT(op == '+' || op == '-');
1534 switch (data_type)
1535 {
1536 case ImGuiDataType_S32:
1537 if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2;
1538 else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
1539 return;
1540 case ImGuiDataType_U32:
1541 if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
1542 else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
1543 return;
1544 case ImGuiDataType_S64:
1545 if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
1546 else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
1547 return;
1548 case ImGuiDataType_U64:
1549 if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
1550 else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
1551 return;
1552 case ImGuiDataType_Float:
1553 if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2;
1554 else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
1555 return;
1556 case ImGuiDataType_Double:
1557 if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2;
1558 else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
1559 return;
1560 case ImGuiDataType_COUNT: break;
1561 }
1562 IM_ASSERT(0);
1563}
1564
1565// User can input math operators (e.g. +100) to edit a numerical values.
1566// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
1567static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
1568{
1569 while (ImCharIsBlankA(c: *buf))
1570 buf++;
1571
1572 // We don't support '-' op because it would conflict with inputing negative value.
1573 // Instead you can use +-100 to subtract from an existing value
1574 char op = buf[0];
1575 if (op == '+' || op == '*' || op == '/')
1576 {
1577 buf++;
1578 while (ImCharIsBlankA(c: *buf))
1579 buf++;
1580 }
1581 else
1582 {
1583 op = 0;
1584 }
1585 if (!buf[0])
1586 return false;
1587
1588 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
1589 IM_ASSERT(data_type < ImGuiDataType_COUNT);
1590 int data_backup[2];
1591 IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
1592 memcpy(dest: data_backup, src: data_ptr, n: GDataTypeInfo[data_type].Size);
1593
1594 if (format == NULL)
1595 format = GDataTypeInfo[data_type].ScanFmt;
1596
1597 int arg1i = 0;
1598 if (data_type == ImGuiDataType_S32)
1599 {
1600 int* v = (int*)data_ptr;
1601 int arg0i = *v;
1602 float arg1f = 0.0f;
1603 if (op && sscanf(s: initial_value_buf, format: format, &arg0i) < 1)
1604 return false;
1605 // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
1606 if (op == '+') { if (sscanf(s: buf, format: "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)
1607 else if (op == '*') { if (sscanf(s: buf, format: "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply
1608 else if (op == '/') { if (sscanf(s: buf, format: "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide
1609 else { if (sscanf(s: buf, format: format, &arg1i) == 1) *v = arg1i; } // Assign constant
1610 }
1611 else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1612 {
1613 // Assign constant
1614 // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
1615 sscanf(s: buf, format: format, data_ptr);
1616 }
1617 else if (data_type == ImGuiDataType_Float)
1618 {
1619 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
1620 format = "%f";
1621 float* v = (float*)data_ptr;
1622 float arg0f = *v, arg1f = 0.0f;
1623 if (op && sscanf(s: initial_value_buf, format: format, &arg0f) < 1)
1624 return false;
1625 if (sscanf(s: buf, format: format, &arg1f) < 1)
1626 return false;
1627 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
1628 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
1629 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
1630 else { *v = arg1f; } // Assign constant
1631 }
1632 else if (data_type == ImGuiDataType_Double)
1633 {
1634 format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
1635 double* v = (double*)data_ptr;
1636 double arg0f = *v, arg1f = 0.0;
1637 if (op && sscanf(s: initial_value_buf, format: format, &arg0f) < 1)
1638 return false;
1639 if (sscanf(s: buf, format: format, &arg1f) < 1)
1640 return false;
1641 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
1642 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
1643 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
1644 else { *v = arg1f; } // Assign constant
1645 }
1646 return memcmp(s1: data_backup, s2: data_ptr, n: GDataTypeInfo[data_type].Size) != 0;
1647}
1648
1649static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
1650{
1651 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 };
1652 if (decimal_precision < 0)
1653 return FLT_MIN;
1654 return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(x: 10.0f, y: (float)-decimal_precision);
1655}
1656
1657template<typename TYPE>
1658static const char* ImAtoi(const char* src, TYPE* output)
1659{
1660 int negative = 0;
1661 if (*src == '-') { negative = 1; src++; }
1662 if (*src == '+') { src++; }
1663 TYPE v = 0;
1664 while (*src >= '0' && *src <= '9')
1665 v = (v * 10) + (*src++ - '0');
1666 *output = negative ? -v : v;
1667 return src;
1668}
1669
1670template<typename TYPE, typename SIGNEDTYPE>
1671TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
1672{
1673 const char* fmt_start = ImParseFormatFindStart(format);
1674 if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
1675 return v;
1676 char v_str[64];
1677 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
1678 const char* p = v_str;
1679 while (*p == ' ')
1680 p++;
1681 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
1682 v = (TYPE)ImAtof(s: p);
1683 else
1684 ImAtoi(p, (SIGNEDTYPE*)&v);
1685 return v;
1686}
1687
1688//-------------------------------------------------------------------------
1689// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1690//-------------------------------------------------------------------------
1691// - DragBehaviorT<>() [Internal]
1692// - DragBehavior() [Internal]
1693// - DragScalar()
1694// - DragScalarN()
1695// - DragFloat()
1696// - DragFloat2()
1697// - DragFloat3()
1698// - DragFloat4()
1699// - DragFloatRange2()
1700// - DragInt()
1701// - DragInt2()
1702// - DragInt3()
1703// - DragInt4()
1704// - DragIntRange2()
1705//-------------------------------------------------------------------------
1706
1707// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
1708template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1709bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiDragFlags flags)
1710{
1711 ImGuiContext& g = *GImGui;
1712 const ImGuiAxis axis = (flags & ImGuiDragFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
1713 const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1714 const bool has_min_max = (v_min != v_max);
1715
1716 // Default tweak speed
1717 if (v_speed == 0.0f && has_min_max && (v_max - v_min < FLT_MAX))
1718 v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
1719
1720 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
1721 float adjust_delta = 0.0f;
1722 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
1723 {
1724 adjust_delta = g.IO.MouseDelta[axis];
1725 if (g.IO.KeyAlt)
1726 adjust_delta *= 1.0f / 100.0f;
1727 if (g.IO.KeyShift)
1728 adjust_delta *= 10.0f;
1729 }
1730 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
1731 {
1732 int decimal_precision = is_decimal ? ImParseFormatPrecision(format, default_value: 3) : 0;
1733 adjust_delta = GetNavInputAmount2d(dir_sources: ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, mode: ImGuiInputReadMode_RepeatFast, slow_factor: 1.0f / 10.0f, fast_factor: 10.0f)[axis];
1734 v_speed = ImMax(lhs: v_speed, rhs: GetMinimumStepAtDecimalPrecision(decimal_precision));
1735 }
1736 adjust_delta *= v_speed;
1737
1738 // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
1739 if (axis == ImGuiAxis_Y)
1740 adjust_delta = -adjust_delta;
1741
1742 // Clear current value on activation
1743 // 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.
1744 bool is_just_activated = g.ActiveIdIsJustActivated;
1745 bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
1746 if (is_just_activated || is_already_past_limits_and_pushing_outward)
1747 {
1748 g.DragCurrentAccum = 0.0f;
1749 g.DragCurrentAccumDirty = false;
1750 }
1751 else if (adjust_delta != 0.0f)
1752 {
1753 g.DragCurrentAccum += adjust_delta;
1754 g.DragCurrentAccumDirty = true;
1755 }
1756
1757 if (!g.DragCurrentAccumDirty)
1758 return false;
1759
1760 TYPE v_cur = *v;
1761 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
1762
1763 const bool is_power = (power != 1.0f && is_decimal && has_min_max && (v_max - v_min < FLT_MAX));
1764 if (is_power)
1765 {
1766 // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
1767 FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
1768 FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
1769 v_cur = v_min + (TYPE)ImPow(x: ImSaturate(f: (float)v_new_norm_curved), y: power) * (v_max - v_min);
1770 v_old_ref_for_accum_remainder = v_old_norm_curved;
1771 }
1772 else
1773 {
1774 v_cur += (TYPE)g.DragCurrentAccum;
1775 }
1776
1777 // Round to user desired precision based on format string
1778 v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
1779
1780 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
1781 g.DragCurrentAccumDirty = false;
1782 if (is_power)
1783 {
1784 FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
1785 g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
1786 }
1787 else
1788 {
1789 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
1790 }
1791
1792 // Lose zero sign for float/double
1793 if (v_cur == (TYPE)-0)
1794 v_cur = (TYPE)0;
1795
1796 // Clamp values (+ handle overflow/wrap-around for integer types)
1797 if (*v != v_cur && has_min_max)
1798 {
1799 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal))
1800 v_cur = v_min;
1801 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal))
1802 v_cur = v_max;
1803 }
1804
1805 // Apply result
1806 if (*v == v_cur)
1807 return false;
1808 *v = v_cur;
1809 return true;
1810}
1811
1812bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power, ImGuiDragFlags flags)
1813{
1814 ImGuiContext& g = *GImGui;
1815 if (g.ActiveId == id)
1816 {
1817 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
1818 ClearActiveID();
1819 else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1820 ClearActiveID();
1821 }
1822 if (g.ActiveId != id)
1823 return false;
1824
1825 switch (data_type)
1826 {
1827 case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, v: (ImS32*)v, v_speed, v_min: v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max: v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power, flags);
1828 case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, v: (ImU32*)v, v_speed, v_min: v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max: v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power, flags);
1829 case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, v: (ImS64*)v, v_speed, v_min: v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max: v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power, flags);
1830 case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, v: (ImU64*)v, v_speed, v_min: v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max: v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power, flags);
1831 case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, v: (float*)v, v_speed, v_min: v_min ? *(const float* )v_min : -FLT_MAX, v_max: v_max ? *(const float* )v_max : FLT_MAX, format, power, flags);
1832 case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, v: (double*)v, v_speed, v_min: v_min ? *(const double*)v_min : -DBL_MAX, v_max: v_max ? *(const double*)v_max : DBL_MAX, format, power, flags);
1833 case ImGuiDataType_COUNT: break;
1834 }
1835 IM_ASSERT(0);
1836 return false;
1837}
1838
1839bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
1840{
1841 ImGuiWindow* window = GetCurrentWindow();
1842 if (window->SkipItems)
1843 return false;
1844
1845 if (power != 1.0f)
1846 IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
1847
1848 ImGuiContext& g = *GImGui;
1849 const ImGuiStyle& style = g.Style;
1850 const ImGuiID id = window->GetID(str: label);
1851 const float w = CalcItemWidth();
1852
1853 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
1854 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
1855 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
1856 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));
1857
1858 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
1859 if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb))
1860 {
1861 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
1862 return false;
1863 }
1864 const bool hovered = ItemHoverable(bb: frame_bb, id);
1865
1866 // Default format string when passing NULL
1867 // Patch old "%.0f" format string to use "%d", read function comments for more details.
1868 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
1869 if (format == NULL)
1870 format = GDataTypeInfo[data_type].PrintFmt;
1871 else if (data_type == ImGuiDataType_S32 && strcmp(s1: format, s2: "%d") != 0)
1872 format = PatchFormatStringFloatToInt(fmt: format);
1873
1874 // Tabbing or CTRL-clicking on Drag turns it into an input box
1875 bool start_text_input = false;
1876 const bool tab_focus_requested = FocusableItemRegister(window, id);
1877 if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
1878 {
1879 SetActiveID(id, window);
1880 SetFocusID(id, window);
1881 FocusWindow(window);
1882 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
1883 if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
1884 {
1885 start_text_input = true;
1886 g.ScalarAsInputTextId = 0;
1887 }
1888 }
1889 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
1890 {
1891 FocusableItemUnregister(window);
1892 return InputScalarAsWidgetReplacement(bb: frame_bb, id, label, data_type, data_ptr: v, format);
1893 }
1894
1895 // Actual drag behavior
1896 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
1897 const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power, flags: ImGuiDragFlags_None);
1898 if (value_changed)
1899 MarkItemEdited(id);
1900
1901 // Draw frame
1902 const ImU32 frame_col = GetColorU32(idx: g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1903 RenderNavHighlight(bb: frame_bb, id);
1904 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: frame_col, border: true, rounding: style.FrameRounding);
1905
1906 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1907 char value_buf[64];
1908 const char* value_buf_end = value_buf + DataTypeFormatString(buf: value_buf, IM_ARRAYSIZE(value_buf), data_type, data_ptr: v, format);
1909 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));
1910
1911 if (label_size.x > 0.0f)
1912 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), text: label);
1913
1914 return value_changed;
1915}
1916
1917bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
1918{
1919 ImGuiWindow* window = GetCurrentWindow();
1920 if (window->SkipItems)
1921 return false;
1922
1923 ImGuiContext& g = *GImGui;
1924 bool value_changed = false;
1925 BeginGroup();
1926 PushID(str_id: label);
1927 PushMultiItemsWidths(components);
1928 size_t type_size = GDataTypeInfo[data_type].Size;
1929 for (int i = 0; i < components; i++)
1930 {
1931 PushID(int_id: i);
1932 value_changed |= DragScalar(label: "##v", data_type, v, v_speed, v_min, v_max, format, power);
1933 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
1934 PopID();
1935 PopItemWidth();
1936 v = (void*)((char*)v + type_size);
1937 }
1938 PopID();
1939
1940 TextUnformatted(text: label, text_end: FindRenderedTextEnd(text: label));
1941 EndGroup();
1942 return value_changed;
1943}
1944
1945bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
1946{
1947 return DragScalar(label, data_type: ImGuiDataType_Float, v, v_speed, v_min: &v_min, v_max: &v_max, format, power);
1948}
1949
1950bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
1951{
1952 return DragScalarN(label, data_type: ImGuiDataType_Float, v, components: 2, v_speed, v_min: &v_min, v_max: &v_max, format, power);
1953}
1954
1955bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
1956{
1957 return DragScalarN(label, data_type: ImGuiDataType_Float, v, components: 3, v_speed, v_min: &v_min, v_max: &v_max, format, power);
1958}
1959
1960bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
1961{
1962 return DragScalarN(label, data_type: ImGuiDataType_Float, v, components: 4, v_speed, v_min: &v_min, v_max: &v_max, format, power);
1963}
1964
1965bool 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, float power)
1966{
1967 ImGuiWindow* window = GetCurrentWindow();
1968 if (window->SkipItems)
1969 return false;
1970
1971 ImGuiContext& g = *GImGui;
1972 PushID(str_id: label);
1973 BeginGroup();
1974 PushMultiItemsWidths(components: 2);
1975
1976 bool value_changed = DragFloat(label: "##min", v: v_current_min, v_speed, v_min: (v_min >= v_max) ? -FLT_MAX : v_min, v_max: (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max), format, power);
1977 PopItemWidth();
1978 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
1979 value_changed |= DragFloat(label: "##max", v: v_current_max, v_speed, v_min: (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min), v_max: (v_min >= v_max) ? FLT_MAX : v_max, format: format_max ? format_max : format, power);
1980 PopItemWidth();
1981 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
1982
1983 TextUnformatted(text: label, text_end: FindRenderedTextEnd(text: label));
1984 EndGroup();
1985 PopID();
1986 return value_changed;
1987}
1988
1989// NB: v_speed is float to allow adjusting the drag speed with more precision
1990bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
1991{
1992 return DragScalar(label, data_type: ImGuiDataType_S32, v, v_speed, v_min: &v_min, v_max: &v_max, format);
1993}
1994
1995bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
1996{
1997 return DragScalarN(label, data_type: ImGuiDataType_S32, v, components: 2, v_speed, v_min: &v_min, v_max: &v_max, format);
1998}
1999
2000bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
2001{
2002 return DragScalarN(label, data_type: ImGuiDataType_S32, v, components: 3, v_speed, v_min: &v_min, v_max: &v_max, format);
2003}
2004
2005bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
2006{
2007 return DragScalarN(label, data_type: ImGuiDataType_S32, v, components: 4, v_speed, v_min: &v_min, v_max: &v_max, format);
2008}
2009
2010bool 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)
2011{
2012 ImGuiWindow* window = GetCurrentWindow();
2013 if (window->SkipItems)
2014 return false;
2015
2016 ImGuiContext& g = *GImGui;
2017 PushID(str_id: label);
2018 BeginGroup();
2019 PushMultiItemsWidths(components: 2);
2020
2021 bool value_changed = DragInt(label: "##min", v: v_current_min, v_speed, v_min: (v_min >= v_max) ? INT_MIN : v_min, v_max: (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max), format);
2022 PopItemWidth();
2023 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
2024 value_changed |= DragInt(label: "##max", v: v_current_max, v_speed, v_min: (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min), v_max: (v_min >= v_max) ? INT_MAX : v_max, format: format_max ? format_max : format);
2025 PopItemWidth();
2026 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
2027
2028 TextUnformatted(text: label, text_end: FindRenderedTextEnd(text: label));
2029 EndGroup();
2030 PopID();
2031
2032 return value_changed;
2033}
2034
2035//-------------------------------------------------------------------------
2036// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2037//-------------------------------------------------------------------------
2038// - SliderBehaviorT<>() [Internal]
2039// - SliderBehavior() [Internal]
2040// - SliderScalar()
2041// - SliderScalarN()
2042// - SliderFloat()
2043// - SliderFloat2()
2044// - SliderFloat3()
2045// - SliderFloat4()
2046// - SliderAngle()
2047// - SliderInt()
2048// - SliderInt2()
2049// - SliderInt3()
2050// - SliderInt4()
2051// - VSliderScalar()
2052// - VSliderFloat()
2053// - VSliderInt()
2054//-------------------------------------------------------------------------
2055
2056template<typename TYPE, typename FLOATTYPE>
2057float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
2058{
2059 if (v_min == v_max)
2060 return 0.0f;
2061
2062 const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2063 const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2064 if (is_power)
2065 {
2066 if (v_clamped < 0.0f)
2067 {
2068 const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
2069 return (1.0f - ImPow(x: f, y: 1.0f/power)) * linear_zero_pos;
2070 }
2071 else
2072 {
2073 const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
2074 return linear_zero_pos + ImPow(x: f, y: 1.0f/power) * (1.0f - linear_zero_pos);
2075 }
2076 }
2077
2078 // Linear slider
2079 return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
2080}
2081
2082// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
2083template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2084bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2085{
2086 ImGuiContext& g = *GImGui;
2087 const ImGuiStyle& style = g.Style;
2088
2089 const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2090 const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2091 const bool is_power = (power != 1.0f) && is_decimal;
2092
2093 const float grab_padding = 2.0f;
2094 const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
2095 float grab_sz = style.GrabMinSize;
2096 SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
2097 if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows
2098 grab_sz = ImMax(lhs: (float)(slider_sz / (v_range + 1)), rhs: style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
2099 grab_sz = ImMin(lhs: grab_sz, rhs: slider_sz);
2100 const float slider_usable_sz = slider_sz - grab_sz;
2101 const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz*0.5f;
2102 const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz*0.5f;
2103
2104 // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
2105 float linear_zero_pos; // 0.0->1.0f
2106 if (is_power && v_min * v_max < 0.0f)
2107 {
2108 // Different sign
2109 const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
2110 const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
2111 linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
2112 }
2113 else
2114 {
2115 // Same sign
2116 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
2117 }
2118
2119 // Process interacting with the slider
2120 bool value_changed = false;
2121 if (g.ActiveId == id)
2122 {
2123 bool set_new_value = false;
2124 float clicked_t = 0.0f;
2125 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
2126 {
2127 if (!g.IO.MouseDown[0])
2128 {
2129 ClearActiveID();
2130 }
2131 else
2132 {
2133 const float mouse_abs_pos = g.IO.MousePos[axis];
2134 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp(v: (mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, mn: 0.0f, mx: 1.0f) : 0.0f;
2135 if (axis == ImGuiAxis_Y)
2136 clicked_t = 1.0f - clicked_t;
2137 set_new_value = true;
2138 }
2139 }
2140 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2141 {
2142 const ImVec2 delta2 = GetNavInputAmount2d(dir_sources: ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, mode: ImGuiInputReadMode_RepeatFast, slow_factor: 0.0f, fast_factor: 0.0f);
2143 float delta = (axis == ImGuiAxis_X) ? delta2.x : -delta2.y;
2144 if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2145 {
2146 ClearActiveID();
2147 }
2148 else if (delta != 0.0f)
2149 {
2150 clicked_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
2151 const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, default_value: 3) : 0;
2152 if ((decimal_precision > 0) || is_power)
2153 {
2154 delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
2155 if (IsNavInputDown(n: ImGuiNavInput_TweakSlow))
2156 delta /= 10.0f;
2157 }
2158 else
2159 {
2160 if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(n: ImGuiNavInput_TweakSlow))
2161 delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
2162 else
2163 delta /= 100.0f;
2164 }
2165 if (IsNavInputDown(n: ImGuiNavInput_TweakFast))
2166 delta *= 10.0f;
2167 set_new_value = true;
2168 if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
2169 set_new_value = false;
2170 else
2171 clicked_t = ImSaturate(f: clicked_t + delta);
2172 }
2173 }
2174
2175 if (set_new_value)
2176 {
2177 TYPE v_new;
2178 if (is_power)
2179 {
2180 // Account for power curve scale on both sides of the zero
2181 if (clicked_t < linear_zero_pos)
2182 {
2183 // Negative: rescale to the negative range before powering
2184 float a = 1.0f - (clicked_t / linear_zero_pos);
2185 a = ImPow(x: a, y: power);
2186 v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
2187 }
2188 else
2189 {
2190 // Positive: rescale to the positive range before powering
2191 float a;
2192 if (ImFabs(x: linear_zero_pos - 1.0f) > 1.e-6f)
2193 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
2194 else
2195 a = clicked_t;
2196 a = ImPow(x: a, y: power);
2197 v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
2198 }
2199 }
2200 else
2201 {
2202 // Linear slider
2203 if (is_decimal)
2204 {
2205 v_new = ImLerp(v_min, v_max, clicked_t);
2206 }
2207 else
2208 {
2209 // For integer values we want the clicking position to match the grab box so we round above
2210 // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2211 FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
2212 TYPE v_new_off_floor = (TYPE)(v_new_off_f);
2213 TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
2214 if (!is_decimal && v_new_off_floor < v_new_off_round)
2215 v_new = v_min + v_new_off_round;
2216 else
2217 v_new = v_min + v_new_off_floor;
2218 }
2219 }
2220
2221 // Round to user desired precision based on format string
2222 v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);
2223
2224 // Apply result
2225 if (*v != v_new)
2226 {
2227 *v = v_new;
2228 value_changed = true;
2229 }
2230 }
2231 }
2232
2233 // Output grab position so it can be displayed by the caller
2234 float grab_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
2235 if (axis == ImGuiAxis_Y)
2236 grab_t = 1.0f - grab_t;
2237 const float grab_pos = ImLerp(a: slider_usable_pos_min, b: slider_usable_pos_max, t: grab_t);
2238 if (axis == ImGuiAxis_X)
2239 *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding);
2240 else
2241 *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f);
2242
2243 return value_changed;
2244}
2245
2246// For 32-bits and larger types, slider bounds are limited to half the natural type range.
2247// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
2248// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
2249bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2250{
2251 switch (data_type)
2252 {
2253 case ImGuiDataType_S32:
2254 IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
2255 return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, v: (ImS32*)v, v_min: *(const ImS32*)v_min, v_max: *(const ImS32*)v_max, format, power, flags, out_grab_bb);
2256 case ImGuiDataType_U32:
2257 IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
2258 return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, v: (ImU32*)v, v_min: *(const ImU32*)v_min, v_max: *(const ImU32*)v_max, format, power, flags, out_grab_bb);
2259 case ImGuiDataType_S64:
2260 IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
2261 return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, v: (ImS64*)v, v_min: *(const ImS64*)v_min, v_max: *(const ImS64*)v_max, format, power, flags, out_grab_bb);
2262 case ImGuiDataType_U64:
2263 IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
2264 return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, v: (ImU64*)v, v_min: *(const ImU64*)v_min, v_max: *(const ImU64*)v_max, format, power, flags, out_grab_bb);
2265 case ImGuiDataType_Float:
2266 IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
2267 return SliderBehaviorT<float, float, float >(bb, id, data_type, v: (float*)v, v_min: *(const float*)v_min, v_max: *(const float*)v_max, format, power, flags, out_grab_bb);
2268 case ImGuiDataType_Double:
2269 IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
2270 return SliderBehaviorT<double,double,double>(bb, id, data_type, v: (double*)v, v_min: *(const double*)v_min, v_max: *(const double*)v_max, format, power, flags, out_grab_bb);
2271 case ImGuiDataType_COUNT: break;
2272 }
2273 IM_ASSERT(0);
2274 return false;
2275}
2276
2277bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
2278{
2279 ImGuiWindow* window = GetCurrentWindow();
2280 if (window->SkipItems)
2281 return false;
2282
2283 ImGuiContext& g = *GImGui;
2284 const ImGuiStyle& style = g.Style;
2285 const ImGuiID id = window->GetID(str: label);
2286 const float w = CalcItemWidth();
2287
2288 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
2289 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
2290 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));
2291
2292 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
2293 if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb))
2294 {
2295 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
2296 return false;
2297 }
2298
2299 // Default format string when passing NULL
2300 // Patch old "%.0f" format string to use "%d", read function comments for more details.
2301 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2302 if (format == NULL)
2303 format = GDataTypeInfo[data_type].PrintFmt;
2304 else if (data_type == ImGuiDataType_S32 && strcmp(s1: format, s2: "%d") != 0)
2305 format = PatchFormatStringFloatToInt(fmt: format);
2306
2307 // Tabbing or CTRL-clicking on Slider turns it into an input box
2308 bool start_text_input = false;
2309 const bool tab_focus_requested = FocusableItemRegister(window, id);
2310 const bool hovered = ItemHoverable(bb: frame_bb, id);
2311 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
2312 {
2313 SetActiveID(id, window);
2314 SetFocusID(id, window);
2315 FocusWindow(window);
2316 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
2317 if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
2318 {
2319 start_text_input = true;
2320 g.ScalarAsInputTextId = 0;
2321 }
2322 }
2323 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
2324 {
2325 FocusableItemUnregister(window);
2326 return InputScalarAsWidgetReplacement(bb: frame_bb, id, label, data_type, data_ptr: v, format);
2327 }
2328
2329 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
2330
2331 // Draw frame
2332 const ImU32 frame_col = GetColorU32(idx: g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2333 RenderNavHighlight(bb: frame_bb, id);
2334 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: frame_col, border: true, rounding: g.Style.FrameRounding);
2335
2336 // Slider behavior
2337 ImRect grab_bb;
2338 const bool value_changed = SliderBehavior(bb: frame_bb, id, data_type, v, v_min, v_max, format, power, flags: ImGuiSliderFlags_None, out_grab_bb: &grab_bb);
2339 if (value_changed)
2340 MarkItemEdited(id);
2341
2342 // Render grab
2343 window->DrawList->AddRectFilled(a: grab_bb.Min, b: grab_bb.Max, col: GetColorU32(idx: g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), rounding: style.GrabRounding);
2344
2345 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2346 char value_buf[64];
2347 const char* value_buf_end = value_buf + DataTypeFormatString(buf: value_buf, IM_ARRAYSIZE(value_buf), data_type, data_ptr: v, format);
2348 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));
2349
2350 if (label_size.x > 0.0f)
2351 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
2352
2353 return value_changed;
2354}
2355
2356// Add multiple sliders on 1 line for compact edition of multiple components
2357bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
2358{
2359 ImGuiWindow* window = GetCurrentWindow();
2360 if (window->SkipItems)
2361 return false;
2362
2363 ImGuiContext& g = *GImGui;
2364 bool value_changed = false;
2365 BeginGroup();
2366 PushID(str_id: label);
2367 PushMultiItemsWidths(components);
2368 size_t type_size = GDataTypeInfo[data_type].Size;
2369 for (int i = 0; i < components; i++)
2370 {
2371 PushID(int_id: i);
2372 value_changed |= SliderScalar(label: "##v", data_type, v, v_min, v_max, format, power);
2373 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
2374 PopID();
2375 PopItemWidth();
2376 v = (void*)((char*)v + type_size);
2377 }
2378 PopID();
2379
2380 TextUnformatted(text: label, text_end: FindRenderedTextEnd(text: label));
2381 EndGroup();
2382 return value_changed;
2383}
2384
2385bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
2386{
2387 return SliderScalar(label, data_type: ImGuiDataType_Float, v, v_min: &v_min, v_max: &v_max, format, power);
2388}
2389
2390bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
2391{
2392 return SliderScalarN(label, data_type: ImGuiDataType_Float, v, components: 2, v_min: &v_min, v_max: &v_max, format, power);
2393}
2394
2395bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
2396{
2397 return SliderScalarN(label, data_type: ImGuiDataType_Float, v, components: 3, v_min: &v_min, v_max: &v_max, format, power);
2398}
2399
2400bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
2401{
2402 return SliderScalarN(label, data_type: ImGuiDataType_Float, v, components: 4, v_min: &v_min, v_max: &v_max, format, power);
2403}
2404
2405bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format)
2406{
2407 if (format == NULL)
2408 format = "%.0f deg";
2409 float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
2410 bool value_changed = SliderFloat(label, v: &v_deg, v_min: v_degrees_min, v_max: v_degrees_max, format, power: 1.0f);
2411 *v_rad = v_deg * (2*IM_PI) / 360.0f;
2412 return value_changed;
2413}
2414
2415bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
2416{
2417 return SliderScalar(label, data_type: ImGuiDataType_S32, v, v_min: &v_min, v_max: &v_max, format);
2418}
2419
2420bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
2421{
2422 return SliderScalarN(label, data_type: ImGuiDataType_S32, v, components: 2, v_min: &v_min, v_max: &v_max, format);
2423}
2424
2425bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
2426{
2427 return SliderScalarN(label, data_type: ImGuiDataType_S32, v, components: 3, v_min: &v_min, v_max: &v_max, format);
2428}
2429
2430bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
2431{
2432 return SliderScalarN(label, data_type: ImGuiDataType_S32, v, components: 4, v_min: &v_min, v_max: &v_max, format);
2433}
2434
2435bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
2436{
2437 ImGuiWindow* window = GetCurrentWindow();
2438 if (window->SkipItems)
2439 return false;
2440
2441 ImGuiContext& g = *GImGui;
2442 const ImGuiStyle& style = g.Style;
2443 const ImGuiID id = window->GetID(str: label);
2444
2445 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
2446 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
2447 const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
2448
2449 ItemSize(bb, text_offset_y: style.FramePadding.y);
2450 if (!ItemAdd(bb: frame_bb, id))
2451 return false;
2452
2453 // Default format string when passing NULL
2454 // Patch old "%.0f" format string to use "%d", read function comments for more details.
2455 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2456 if (format == NULL)
2457 format = GDataTypeInfo[data_type].PrintFmt;
2458 else if (data_type == ImGuiDataType_S32 && strcmp(s1: format, s2: "%d") != 0)
2459 format = PatchFormatStringFloatToInt(fmt: format);
2460
2461 const bool hovered = ItemHoverable(bb: frame_bb, id);
2462 if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
2463 {
2464 SetActiveID(id, window);
2465 SetFocusID(id, window);
2466 FocusWindow(window);
2467 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2468 }
2469
2470 // Draw frame
2471 const ImU32 frame_col = GetColorU32(idx: g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? 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: g.Style.FrameRounding);
2474
2475 // Slider behavior
2476 ImRect grab_bb;
2477 const bool value_changed = SliderBehavior(bb: frame_bb, id, data_type, v, v_min, v_max, format, power, flags: ImGuiSliderFlags_Vertical, out_grab_bb: &grab_bb);
2478 if (value_changed)
2479 MarkItemEdited(id);
2480
2481 // Render grab
2482 window->DrawList->AddRectFilled(a: grab_bb.Min, b: grab_bb.Max, col: GetColorU32(idx: g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), rounding: style.GrabRounding);
2483
2484 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2485 // For the vertical slider we allow centered text to overlap the frame padding
2486 char value_buf[64];
2487 const char* value_buf_end = value_buf + DataTypeFormatString(buf: value_buf, IM_ARRAYSIZE(value_buf), data_type, data_ptr: v, format);
2488 RenderTextClipped(pos_min: ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), pos_max: frame_bb.Max, text: value_buf, text_end: value_buf_end, NULL, align: ImVec2(0.5f,0.0f));
2489 if (label_size.x > 0.0f)
2490 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
2491
2492 return value_changed;
2493}
2494
2495bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
2496{
2497 return VSliderScalar(label, size, data_type: ImGuiDataType_Float, v, v_min: &v_min, v_max: &v_max, format, power);
2498}
2499
2500bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
2501{
2502 return VSliderScalar(label, size, data_type: ImGuiDataType_S32, v, v_min: &v_min, v_max: &v_max, format);
2503}
2504
2505//-------------------------------------------------------------------------
2506// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
2507//-------------------------------------------------------------------------
2508// - ImParseFormatFindStart() [Internal]
2509// - ImParseFormatFindEnd() [Internal]
2510// - ImParseFormatTrimDecorations() [Internal]
2511// - ImParseFormatPrecision() [Internal]
2512// - InputScalarAsWidgetReplacement() [Internal]
2513// - InputScalar()
2514// - InputScalarN()
2515// - InputFloat()
2516// - InputFloat2()
2517// - InputFloat3()
2518// - InputFloat4()
2519// - InputInt()
2520// - InputInt2()
2521// - InputInt3()
2522// - InputInt4()
2523// - InputDouble()
2524//-------------------------------------------------------------------------
2525
2526// We don't use strchr() because our strings are usually very short and often start with '%'
2527const char* ImParseFormatFindStart(const char* fmt)
2528{
2529 while (char c = fmt[0])
2530 {
2531 if (c == '%' && fmt[1] != '%')
2532 return fmt;
2533 else if (c == '%')
2534 fmt++;
2535 fmt++;
2536 }
2537 return fmt;
2538}
2539
2540const char* ImParseFormatFindEnd(const char* fmt)
2541{
2542 // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
2543 if (fmt[0] != '%')
2544 return fmt;
2545 const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
2546 const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
2547 for (char c; (c = *fmt) != 0; fmt++)
2548 {
2549 if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
2550 return fmt + 1;
2551 if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
2552 return fmt + 1;
2553 }
2554 return fmt;
2555}
2556
2557// Extract the format out of a format string with leading or trailing decorations
2558// fmt = "blah blah" -> return fmt
2559// fmt = "%.3f" -> return fmt
2560// fmt = "hello %.3f" -> return fmt + 6
2561// fmt = "%.3f hello" -> return buf written with "%.3f"
2562const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size)
2563{
2564 const char* fmt_start = ImParseFormatFindStart(fmt);
2565 if (fmt_start[0] != '%')
2566 return fmt;
2567 const char* fmt_end = ImParseFormatFindEnd(fmt: fmt_start);
2568 if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
2569 return fmt_start;
2570 ImStrncpy(dst: buf, src: fmt_start, count: ImMin(lhs: (int)(fmt_end + 1 - fmt_start), rhs: buf_size));
2571 return buf;
2572}
2573
2574// Parse display precision back from the display format string
2575// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
2576int ImParseFormatPrecision(const char* fmt, int default_precision)
2577{
2578 fmt = ImParseFormatFindStart(fmt);
2579 if (fmt[0] != '%')
2580 return default_precision;
2581 fmt++;
2582 while (*fmt >= '0' && *fmt <= '9')
2583 fmt++;
2584 int precision = INT_MAX;
2585 if (*fmt == '.')
2586 {
2587 fmt = ImAtoi<int>(src: fmt + 1, output: &precision);
2588 if (precision < 0 || precision > 99)
2589 precision = default_precision;
2590 }
2591 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
2592 precision = -1;
2593 if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
2594 precision = -1;
2595 return (precision == INT_MAX) ? default_precision : precision;
2596}
2597
2598// Create text input in place of an active drag/slider (used when doing a CTRL+Click on drag/slider widgets)
2599// FIXME: Logic is awkward and confusing. This should be reworked to facilitate using in other situations.
2600bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
2601{
2602 ImGuiContext& g = *GImGui;
2603 ImGuiWindow* window = GetCurrentWindow();
2604
2605 // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
2606 // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
2607 SetActiveID(id: g.ScalarAsInputTextId, window);
2608 SetHoveredID(0);
2609 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
2610
2611 char fmt_buf[32];
2612 char data_buf[32];
2613 format = ImParseFormatTrimDecorations(fmt: format, buf: fmt_buf, IM_ARRAYSIZE(fmt_buf));
2614 DataTypeFormatString(buf: data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
2615 ImStrTrimBlanks(str: data_buf);
2616 ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
2617 bool value_changed = InputTextEx(label, buf: data_buf, IM_ARRAYSIZE(data_buf), size_arg: bb.GetSize(), flags);
2618 if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
2619 {
2620 IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID
2621 g.ScalarAsInputTextId = g.ActiveId;
2622 SetHoveredID(id);
2623 }
2624 if (value_changed)
2625 return DataTypeApplyOpFromText(buf: data_buf, initial_value_buf: g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
2626 return false;
2627}
2628
2629// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
2630bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
2631{
2632 ImGuiWindow* window = GetCurrentWindow();
2633 if (window->SkipItems)
2634 return false;
2635
2636 ImGuiContext& g = *GImGui;
2637 const ImGuiStyle& style = g.Style;
2638
2639 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2640 if (format == NULL)
2641 format = GDataTypeInfo[data_type].PrintFmt;
2642
2643 char buf[64];
2644 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
2645
2646 bool value_changed = false;
2647 if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
2648 extra_flags |= ImGuiInputTextFlags_CharsDecimal;
2649 extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
2650
2651 if (step != NULL)
2652 {
2653 const float button_size = GetFrameHeight();
2654
2655 BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
2656 PushID(str_id: label);
2657 PushItemWidth(item_width: ImMax(lhs: 1.0f, rhs: CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
2658 if (InputText(label: "", buf, IM_ARRAYSIZE(buf), flags: extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
2659 value_changed = DataTypeApplyOpFromText(buf, initial_value_buf: g.InputTextState.InitialText.Data, data_type, data_ptr, format);
2660 PopItemWidth();
2661
2662 // Step buttons
2663 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
2664 if (ButtonEx(label: "-", size_arg: ImVec2(button_size, button_size), flags: ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
2665 {
2666 DataTypeApplyOp(data_type, op: '-', output: data_ptr, arg1: data_ptr, arg2: g.IO.KeyCtrl && step_fast ? step_fast : step);
2667 value_changed = true;
2668 }
2669 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
2670 if (ButtonEx(label: "+", size_arg: ImVec2(button_size, button_size), flags: ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
2671 {
2672 DataTypeApplyOp(data_type, op: '+', output: data_ptr, arg1: data_ptr, arg2: g.IO.KeyCtrl && step_fast ? step_fast : step);
2673 value_changed = true;
2674 }
2675 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
2676 TextUnformatted(text: label, text_end: FindRenderedTextEnd(text: label));
2677
2678 PopID();
2679 EndGroup();
2680 }
2681 else
2682 {
2683 if (InputText(label, buf, IM_ARRAYSIZE(buf), flags: extra_flags))
2684 value_changed = DataTypeApplyOpFromText(buf, initial_value_buf: g.InputTextState.InitialText.Data, data_type, data_ptr, format);
2685 }
2686
2687 return value_changed;
2688}
2689
2690bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
2691{
2692 ImGuiWindow* window = GetCurrentWindow();
2693 if (window->SkipItems)
2694 return false;
2695
2696 ImGuiContext& g = *GImGui;
2697 bool value_changed = false;
2698 BeginGroup();
2699 PushID(str_id: label);
2700 PushMultiItemsWidths(components);
2701 size_t type_size = GDataTypeInfo[data_type].Size;
2702 for (int i = 0; i < components; i++)
2703 {
2704 PushID(int_id: i);
2705 value_changed |= InputScalar(label: "##v", data_type, data_ptr: v, step, step_fast, format, extra_flags);
2706 SameLine(pos_x: 0, spacing_w: g.Style.ItemInnerSpacing.x);
2707 PopID();
2708 PopItemWidth();
2709 v = (void*)((char*)v + type_size);
2710 }
2711 PopID();
2712
2713 TextUnformatted(text: label, text_end: FindRenderedTextEnd(text: label));
2714 EndGroup();
2715 return value_changed;
2716}
2717
2718bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags)
2719{
2720 extra_flags |= ImGuiInputTextFlags_CharsScientific;
2721 return InputScalar(label, data_type: ImGuiDataType_Float, data_ptr: (void*)v, step: (void*)(step>0.0f ? &step : NULL), step_fast: (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags);
2722}
2723
2724bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags)
2725{
2726 return InputScalarN(label, data_type: ImGuiDataType_Float, v, components: 2, NULL, NULL, format, extra_flags);
2727}
2728
2729bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags)
2730{
2731 return InputScalarN(label, data_type: ImGuiDataType_Float, v, components: 3, NULL, NULL, format, extra_flags);
2732}
2733
2734bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags)
2735{
2736 return InputScalarN(label, data_type: ImGuiDataType_Float, v, components: 4, NULL, NULL, format, extra_flags);
2737}
2738
2739// Prefer using "const char* format" directly, which is more flexible and consistent with other API.
2740#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2741bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
2742{
2743 char format[16] = "%f";
2744 if (decimal_precision >= 0)
2745 ImFormatString(buf: format, IM_ARRAYSIZE(format), fmt: "%%.%df", decimal_precision);
2746 return InputFloat(label, v, step, step_fast, format, extra_flags);
2747}
2748
2749bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
2750{
2751 char format[16] = "%f";
2752 if (decimal_precision >= 0)
2753 ImFormatString(buf: format, IM_ARRAYSIZE(format), fmt: "%%.%df", decimal_precision);
2754 return InputScalarN(label, data_type: ImGuiDataType_Float, v, components: 2, NULL, NULL, format, extra_flags);
2755}
2756
2757bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
2758{
2759 char format[16] = "%f";
2760 if (decimal_precision >= 0)
2761 ImFormatString(buf: format, IM_ARRAYSIZE(format), fmt: "%%.%df", decimal_precision);
2762 return InputScalarN(label, data_type: ImGuiDataType_Float, v, components: 3, NULL, NULL, format, extra_flags);
2763}
2764
2765bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
2766{
2767 char format[16] = "%f";
2768 if (decimal_precision >= 0)
2769 ImFormatString(buf: format, IM_ARRAYSIZE(format), fmt: "%%.%df", decimal_precision);
2770 return InputScalarN(label, data_type: ImGuiDataType_Float, v, components: 4, NULL, NULL, format, extra_flags);
2771}
2772#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2773
2774bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
2775{
2776 // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
2777 const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
2778 return InputScalar(label, data_type: ImGuiDataType_S32, data_ptr: (void*)v, step: (void*)(step>0 ? &step : NULL), step_fast: (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags);
2779}
2780
2781bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
2782{
2783 return InputScalarN(label, data_type: ImGuiDataType_S32, v, components: 2, NULL, NULL, format: "%d", extra_flags);
2784}
2785
2786bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
2787{
2788 return InputScalarN(label, data_type: ImGuiDataType_S32, v, components: 3, NULL, NULL, format: "%d", extra_flags);
2789}
2790
2791bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
2792{
2793 return InputScalarN(label, data_type: ImGuiDataType_S32, v, components: 4, NULL, NULL, format: "%d", extra_flags);
2794}
2795
2796bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags)
2797{
2798 extra_flags |= ImGuiInputTextFlags_CharsScientific;
2799 return InputScalar(label, data_type: ImGuiDataType_Double, data_ptr: (void*)v, step: (void*)(step>0.0 ? &step : NULL), step_fast: (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags);
2800}
2801
2802//-------------------------------------------------------------------------
2803// [SECTION] Widgets: InputText, InputTextMultiline
2804//-------------------------------------------------------------------------
2805// - InputText()
2806// - InputTextMultiline()
2807// - InputTextEx() [Internal]
2808//-------------------------------------------------------------------------
2809
2810bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
2811{
2812 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
2813 return InputTextEx(label, buf, buf_size: (int)buf_size, size_arg: ImVec2(0,0), flags, callback, user_data);
2814}
2815
2816bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
2817{
2818 return InputTextEx(label, buf, buf_size: (int)buf_size, size_arg: size, flags: flags | ImGuiInputTextFlags_Multiline, callback, user_data);
2819}
2820
2821static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
2822{
2823 int line_count = 0;
2824 const char* s = text_begin;
2825 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
2826 if (c == '\n')
2827 line_count++;
2828 s--;
2829 if (s[0] != '\n' && s[0] != '\r')
2830 line_count++;
2831 *out_text_end = s;
2832 return line_count;
2833}
2834
2835static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
2836{
2837 ImFont* font = GImGui->Font;
2838 const float line_height = GImGui->FontSize;
2839 const float scale = line_height / font->FontSize;
2840
2841 ImVec2 text_size = ImVec2(0,0);
2842 float line_width = 0.0f;
2843
2844 const ImWchar* s = text_begin;
2845 while (s < text_end)
2846 {
2847 unsigned int c = (unsigned int)(*s++);
2848 if (c == '\n')
2849 {
2850 text_size.x = ImMax(lhs: text_size.x, rhs: line_width);
2851 text_size.y += line_height;
2852 line_width = 0.0f;
2853 if (stop_on_new_line)
2854 break;
2855 continue;
2856 }
2857 if (c == '\r')
2858 continue;
2859
2860 const float char_width = font->GetCharAdvance(c: (ImWchar)c) * scale;
2861 line_width += char_width;
2862 }
2863
2864 if (text_size.x < line_width)
2865 text_size.x = line_width;
2866
2867 if (out_offset)
2868 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
2869
2870 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
2871 text_size.y += line_height;
2872
2873 if (remaining)
2874 *remaining = s;
2875
2876 return text_size;
2877}
2878
2879// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
2880namespace ImGuiStb
2881{
2882
2883static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
2884static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; }
2885static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
2886static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
2887static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
2888static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
2889{
2890 const ImWchar* text = obj->TextW.Data;
2891 const ImWchar* text_remaining = NULL;
2892 const ImVec2 size = InputTextCalcTextSizeW(text_begin: text + line_start_idx, text_end: text + obj->CurLenW, remaining: &text_remaining, NULL, stop_on_new_line: true);
2893 r->x0 = 0.0f;
2894 r->x1 = size.x;
2895 r->baseline_y_delta = size.y;
2896 r->ymin = 0.0f;
2897 r->ymax = size.y;
2898 r->num_chars = (int)(text_remaining - (text + line_start_idx));
2899}
2900
2901static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
2902static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( c: obj->TextW[idx-1] ) && !is_separator( c: obj->TextW[idx] ) ) : 1; }
2903static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
2904#ifdef __APPLE__ // FIXME: Move setting to IO structure
2905static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
2906static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
2907#else
2908static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
2909#endif
2910#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
2911#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
2912
2913static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
2914{
2915 ImWchar* dst = obj->TextW.Data + pos;
2916
2917 // We maintain our buffer length in both UTF-8 and wchar formats
2918 obj->CurLenA -= ImTextCountUtf8BytesFromStr(in_text: dst, in_text_end: dst + n);
2919 obj->CurLenW -= n;
2920
2921 // Offset remaining text (FIXME-OPT: Use memmove)
2922 const ImWchar* src = obj->TextW.Data + pos + n;
2923 while (ImWchar c = *src++)
2924 *dst++ = c;
2925 *dst = '\0';
2926}
2927
2928static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
2929{
2930 const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
2931 const int text_len = obj->CurLenW;
2932 IM_ASSERT(pos <= text_len);
2933
2934 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(in_text: new_text, in_text_end: new_text + new_text_len);
2935 if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
2936 return false;
2937
2938 // Grow internal buffer if needed
2939 if (new_text_len + text_len + 1 > obj->TextW.Size)
2940 {
2941 if (!is_resizable)
2942 return false;
2943 IM_ASSERT(text_len < obj->TextW.Size);
2944 obj->TextW.resize(new_size: text_len + ImClamp(v: new_text_len * 4, mn: 32, mx: ImMax(lhs: 256, rhs: new_text_len)) + 1);
2945 }
2946
2947 ImWchar* text = obj->TextW.Data;
2948 if (pos != text_len)
2949 memmove(dest: text + pos + new_text_len, src: text + pos, n: (size_t)(text_len - pos) * sizeof(ImWchar));
2950 memcpy(dest: text + pos, src: new_text, n: (size_t)new_text_len * sizeof(ImWchar));
2951
2952 obj->CurLenW += new_text_len;
2953 obj->CurLenA += new_text_len_utf8;
2954 obj->TextW[obj->CurLenW] = '\0';
2955
2956 return true;
2957}
2958
2959// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
2960#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
2961#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
2962#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
2963#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
2964#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
2965#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
2966#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
2967#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
2968#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
2969#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
2970#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
2971#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
2972#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
2973#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
2974#define STB_TEXTEDIT_K_SHIFT 0x20000
2975
2976#define STB_TEXTEDIT_IMPLEMENTATION
2977#include "imstb_textedit.h"
2978
2979}
2980
2981void ImGuiInputTextState::OnKeyPressed(int key)
2982{
2983 stb_textedit_key(str: this, state: &StbState, key);
2984 CursorFollow = true;
2985 CursorAnimReset();
2986}
2987
2988ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
2989{
2990 memset(s: this, c: 0, n: sizeof(*this));
2991}
2992
2993// Public API to manipulate UTF-8 text
2994// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
2995// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
2996void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
2997{
2998 IM_ASSERT(pos + bytes_count <= BufTextLen);
2999 char* dst = Buf + pos;
3000 const char* src = Buf + pos + bytes_count;
3001 while (char c = *src++)
3002 *dst++ = c;
3003 *dst = '\0';
3004
3005 if (CursorPos + bytes_count >= pos)
3006 CursorPos -= bytes_count;
3007 else if (CursorPos >= pos)
3008 CursorPos = pos;
3009 SelectionStart = SelectionEnd = CursorPos;
3010 BufDirty = true;
3011 BufTextLen -= bytes_count;
3012}
3013
3014void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
3015{
3016 const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
3017 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(s: new_text);
3018 if (new_text_len + BufTextLen >= BufSize)
3019 {
3020 if (!is_resizable)
3021 return;
3022
3023 // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
3024 ImGuiContext& g = *GImGui;
3025 ImGuiInputTextState* edit_state = &g.InputTextState;
3026 IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
3027 IM_ASSERT(Buf == edit_state->TempBuffer.Data);
3028 int new_buf_size = BufTextLen + ImClamp(v: new_text_len * 4, mn: 32, mx: ImMax(lhs: 256, rhs: new_text_len)) + 1;
3029 edit_state->TempBuffer.reserve(new_capacity: new_buf_size + 1);
3030 Buf = edit_state->TempBuffer.Data;
3031 BufSize = edit_state->BufCapacityA = new_buf_size;
3032 }
3033
3034 if (BufTextLen != pos)
3035 memmove(dest: Buf + pos + new_text_len, src: Buf + pos, n: (size_t)(BufTextLen - pos));
3036 memcpy(dest: Buf + pos, src: new_text, n: (size_t)new_text_len * sizeof(char));
3037 Buf[BufTextLen + new_text_len] = '\0';
3038
3039 if (CursorPos >= pos)
3040 CursorPos += new_text_len;
3041 SelectionStart = SelectionEnd = CursorPos;
3042 BufDirty = true;
3043 BufTextLen += new_text_len;
3044}
3045
3046// Return false to discard a character.
3047static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3048{
3049 unsigned int c = *p_char;
3050
3051 if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
3052 {
3053 bool pass = false;
3054 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
3055 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
3056 if (!pass)
3057 return false;
3058 }
3059
3060 if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
3061 return false;
3062
3063 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
3064 {
3065 if (flags & ImGuiInputTextFlags_CharsDecimal)
3066 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
3067 return false;
3068
3069 if (flags & ImGuiInputTextFlags_CharsScientific)
3070 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
3071 return false;
3072
3073 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
3074 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
3075 return false;
3076
3077 if (flags & ImGuiInputTextFlags_CharsUppercase)
3078 if (c >= 'a' && c <= 'z')
3079 *p_char = (c += (unsigned int)('A'-'a'));
3080
3081 if (flags & ImGuiInputTextFlags_CharsNoBlank)
3082 if (ImCharIsBlankW(c))
3083 return false;
3084 }
3085
3086 if (flags & ImGuiInputTextFlags_CallbackCharFilter)
3087 {
3088 ImGuiInputTextCallbackData callback_data;
3089 memset(s: &callback_data, c: 0, n: sizeof(ImGuiInputTextCallbackData));
3090 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
3091 callback_data.EventChar = (ImWchar)c;
3092 callback_data.Flags = flags;
3093 callback_data.UserData = user_data;
3094 if (callback(&callback_data) != 0)
3095 return false;
3096 *p_char = callback_data.EventChar;
3097 if (!callback_data.EventChar)
3098 return false;
3099 }
3100
3101 return true;
3102}
3103
3104// Edit a string of text
3105// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
3106// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
3107// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
3108// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
3109// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
3110// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
3111bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
3112{
3113 ImGuiWindow* window = GetCurrentWindow();
3114 if (window->SkipItems)
3115 return false;
3116
3117 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
3118 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
3119
3120 ImGuiContext& g = *GImGui;
3121 const ImGuiIO& io = g.IO;
3122 const ImGuiStyle& style = g.Style;
3123
3124 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
3125 const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
3126 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
3127 const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
3128 const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
3129 if (is_resizable)
3130 IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
3131
3132 if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
3133 BeginGroup();
3134 const ImGuiID id = window->GetID(str: label);
3135 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
3136 ImVec2 size = CalcItemSize(size: size_arg, default_x: CalcItemWidth(), default_y: (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
3137 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3138 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));
3139
3140 ImGuiWindow* draw_window = window;
3141 if (is_multiline)
3142 {
3143 ItemAdd(bb: total_bb, id, nav_bb: &frame_bb);
3144 if (!BeginChildFrame(id, size: frame_bb.GetSize()))
3145 {
3146 EndChildFrame();
3147 EndGroup();
3148 return false;
3149 }
3150 draw_window = GetCurrentWindow();
3151 draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
3152 size.x -= draw_window->ScrollbarSizes.x;
3153 }
3154 else
3155 {
3156 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
3157 if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb))
3158 return false;
3159 }
3160 const bool hovered = ItemHoverable(bb: frame_bb, id);
3161 if (hovered)
3162 g.MouseCursor = ImGuiMouseCursor_TextInput;
3163
3164 // Password pushes a temporary font with only a fallback glyph
3165 if (is_password)
3166 {
3167 const ImFontGlyph* glyph = g.Font->FindGlyph(c: '*');
3168 ImFont* password_font = &g.InputTextPasswordFont;
3169 password_font->FontSize = g.Font->FontSize;
3170 password_font->Scale = g.Font->Scale;
3171 password_font->DisplayOffset = g.Font->DisplayOffset;
3172 password_font->Ascent = g.Font->Ascent;
3173 password_font->Descent = g.Font->Descent;
3174 password_font->ContainerAtlas = g.Font->ContainerAtlas;
3175 password_font->FallbackGlyph = glyph;
3176 password_font->FallbackAdvanceX = glyph->AdvanceX;
3177 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
3178 PushFont(font: password_font);
3179 }
3180
3181 // NB: we are only allowed to access 'edit_state' if we are the active widget.
3182 ImGuiInputTextState& edit_state = g.InputTextState;
3183
3184 const bool focus_requested = FocusableItemRegister(window, id, tab_stop: (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
3185 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
3186 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
3187
3188 const bool user_clicked = hovered && io.MouseClicked[0];
3189 const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive(str: "#SCROLLY");
3190 const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
3191
3192 bool clear_active_id = false;
3193
3194 bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
3195 if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
3196 {
3197 if (g.ActiveId != id)
3198 {
3199 // Start edition
3200 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
3201 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
3202 const int prev_len_w = edit_state.CurLenW;
3203 const int init_buf_len = (int)strlen(s: buf);
3204 edit_state.TextW.resize(new_size: buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
3205 edit_state.InitialText.resize(new_size: init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
3206 memcpy(dest: edit_state.InitialText.Data, src: buf, n: init_buf_len + 1);
3207 const char* buf_end = NULL;
3208 edit_state.CurLenW = ImTextStrFromUtf8(buf: edit_state.TextW.Data, buf_size, in_text: buf, NULL, in_remaining: &buf_end);
3209 edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
3210 edit_state.CursorAnimReset();
3211
3212 // Preserve cursor position and undo/redo stack if we come back to same widget
3213 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
3214 const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
3215 if (recycle_state)
3216 {
3217 // Recycle existing cursor/selection/undo stack but clamp position
3218 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
3219 edit_state.CursorClamp();
3220 }
3221 else
3222 {
3223 edit_state.ID = id;
3224 edit_state.ScrollX = 0.0f;
3225 stb_textedit_initialize_state(state: &edit_state.StbState, is_single_line: !is_multiline);
3226 if (!is_multiline && focus_requested_by_code)
3227 select_all = true;
3228 }
3229 if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
3230 edit_state.StbState.insert_mode = true;
3231 if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
3232 select_all = true;
3233 }
3234 SetActiveID(id, window);
3235 SetFocusID(id, window);
3236 FocusWindow(window);
3237 if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
3238 g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
3239 }
3240 else if (io.MouseClicked[0])
3241 {
3242 // Release focus when we click outside
3243 clear_active_id = true;
3244 }
3245
3246 bool value_changed = false;
3247 bool enter_pressed = false;
3248 int backup_current_text_length = 0;
3249
3250 if (g.ActiveId == id)
3251 {
3252 if (!is_editable && !g.ActiveIdIsJustActivated)
3253 {
3254 // When read-only we always use the live data passed to the function
3255 edit_state.TextW.resize(new_size: buf_size+1);
3256 const char* buf_end = NULL;
3257 edit_state.CurLenW = ImTextStrFromUtf8(buf: edit_state.TextW.Data, buf_size: edit_state.TextW.Size, in_text: buf, NULL, in_remaining: &buf_end);
3258 edit_state.CurLenA = (int)(buf_end - buf);
3259 edit_state.CursorClamp();
3260 }
3261
3262 backup_current_text_length = edit_state.CurLenA;
3263 edit_state.BufCapacityA = buf_size;
3264 edit_state.UserFlags = flags;
3265 edit_state.UserCallback = callback;
3266 edit_state.UserCallbackData = callback_user_data;
3267
3268 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
3269 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
3270 g.ActiveIdAllowOverlap = !io.MouseDown[0];
3271 g.WantTextInputNextFrame = 1;
3272
3273 // Edit in progress
3274 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
3275 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
3276
3277 const bool is_osx = io.ConfigMacOSXBehaviors;
3278 if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
3279 {
3280 edit_state.SelectAll();
3281 edit_state.SelectedAllMouseLock = true;
3282 }
3283 else if (hovered && is_osx && io.MouseDoubleClicked[0])
3284 {
3285 // Double-click select a word only, OS X style (by simulating keystrokes)
3286 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
3287 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
3288 }
3289 else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
3290 {
3291 if (hovered)
3292 {
3293 stb_textedit_click(str: &edit_state, state: &edit_state.StbState, x: mouse_x, y: mouse_y);
3294 edit_state.CursorAnimReset();
3295 }
3296 }
3297 else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
3298 {
3299 stb_textedit_drag(str: &edit_state, state: &edit_state.StbState, x: mouse_x, y: mouse_y);
3300 edit_state.CursorAnimReset();
3301 edit_state.CursorFollow = true;
3302 }
3303 if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
3304 edit_state.SelectedAllMouseLock = false;
3305
3306 if (io.InputCharacters[0])
3307 {
3308 // Process text input (before we check for Return because using some IME will effectively send a Return?)
3309 // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
3310 bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
3311 if (!ignore_inputs && is_editable && !user_nav_input_start)
3312 for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
3313 {
3314 // Insert character if they pass filtering
3315 unsigned int c = (unsigned int)io.InputCharacters[n];
3316 if (InputTextFilterCharacter(p_char: &c, flags, callback, user_data: callback_user_data))
3317 edit_state.OnKeyPressed(key: (int)c);
3318 }
3319
3320 // Consume characters
3321 memset(s: g.IO.InputCharacters, c: 0, n: sizeof(g.IO.InputCharacters));
3322 }
3323 }
3324
3325 bool cancel_edit = false;
3326 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
3327 {
3328 // Handle key-presses
3329 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
3330 const bool is_osx = io.ConfigMacOSXBehaviors;
3331 const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
3332 const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
3333 const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
3334 const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
3335 const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
3336 const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
3337
3338 const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(key: ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(key: ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
3339 const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(key: ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(key: ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
3340 const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(key: ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(key: ImGuiKey_Insert))) && is_editable;
3341 const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(key: ImGuiKey_Z)) && is_editable && is_undoable);
3342 const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(key: ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(key: ImGuiKey_Z))) && is_editable && is_undoable;
3343
3344 if (IsKeyPressedMap(key: ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(key: (is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
3345 else if (IsKeyPressedMap(key: ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(key: (is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
3346 else if (IsKeyPressedMap(key: ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(window: draw_window, new_scroll_y: ImMax(lhs: draw_window->Scroll.y - g.FontSize, rhs: 0.0f)); else edit_state.OnKeyPressed(key: (is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
3347 else if (IsKeyPressedMap(key: ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(window: draw_window, new_scroll_y: ImMin(lhs: draw_window->Scroll.y + g.FontSize, rhs: GetScrollMaxY())); else edit_state.OnKeyPressed(key: (is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
3348 else if (IsKeyPressedMap(key: ImGuiKey_Home)) { edit_state.OnKeyPressed(key: io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
3349 else if (IsKeyPressedMap(key: ImGuiKey_End)) { edit_state.OnKeyPressed(key: io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
3350 else if (IsKeyPressedMap(key: ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
3351 else if (IsKeyPressedMap(key: ImGuiKey_Backspace) && is_editable)
3352 {
3353 if (!edit_state.HasSelection())
3354 {
3355 if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
3356 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
3357 }
3358 edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
3359 }
3360 else if (IsKeyPressedMap(key: ImGuiKey_Enter))
3361 {
3362 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
3363 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
3364 {
3365 enter_pressed = clear_active_id = true;
3366 }
3367 else if (is_editable)
3368 {
3369 unsigned int c = '\n'; // Insert new line
3370 if (InputTextFilterCharacter(p_char: &c, flags, callback, user_data: callback_user_data))
3371 edit_state.OnKeyPressed(key: (int)c);
3372 }
3373 }
3374 else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(key: ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
3375 {
3376 unsigned int c = '\t'; // Insert TAB
3377 if (InputTextFilterCharacter(p_char: &c, flags, callback, user_data: callback_user_data))
3378 edit_state.OnKeyPressed(key: (int)c);
3379 }
3380 else if (IsKeyPressedMap(key: ImGuiKey_Escape))
3381 {
3382 clear_active_id = cancel_edit = true;
3383 }
3384 else if (is_undo || is_redo)
3385 {
3386 edit_state.OnKeyPressed(key: is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
3387 edit_state.ClearSelection();
3388 }
3389 else if (is_shortcut_key && IsKeyPressedMap(key: ImGuiKey_A))
3390 {
3391 edit_state.SelectAll();
3392 edit_state.CursorFollow = true;
3393 }
3394 else if (is_cut || is_copy)
3395 {
3396 // Cut, Copy
3397 if (io.SetClipboardTextFn)
3398 {
3399 const int ib = edit_state.HasSelection() ? ImMin(lhs: edit_state.StbState.select_start, rhs: edit_state.StbState.select_end) : 0;
3400 const int ie = edit_state.HasSelection() ? ImMax(lhs: edit_state.StbState.select_start, rhs: edit_state.StbState.select_end) : edit_state.CurLenW;
3401 edit_state.TempBuffer.resize(new_size: (ie-ib) * 4 + 1);
3402 ImTextStrToUtf8(buf: edit_state.TempBuffer.Data, buf_size: edit_state.TempBuffer.Size, in_text: edit_state.TextW.Data+ib, in_text_end: edit_state.TextW.Data+ie);
3403 SetClipboardText(edit_state.TempBuffer.Data);
3404 }
3405 if (is_cut)
3406 {
3407 if (!edit_state.HasSelection())
3408 edit_state.SelectAll();
3409 edit_state.CursorFollow = true;
3410 stb_textedit_cut(str: &edit_state, state: &edit_state.StbState);
3411 }
3412 }
3413 else if (is_paste)
3414 {
3415 if (const char* clipboard = GetClipboardText())
3416 {
3417 // Filter pasted buffer
3418 const int clipboard_len = (int)strlen(s: clipboard);
3419 ImWchar* clipboard_filtered = (ImWchar*)MemAlloc(size: (clipboard_len+1) * sizeof(ImWchar));
3420 int clipboard_filtered_len = 0;
3421 for (const char* s = clipboard; *s; )
3422 {
3423 unsigned int c;
3424 s += ImTextCharFromUtf8(out_char: &c, in_text: s, NULL);
3425 if (c == 0)
3426 break;
3427 if (c >= 0x10000 || !InputTextFilterCharacter(p_char: &c, flags, callback, user_data: callback_user_data))
3428 continue;
3429 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
3430 }
3431 clipboard_filtered[clipboard_filtered_len] = 0;
3432 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
3433 {
3434 stb_textedit_paste(str: &edit_state, state: &edit_state.StbState, ctext: clipboard_filtered, len: clipboard_filtered_len);
3435 edit_state.CursorFollow = true;
3436 }
3437 MemFree(ptr: clipboard_filtered);
3438 }
3439 }
3440 }
3441
3442 if (g.ActiveId == id)
3443 {
3444 const char* apply_new_text = NULL;
3445 int apply_new_text_length = 0;
3446 if (cancel_edit)
3447 {
3448 // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
3449 if (is_editable && strcmp(s1: buf, s2: edit_state.InitialText.Data) != 0)
3450 {
3451 apply_new_text = edit_state.InitialText.Data;
3452 apply_new_text_length = edit_state.InitialText.Size - 1;
3453 }
3454 }
3455
3456 // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
3457 // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
3458 bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
3459 if (apply_edit_back_to_user_buffer)
3460 {
3461 // Apply new value immediately - copy modified buffer back
3462 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
3463 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
3464 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
3465 if (is_editable)
3466 {
3467 edit_state.TempBuffer.resize(new_size: edit_state.TextW.Size * 4 + 1);
3468 ImTextStrToUtf8(buf: edit_state.TempBuffer.Data, buf_size: edit_state.TempBuffer.Size, in_text: edit_state.TextW.Data, NULL);
3469 }
3470
3471 // User callback
3472 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
3473 {
3474 IM_ASSERT(callback != NULL);
3475
3476 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
3477 ImGuiInputTextFlags event_flag = 0;
3478 ImGuiKey event_key = ImGuiKey_COUNT;
3479 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(key: ImGuiKey_Tab))
3480 {
3481 event_flag = ImGuiInputTextFlags_CallbackCompletion;
3482 event_key = ImGuiKey_Tab;
3483 }
3484 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(key: ImGuiKey_UpArrow))
3485 {
3486 event_flag = ImGuiInputTextFlags_CallbackHistory;
3487 event_key = ImGuiKey_UpArrow;
3488 }
3489 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(key: ImGuiKey_DownArrow))
3490 {
3491 event_flag = ImGuiInputTextFlags_CallbackHistory;
3492 event_key = ImGuiKey_DownArrow;
3493 }
3494 else if (flags & ImGuiInputTextFlags_CallbackAlways)
3495 event_flag = ImGuiInputTextFlags_CallbackAlways;
3496
3497 if (event_flag)
3498 {
3499 ImGuiInputTextCallbackData callback_data;
3500 memset(s: &callback_data, c: 0, n: sizeof(ImGuiInputTextCallbackData));
3501 callback_data.EventFlag = event_flag;
3502 callback_data.Flags = flags;
3503 callback_data.UserData = callback_user_data;
3504
3505 callback_data.EventKey = event_key;
3506 callback_data.Buf = edit_state.TempBuffer.Data;
3507 callback_data.BufTextLen = edit_state.CurLenA;
3508 callback_data.BufSize = edit_state.BufCapacityA;
3509 callback_data.BufDirty = false;
3510
3511 // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
3512 ImWchar* text = edit_state.TextW.Data;
3513 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(in_text: text, in_text_end: text + edit_state.StbState.cursor);
3514 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(in_text: text, in_text_end: text + edit_state.StbState.select_start);
3515 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(in_text: text, in_text_end: text + edit_state.StbState.select_end);
3516
3517 // Call user code
3518 callback(&callback_data);
3519
3520 // Read back what user may have modified
3521 IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields
3522 IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
3523 IM_ASSERT(callback_data.Flags == flags);
3524 if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(in_text: callback_data.Buf, in_text_end: callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
3525 if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(in_text: callback_data.Buf, in_text_end: callback_data.Buf + callback_data.SelectionStart); }
3526 if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(in_text: callback_data.Buf, in_text_end: callback_data.Buf + callback_data.SelectionEnd); }
3527 if (callback_data.BufDirty)
3528 {
3529 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
3530 if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
3531 edit_state.TextW.resize(new_size: edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
3532 edit_state.CurLenW = ImTextStrFromUtf8(buf: edit_state.TextW.Data, buf_size: edit_state.TextW.Size, in_text: callback_data.Buf, NULL);
3533 edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
3534 edit_state.CursorAnimReset();
3535 }
3536 }
3537 }
3538
3539 // Will copy result string if modified
3540 if (is_editable && strcmp(s1: edit_state.TempBuffer.Data, s2: buf) != 0)
3541 {
3542 apply_new_text = edit_state.TempBuffer.Data;
3543 apply_new_text_length = edit_state.CurLenA;
3544 }
3545 }
3546
3547 // Copy result to user buffer
3548 if (apply_new_text)
3549 {
3550 IM_ASSERT(apply_new_text_length >= 0);
3551 if (backup_current_text_length != apply_new_text_length && is_resizable)
3552 {
3553 ImGuiInputTextCallbackData callback_data;
3554 callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
3555 callback_data.Flags = flags;
3556 callback_data.Buf = buf;
3557 callback_data.BufTextLen = apply_new_text_length;
3558 callback_data.BufSize = ImMax(lhs: buf_size, rhs: apply_new_text_length + 1);
3559 callback_data.UserData = callback_user_data;
3560 callback(&callback_data);
3561 buf = callback_data.Buf;
3562 buf_size = callback_data.BufSize;
3563 apply_new_text_length = ImMin(lhs: callback_data.BufTextLen, rhs: buf_size - 1);
3564 IM_ASSERT(apply_new_text_length <= buf_size);
3565 }
3566
3567 // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
3568 ImStrncpy(dst: buf, src: edit_state.TempBuffer.Data, count: ImMin(lhs: apply_new_text_length + 1, rhs: buf_size));
3569 value_changed = true;
3570 }
3571
3572 // Clear temporary user storage
3573 edit_state.UserFlags = 0;
3574 edit_state.UserCallback = NULL;
3575 edit_state.UserCallbackData = NULL;
3576 }
3577
3578 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
3579 if (clear_active_id && g.ActiveId == id)
3580 ClearActiveID();
3581
3582 // Render
3583 // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
3584 const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
3585
3586 // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
3587 // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
3588 // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
3589 const int buf_display_max_length = 2 * 1024 * 1024;
3590
3591 if (!is_multiline)
3592 {
3593 RenderNavHighlight(bb: frame_bb, id);
3594 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: GetColorU32(idx: ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding);
3595 }
3596
3597 const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
3598 ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
3599 ImVec2 text_size(0.f, 0.f);
3600 const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive(str: "#SCROLLY"));
3601 if (g.ActiveId == id || is_currently_scrolling)
3602 {
3603 edit_state.CursorAnim += io.DeltaTime;
3604
3605 // This is going to be messy. We need to:
3606 // - Display the text (this alone can be more easily clipped)
3607 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
3608 // - Measure text height (for scrollbar)
3609 // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
3610 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
3611 const ImWchar* text_begin = edit_state.TextW.Data;
3612 ImVec2 cursor_offset, select_start_offset;
3613
3614 {
3615 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
3616 const ImWchar* searches_input_ptr[2];
3617 searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
3618 searches_input_ptr[1] = NULL;
3619 int searches_remaining = 1;
3620 int searches_result_line_number[2] = { -1, -999 };
3621 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
3622 {
3623 searches_input_ptr[1] = text_begin + ImMin(lhs: edit_state.StbState.select_start, rhs: edit_state.StbState.select_end);
3624 searches_result_line_number[1] = -1;
3625 searches_remaining++;
3626 }
3627
3628 // Iterate all lines to find our line numbers
3629 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
3630 searches_remaining += is_multiline ? 1 : 0;
3631 int line_count = 0;
3632 //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bits
3633 for (const ImWchar* s = text_begin; *s != 0; s++)
3634 if (*s == '\n')
3635 {
3636 line_count++;
3637 if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
3638 if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
3639 }
3640 line_count++;
3641 if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
3642 if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
3643
3644 // Calculate 2d position by finding the beginning of the line and measuring distance
3645 cursor_offset.x = InputTextCalcTextSizeW(text_begin: ImStrbolW(buf_mid_line: searches_input_ptr[0], buf_begin: text_begin), text_end: searches_input_ptr[0]).x;
3646 cursor_offset.y = searches_result_line_number[0] * g.FontSize;
3647 if (searches_result_line_number[1] >= 0)
3648 {
3649 select_start_offset.x = InputTextCalcTextSizeW(text_begin: ImStrbolW(buf_mid_line: searches_input_ptr[1], buf_begin: text_begin), text_end: searches_input_ptr[1]).x;
3650 select_start_offset.y = searches_result_line_number[1] * g.FontSize;
3651 }
3652
3653 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
3654 if (is_multiline)
3655 text_size = ImVec2(size.x, line_count * g.FontSize);
3656 }
3657
3658 // Scroll
3659 if (edit_state.CursorFollow)
3660 {
3661 // Horizontal scroll in chunks of quarter width
3662 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
3663 {
3664 const float scroll_increment_x = size.x * 0.25f;
3665 if (cursor_offset.x < edit_state.ScrollX)
3666 edit_state.ScrollX = (float)(int)ImMax(lhs: 0.0f, rhs: cursor_offset.x - scroll_increment_x);
3667 else if (cursor_offset.x - size.x >= edit_state.ScrollX)
3668 edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
3669 }
3670 else
3671 {
3672 edit_state.ScrollX = 0.0f;
3673 }
3674
3675 // Vertical scroll
3676 if (is_multiline)
3677 {
3678 float scroll_y = draw_window->Scroll.y;
3679 if (cursor_offset.y - g.FontSize < scroll_y)
3680 scroll_y = ImMax(lhs: 0.0f, rhs: cursor_offset.y - g.FontSize);
3681 else if (cursor_offset.y - size.y >= scroll_y)
3682 scroll_y = cursor_offset.y - size.y;
3683 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
3684 draw_window->Scroll.y = scroll_y;
3685 render_pos.y = draw_window->DC.CursorPos.y;
3686 }
3687 }
3688 edit_state.CursorFollow = false;
3689 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
3690
3691 // Draw selection
3692 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
3693 {
3694 const ImWchar* text_selected_begin = text_begin + ImMin(lhs: edit_state.StbState.select_start, rhs: edit_state.StbState.select_end);
3695 const ImWchar* text_selected_end = text_begin + ImMax(lhs: edit_state.StbState.select_start, rhs: edit_state.StbState.select_end);
3696
3697 float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
3698 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
3699 ImU32 bg_color = GetColorU32(idx: ImGuiCol_TextSelectedBg);
3700 ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
3701 for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
3702 {
3703 if (rect_pos.y > clip_rect.w + g.FontSize)
3704 break;
3705 if (rect_pos.y < clip_rect.y)
3706 {
3707 //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bits
3708 //p = p ? p + 1 : text_selected_end;
3709 while (p < text_selected_end)
3710 if (*p++ == '\n')
3711 break;
3712 }
3713 else
3714 {
3715 ImVec2 rect_size = InputTextCalcTextSizeW(text_begin: p, text_end: text_selected_end, remaining: &p, NULL, stop_on_new_line: true);
3716 if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance(c: (ImWchar)' ') * 0.50f); // So we can see selected empty lines
3717 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
3718 rect.ClipWith(r: clip_rect);
3719 if (rect.Overlaps(r: clip_rect))
3720 draw_window->DrawList->AddRectFilled(a: rect.Min, b: rect.Max, col: bg_color);
3721 }
3722 rect_pos.x = render_pos.x - render_scroll.x;
3723 rect_pos.y += g.FontSize;
3724 }
3725 }
3726
3727 const int buf_display_len = edit_state.CurLenA;
3728 if (is_multiline || buf_display_len < buf_display_max_length)
3729 draw_window->DrawList->AddText(font: g.Font, font_size: g.FontSize, pos: render_pos - render_scroll, col: GetColorU32(idx: ImGuiCol_Text), text_begin: buf_display, text_end: buf_display + buf_display_len, wrap_width: 0.0f, cpu_fine_clip_rect: is_multiline ? NULL : &clip_rect);
3730
3731 // Draw blinking cursor
3732 bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(x: g.InputTextState.CursorAnim, y: 1.20f) <= 0.80f;
3733 ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
3734 ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
3735 if (cursor_is_visible && cursor_screen_rect.Overlaps(r: clip_rect))
3736 draw_window->DrawList->AddLine(a: cursor_screen_rect.Min, b: cursor_screen_rect.GetBL(), col: GetColorU32(idx: ImGuiCol_Text));
3737
3738 // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
3739 if (is_editable)
3740 g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
3741 }
3742 else
3743 {
3744 // Render text only
3745 const char* buf_end = NULL;
3746 if (is_multiline)
3747 text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(text_begin: buf_display, out_text_end: &buf_end) * g.FontSize); // We don't need width
3748 else
3749 buf_end = buf_display + strlen(s: buf_display);
3750 if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
3751 draw_window->DrawList->AddText(font: g.Font, font_size: g.FontSize, pos: render_pos, col: GetColorU32(idx: ImGuiCol_Text), text_begin: buf_display, text_end: buf_end, wrap_width: 0.0f, cpu_fine_clip_rect: is_multiline ? NULL : &clip_rect);
3752 }
3753
3754 if (is_multiline)
3755 {
3756 Dummy(size: text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
3757 EndChildFrame();
3758 EndGroup();
3759 }
3760
3761 if (is_password)
3762 PopFont();
3763
3764 // Log as text
3765 if (g.LogEnabled && !is_password)
3766 LogRenderedText(ref_pos: &render_pos, text: buf_display, NULL);
3767
3768 if (label_size.x > 0)
3769 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
3770
3771 if (value_changed)
3772 MarkItemEdited(id);
3773
3774 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
3775 return enter_pressed;
3776 else
3777 return value_changed;
3778}
3779
3780//-------------------------------------------------------------------------
3781// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
3782//-------------------------------------------------------------------------
3783// - ColorEdit3()
3784// - ColorEdit4()
3785// - ColorPicker3()
3786// - RenderColorRectWithAlphaCheckerboard() [Internal]
3787// - ColorPicker4()
3788// - ColorButton()
3789// - SetColorEditOptions()
3790// - ColorTooltip() [Internal]
3791// - ColorEditOptionsPopup() [Internal]
3792// - ColorPickerOptionsPopup() [Internal]
3793//-------------------------------------------------------------------------
3794
3795bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
3796{
3797 return ColorEdit4(label, col, flags: flags | ImGuiColorEditFlags_NoAlpha);
3798}
3799
3800// Edit colors components (each component in 0.0f..1.0f range).
3801// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
3802// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
3803bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
3804{
3805 ImGuiWindow* window = GetCurrentWindow();
3806 if (window->SkipItems)
3807 return false;
3808
3809 ImGuiContext& g = *GImGui;
3810 const ImGuiStyle& style = g.Style;
3811 const float square_sz = GetFrameHeight();
3812 const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
3813 const float w_items_all = CalcItemWidth() - w_extra;
3814 const char* label_display_end = FindRenderedTextEnd(text: label);
3815
3816 BeginGroup();
3817 PushID(str_id: label);
3818
3819 // If we're not showing any slider there's no point in doing any HSV conversions
3820 const ImGuiColorEditFlags flags_untouched = flags;
3821 if (flags & ImGuiColorEditFlags_NoInputs)
3822 flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
3823
3824 // Context menu: display and modify options (before defaults are applied)
3825 if (!(flags & ImGuiColorEditFlags_NoOptions))
3826 ColorEditOptionsPopup(col, flags);
3827
3828 // Read stored options
3829 if (!(flags & ImGuiColorEditFlags__InputsMask))
3830 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
3831 if (!(flags & ImGuiColorEditFlags__DataTypeMask))
3832 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
3833 if (!(flags & ImGuiColorEditFlags__PickerMask))
3834 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
3835 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
3836
3837 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
3838 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
3839 const int components = alpha ? 4 : 3;
3840
3841 // Convert to the formats we need
3842 float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
3843 if (flags & ImGuiColorEditFlags_HSV)
3844 ColorConvertRGBtoHSV(r: f[0], g: f[1], b: f[2], out_h&: f[0], out_s&: f[1], out_v&: f[2]);
3845 int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
3846
3847 bool value_changed = false;
3848 bool value_changed_as_float = false;
3849
3850 if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
3851 {
3852 // RGB/HSV 0..255 Sliders
3853 const float w_item_one = ImMax(lhs: 1.0f, rhs: (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
3854 const float w_item_last = ImMax(lhs: 1.0f, rhs: (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
3855
3856 const bool hide_prefix = (w_item_one <= CalcTextSize(text: (flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
3857 const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
3858 const char* fmt_table_int[3][4] =
3859 {
3860 { "%3d", "%3d", "%3d", "%3d" }, // Short display
3861 { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
3862 { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
3863 };
3864 const char* fmt_table_float[3][4] =
3865 {
3866 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
3867 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
3868 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
3869 };
3870 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
3871
3872 PushItemWidth(item_width: w_item_one);
3873 for (int n = 0; n < components; n++)
3874 {
3875 if (n > 0)
3876 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
3877 if (n + 1 == components)
3878 PushItemWidth(item_width: w_item_last);
3879 if (flags & ImGuiColorEditFlags_Float)
3880 value_changed = value_changed_as_float = value_changed | DragFloat(label: ids[n], v: &f[n], v_speed: 1.0f/255.0f, v_min: 0.0f, v_max: hdr ? 0.0f : 1.0f, format: fmt_table_float[fmt_idx][n]);
3881 else
3882 value_changed |= DragInt(label: ids[n], v: &i[n], v_speed: 1.0f, v_min: 0, v_max: hdr ? 0 : 255, format: fmt_table_int[fmt_idx][n]);
3883 if (!(flags & ImGuiColorEditFlags_NoOptions))
3884 OpenPopupOnItemClick(str_id: "context");
3885 }
3886 PopItemWidth();
3887 PopItemWidth();
3888 }
3889 else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
3890 {
3891 // RGB Hexadecimal Input
3892 char buf[64];
3893 if (alpha)
3894 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "#%02X%02X%02X%02X", ImClamp(v: i[0],mn: 0,mx: 255), ImClamp(v: i[1],mn: 0,mx: 255), ImClamp(v: i[2],mn: 0,mx: 255), ImClamp(v: i[3],mn: 0,mx: 255));
3895 else
3896 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "#%02X%02X%02X", ImClamp(v: i[0],mn: 0,mx: 255), ImClamp(v: i[1],mn: 0,mx: 255), ImClamp(v: i[2],mn: 0,mx: 255));
3897 PushItemWidth(item_width: w_items_all);
3898 if (InputText(label: "##Text", buf, IM_ARRAYSIZE(buf), flags: ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
3899 {
3900 value_changed = true;
3901 char* p = buf;
3902 while (*p == '#' || ImCharIsBlankA(c: *p))
3903 p++;
3904 i[0] = i[1] = i[2] = i[3] = 0;
3905 if (alpha)
3906 sscanf(s: p, format: "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
3907 else
3908 sscanf(s: p, format: "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
3909 }
3910 if (!(flags & ImGuiColorEditFlags_NoOptions))
3911 OpenPopupOnItemClick(str_id: "context");
3912 PopItemWidth();
3913 }
3914
3915 ImGuiWindow* picker_active_window = NULL;
3916 if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
3917 {
3918 if (!(flags & ImGuiColorEditFlags_NoInputs))
3919 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
3920
3921 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
3922 if (ColorButton(desc_id: "##ColorButton", col: col_v4, flags))
3923 {
3924 if (!(flags & ImGuiColorEditFlags_NoPicker))
3925 {
3926 // Store current color and open a picker
3927 g.ColorPickerRef = col_v4;
3928 OpenPopup(str_id: "picker");
3929 SetNextWindowPos(pos: window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
3930 }
3931 }
3932 if (!(flags & ImGuiColorEditFlags_NoOptions))
3933 OpenPopupOnItemClick(str_id: "context");
3934
3935 if (BeginPopup(str_id: "picker"))
3936 {
3937 picker_active_window = g.CurrentWindow;
3938 if (label != label_display_end)
3939 {
3940 TextUnformatted(text: label, text_end: label_display_end);
3941 Spacing();
3942 }
3943 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
3944 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
3945 PushItemWidth(item_width: square_sz * 12.0f); // Use 256 + bar sizes?
3946 value_changed |= ColorPicker4(label: "##picker", col, flags: picker_flags, ref_col: &g.ColorPickerRef.x);
3947 PopItemWidth();
3948 EndPopup();
3949 }
3950 }
3951
3952 if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
3953 {
3954 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
3955 TextUnformatted(text: label, text_end: label_display_end);
3956 }
3957
3958 // Convert back
3959 if (picker_active_window == NULL)
3960 {
3961 if (!value_changed_as_float)
3962 for (int n = 0; n < 4; n++)
3963 f[n] = i[n] / 255.0f;
3964 if (flags & ImGuiColorEditFlags_HSV)
3965 ColorConvertHSVtoRGB(h: f[0], s: f[1], v: f[2], out_r&: f[0], out_g&: f[1], out_b&: f[2]);
3966 if (value_changed)
3967 {
3968 col[0] = f[0];
3969 col[1] = f[1];
3970 col[2] = f[2];
3971 if (alpha)
3972 col[3] = f[3];
3973 }
3974 }
3975
3976 PopID();
3977 EndGroup();
3978
3979 // Drag and Drop Target
3980 // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
3981 if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
3982 {
3983 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
3984 {
3985 memcpy(dest: (float*)col, src: payload->Data, n: sizeof(float) * 3);
3986 value_changed = true;
3987 }
3988 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
3989 {
3990 memcpy(dest: (float*)col, src: payload->Data, n: sizeof(float) * components);
3991 value_changed = true;
3992 }
3993 EndDragDropTarget();
3994 }
3995
3996 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
3997 if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
3998 window->DC.LastItemId = g.ActiveId;
3999
4000 if (value_changed)
4001 MarkItemEdited(id: window->DC.LastItemId);
4002
4003 return value_changed;
4004}
4005
4006bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
4007{
4008 float col4[4] = { col[0], col[1], col[2], 1.0f };
4009 if (!ColorPicker4(label, col: col4, flags: flags | ImGuiColorEditFlags_NoAlpha))
4010 return false;
4011 col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
4012 return true;
4013}
4014
4015static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
4016{
4017 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
4018 int r = ImLerp(a: (int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, b: (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
4019 int g = ImLerp(a: (int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, b: (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
4020 int b = ImLerp(a: (int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, b: (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
4021 return IM_COL32(r, g, b, 0xFF);
4022}
4023
4024// Helper for ColorPicker4()
4025// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
4026// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
4027void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
4028{
4029 ImGuiWindow* window = GetCurrentWindow();
4030 if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
4031 {
4032 ImU32 col_bg1 = GetColorU32(col: ImAlphaBlendColor(IM_COL32(204,204,204,255), col_b: col));
4033 ImU32 col_bg2 = GetColorU32(col: ImAlphaBlendColor(IM_COL32(128,128,128,255), col_b: col));
4034 window->DrawList->AddRectFilled(a: p_min, b: p_max, col: col_bg1, rounding, rounding_corners_flags);
4035
4036 int yi = 0;
4037 for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
4038 {
4039 float y1 = ImClamp(v: y, mn: p_min.y, mx: p_max.y), y2 = ImMin(lhs: y + grid_step, rhs: p_max.y);
4040 if (y2 <= y1)
4041 continue;
4042 for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
4043 {
4044 float x1 = ImClamp(v: x, mn: p_min.x, mx: p_max.x), x2 = ImMin(lhs: x + grid_step, rhs: p_max.x);
4045 if (x2 <= x1)
4046 continue;
4047 int rounding_corners_flags_cell = 0;
4048 if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
4049 if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
4050 rounding_corners_flags_cell &= rounding_corners_flags;
4051 window->DrawList->AddRectFilled(a: ImVec2(x1,y1), b: ImVec2(x2,y2), col: col_bg2, rounding: rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags: rounding_corners_flags_cell);
4052 }
4053 }
4054 }
4055 else
4056 {
4057 window->DrawList->AddRectFilled(a: p_min, b: p_max, col, rounding, rounding_corners_flags);
4058 }
4059}
4060
4061// Helper for ColorPicker4()
4062static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
4063{
4064 ImGui::RenderArrowPointingAt(draw_list, pos: ImVec2(pos.x + half_sz.x + 1, pos.y), half_sz: ImVec2(half_sz.x + 2, half_sz.y + 1), direction: ImGuiDir_Right, IM_COL32_BLACK);
4065 ImGui::RenderArrowPointingAt(draw_list, pos: ImVec2(pos.x + half_sz.x, pos.y), half_sz, direction: ImGuiDir_Right, IM_COL32_WHITE);
4066 ImGui::RenderArrowPointingAt(draw_list, pos: ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), half_sz: ImVec2(half_sz.x + 2, half_sz.y + 1), direction: ImGuiDir_Left, IM_COL32_BLACK);
4067 ImGui::RenderArrowPointingAt(draw_list, pos: ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, direction: ImGuiDir_Left, IM_COL32_WHITE);
4068}
4069
4070// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4071// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
4072bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
4073{
4074 ImGuiContext& g = *GImGui;
4075 ImGuiWindow* window = GetCurrentWindow();
4076 ImDrawList* draw_list = window->DrawList;
4077
4078 ImGuiStyle& style = g.Style;
4079 ImGuiIO& io = g.IO;
4080
4081 PushID(str_id: label);
4082 BeginGroup();
4083
4084 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4085 flags |= ImGuiColorEditFlags_NoSmallPreview;
4086
4087 // Context menu: display and store options.
4088 if (!(flags & ImGuiColorEditFlags_NoOptions))
4089 ColorPickerOptionsPopup(ref_col: col, flags);
4090
4091 // Read stored options
4092 if (!(flags & ImGuiColorEditFlags__PickerMask))
4093 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
4094 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
4095 if (!(flags & ImGuiColorEditFlags_NoOptions))
4096 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
4097
4098 // Setup
4099 int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
4100 bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
4101 ImVec2 picker_pos = window->DC.CursorPos;
4102 float square_sz = GetFrameHeight();
4103 float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
4104 float sv_picker_size = ImMax(lhs: bars_width * 1, rhs: CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
4105 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
4106 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
4107 float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
4108
4109 float backup_initial_col[4];
4110 memcpy(dest: backup_initial_col, src: col, n: components * sizeof(float));
4111
4112 float wheel_thickness = sv_picker_size * 0.08f;
4113 float wheel_r_outer = sv_picker_size * 0.50f;
4114 float wheel_r_inner = wheel_r_outer - wheel_thickness;
4115 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
4116
4117 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
4118 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
4119 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
4120 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
4121 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
4122
4123 float H,S,V;
4124 ColorConvertRGBtoHSV(r: col[0], g: col[1], b: col[2], out_h&: H, out_s&: S, out_v&: V);
4125
4126 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
4127
4128 PushItemFlag(option: ImGuiItemFlags_NoNav, enabled: true);
4129 if (flags & ImGuiColorEditFlags_PickerHueWheel)
4130 {
4131 // Hue wheel + SV triangle logic
4132 InvisibleButton(str_id: "hsv", size_arg: ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
4133 if (IsItemActive())
4134 {
4135 ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
4136 ImVec2 current_off = g.IO.MousePos - wheel_center;
4137 float initial_dist2 = ImLengthSqr(lhs: initial_off);
4138 if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
4139 {
4140 // Interactive with Hue wheel
4141 H = ImAtan2(y: current_off.y, x: current_off.x) / IM_PI*0.5f;
4142 if (H < 0.0f)
4143 H += 1.0f;
4144 value_changed = value_changed_h = true;
4145 }
4146 float cos_hue_angle = ImCos(x: -H * 2.0f * IM_PI);
4147 float sin_hue_angle = ImSin(x: -H * 2.0f * IM_PI);
4148 if (ImTriangleContainsPoint(a: triangle_pa, b: triangle_pb, c: triangle_pc, p: ImRotate(v: initial_off, cos_a: cos_hue_angle, sin_a: sin_hue_angle)))
4149 {
4150 // Interacting with SV triangle
4151 ImVec2 current_off_unrotated = ImRotate(v: current_off, cos_a: cos_hue_angle, sin_a: sin_hue_angle);
4152 if (!ImTriangleContainsPoint(a: triangle_pa, b: triangle_pb, c: triangle_pc, p: current_off_unrotated))
4153 current_off_unrotated = ImTriangleClosestPoint(a: triangle_pa, b: triangle_pb, c: triangle_pc, p: current_off_unrotated);
4154 float uu, vv, ww;
4155 ImTriangleBarycentricCoords(a: triangle_pa, b: triangle_pb, c: triangle_pc, p: current_off_unrotated, out_u&: uu, out_v&: vv, out_w&: ww);
4156 V = ImClamp(v: 1.0f - vv, mn: 0.0001f, mx: 1.0f);
4157 S = ImClamp(v: uu / V, mn: 0.0001f, mx: 1.0f);
4158 value_changed = value_changed_sv = true;
4159 }
4160 }
4161 if (!(flags & ImGuiColorEditFlags_NoOptions))
4162 OpenPopupOnItemClick(str_id: "context");
4163 }
4164 else if (flags & ImGuiColorEditFlags_PickerHueBar)
4165 {
4166 // SV rectangle logic
4167 InvisibleButton(str_id: "sv", size_arg: ImVec2(sv_picker_size, sv_picker_size));
4168 if (IsItemActive())
4169 {
4170 S = ImSaturate(f: (io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
4171 V = 1.0f - ImSaturate(f: (io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4172 value_changed = value_changed_sv = true;
4173 }
4174 if (!(flags & ImGuiColorEditFlags_NoOptions))
4175 OpenPopupOnItemClick(str_id: "context");
4176
4177 // Hue bar logic
4178 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
4179 InvisibleButton(str_id: "hue", size_arg: ImVec2(bars_width, sv_picker_size));
4180 if (IsItemActive())
4181 {
4182 H = ImSaturate(f: (io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4183 value_changed = value_changed_h = true;
4184 }
4185 }
4186
4187 // Alpha bar logic
4188 if (alpha_bar)
4189 {
4190 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
4191 InvisibleButton(str_id: "alpha", size_arg: ImVec2(bars_width, sv_picker_size));
4192 if (IsItemActive())
4193 {
4194 col[3] = 1.0f - ImSaturate(f: (io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4195 value_changed = true;
4196 }
4197 }
4198 PopItemFlag(); // ImGuiItemFlags_NoNav
4199
4200 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4201 {
4202 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
4203 BeginGroup();
4204 }
4205
4206 if (!(flags & ImGuiColorEditFlags_NoLabel))
4207 {
4208 const char* label_display_end = FindRenderedTextEnd(text: label);
4209 if (label != label_display_end)
4210 {
4211 if ((flags & ImGuiColorEditFlags_NoSidePreview))
4212 SameLine(pos_x: 0, spacing_w: style.ItemInnerSpacing.x);
4213 TextUnformatted(text: label, text_end: label_display_end);
4214 }
4215 }
4216
4217 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4218 {
4219 PushItemFlag(option: ImGuiItemFlags_NoNavDefaultFocus, enabled: true);
4220 ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
4221 if ((flags & ImGuiColorEditFlags_NoLabel))
4222 Text(fmt: "Current");
4223 ColorButton(desc_id: "##current", col: col_v4, flags: (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), size: ImVec2(square_sz * 3, square_sz * 2));
4224 if (ref_col != NULL)
4225 {
4226 Text(fmt: "Original");
4227 ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
4228 if (ColorButton(desc_id: "##original", col: ref_col_v4, flags: (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), size: ImVec2(square_sz * 3, square_sz * 2)))
4229 {
4230 memcpy(dest: col, src: ref_col, n: components * sizeof(float));
4231 value_changed = true;
4232 }
4233 }
4234 PopItemFlag();
4235 EndGroup();
4236 }
4237
4238 // Convert back color to RGB
4239 if (value_changed_h || value_changed_sv)
4240 ColorConvertHSVtoRGB(h: H >= 1.0f ? H - 10 * 1e-6f : H, s: S > 0.0f ? S : 10*1e-6f, v: V > 0.0f ? V : 1e-6f, out_r&: col[0], out_g&: col[1], out_b&: col[2]);
4241
4242 // R,G,B and H,S,V slider color editor
4243 bool value_changed_fix_hue_wrap = false;
4244 if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
4245 {
4246 PushItemWidth(item_width: (alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
4247 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
4248 ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
4249 if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4250 if (ColorEdit4(label: "##rgb", col, flags: sub_flags | ImGuiColorEditFlags_RGB))
4251 {
4252 // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
4253 // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
4254 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
4255 value_changed = true;
4256 }
4257 if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4258 value_changed |= ColorEdit4(label: "##hsv", col, flags: sub_flags | ImGuiColorEditFlags_HSV);
4259 if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4260 value_changed |= ColorEdit4(label: "##hex", col, flags: sub_flags | ImGuiColorEditFlags_HEX);
4261 PopItemWidth();
4262 }
4263
4264 // Try to cancel hue wrap (after ColorEdit4 call), if any
4265 if (value_changed_fix_hue_wrap)
4266 {
4267 float new_H, new_S, new_V;
4268 ColorConvertRGBtoHSV(r: col[0], g: col[1], b: col[2], out_h&: new_H, out_s&: new_S, out_v&: new_V);
4269 if (new_H <= 0 && H > 0)
4270 {
4271 if (new_V <= 0 && V != new_V)
4272 ColorConvertHSVtoRGB(h: H, s: S, v: new_V <= 0 ? V * 0.5f : new_V, out_r&: col[0], out_g&: col[1], out_b&: col[2]);
4273 else if (new_S <= 0)
4274 ColorConvertHSVtoRGB(h: H, s: new_S <= 0 ? S * 0.5f : new_S, v: new_V, out_r&: col[0], out_g&: col[1], out_b&: col[2]);
4275 }
4276 }
4277
4278 ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(h: H, s: 1, v: 1, out_r&: hue_color_f.x, out_g&: hue_color_f.y, out_b&: hue_color_f.z);
4279 ImU32 hue_color32 = ColorConvertFloat4ToU32(in: hue_color_f);
4280 ImU32 col32_no_alpha = ColorConvertFloat4ToU32(in: ImVec4(col[0], col[1], col[2], 1.0f));
4281
4282 const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
4283 ImVec2 sv_cursor_pos;
4284
4285 if (flags & ImGuiColorEditFlags_PickerHueWheel)
4286 {
4287 // Render Hue Wheel
4288 const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
4289 const int segment_per_arc = ImMax(lhs: 4, rhs: (int)wheel_r_outer / 12);
4290 for (int n = 0; n < 6; n++)
4291 {
4292 const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
4293 const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
4294 const int vert_start_idx = draw_list->VtxBuffer.Size;
4295 draw_list->PathArcTo(centre: wheel_center, radius: (wheel_r_inner + wheel_r_outer)*0.5f, a_min: a0, a_max: a1, num_segments: segment_per_arc);
4296 draw_list->PathStroke(IM_COL32_WHITE, closed: false, thickness: wheel_thickness);
4297 const int vert_end_idx = draw_list->VtxBuffer.Size;
4298
4299 // Paint colors over existing vertices
4300 ImVec2 gradient_p0(wheel_center.x + ImCos(x: a0) * wheel_r_inner, wheel_center.y + ImSin(x: a0) * wheel_r_inner);
4301 ImVec2 gradient_p1(wheel_center.x + ImCos(x: a1) * wheel_r_inner, wheel_center.y + ImSin(x: a1) * wheel_r_inner);
4302 ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col0: hue_colors[n], col1: hue_colors[n+1]);
4303 }
4304
4305 // Render Cursor + preview on Hue Wheel
4306 float cos_hue_angle = ImCos(x: H * 2.0f * IM_PI);
4307 float sin_hue_angle = ImSin(x: H * 2.0f * IM_PI);
4308 ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
4309 float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
4310 int hue_cursor_segments = ImClamp(v: (int)(hue_cursor_rad / 1.4f), mn: 9, mx: 32);
4311 draw_list->AddCircleFilled(centre: hue_cursor_pos, radius: hue_cursor_rad, col: hue_color32, num_segments: hue_cursor_segments);
4312 draw_list->AddCircle(centre: hue_cursor_pos, radius: hue_cursor_rad+1, IM_COL32(128,128,128,255), num_segments: hue_cursor_segments);
4313 draw_list->AddCircle(centre: hue_cursor_pos, radius: hue_cursor_rad, IM_COL32_WHITE, num_segments: hue_cursor_segments);
4314
4315 // Render SV triangle (rotated according to hue)
4316 ImVec2 tra = wheel_center + ImRotate(v: triangle_pa, cos_a: cos_hue_angle, sin_a: sin_hue_angle);
4317 ImVec2 trb = wheel_center + ImRotate(v: triangle_pb, cos_a: cos_hue_angle, sin_a: sin_hue_angle);
4318 ImVec2 trc = wheel_center + ImRotate(v: triangle_pc, cos_a: cos_hue_angle, sin_a: sin_hue_angle);
4319 ImVec2 uv_white = GetFontTexUvWhitePixel();
4320 draw_list->PrimReserve(idx_count: 6, vtx_count: 6);
4321 draw_list->PrimVtx(pos: tra, uv: uv_white, col: hue_color32);
4322 draw_list->PrimVtx(pos: trb, uv: uv_white, col: hue_color32);
4323 draw_list->PrimVtx(pos: trc, uv: uv_white, IM_COL32_WHITE);
4324 draw_list->PrimVtx(pos: tra, uv: uv_white, IM_COL32_BLACK_TRANS);
4325 draw_list->PrimVtx(pos: trb, uv: uv_white, IM_COL32_BLACK);
4326 draw_list->PrimVtx(pos: trc, uv: uv_white, IM_COL32_BLACK_TRANS);
4327 draw_list->AddTriangle(a: tra, b: trb, c: trc, IM_COL32(128,128,128,255), thickness: 1.5f);
4328 sv_cursor_pos = ImLerp(a: ImLerp(a: trc, b: tra, t: ImSaturate(f: S)), b: trb, t: ImSaturate(f: 1 - V));
4329 }
4330 else if (flags & ImGuiColorEditFlags_PickerHueBar)
4331 {
4332 // Render SV Square
4333 draw_list->AddRectFilledMultiColor(a: picker_pos, b: picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, col_upr_right: hue_color32, col_bot_right: hue_color32, IM_COL32_WHITE);
4334 draw_list->AddRectFilledMultiColor(a: picker_pos, b: picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
4335 RenderFrameBorder(p_min: picker_pos, p_max: picker_pos + ImVec2(sv_picker_size,sv_picker_size), rounding: 0.0f);
4336 sv_cursor_pos.x = ImClamp(v: (float)(int)(picker_pos.x + ImSaturate(f: S) * sv_picker_size + 0.5f), mn: picker_pos.x + 2, mx: picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
4337 sv_cursor_pos.y = ImClamp(v: (float)(int)(picker_pos.y + ImSaturate(f: 1 - V) * sv_picker_size + 0.5f), mn: picker_pos.y + 2, mx: picker_pos.y + sv_picker_size - 2);
4338
4339 // Render Hue Bar
4340 for (int i = 0; i < 6; ++i)
4341 draw_list->AddRectFilledMultiColor(a: ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), b: ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_upr_left: hue_colors[i], col_upr_right: hue_colors[i], col_bot_right: hue_colors[i + 1], col_bot_left: hue_colors[i + 1]);
4342 float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
4343 RenderFrameBorder(p_min: ImVec2(bar0_pos_x, picker_pos.y), p_max: ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), rounding: 0.0f);
4344 RenderArrowsForVerticalBar(draw_list, pos: ImVec2(bar0_pos_x - 1, bar0_line_y), half_sz: ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bar_w: bars_width + 2.0f);
4345 }
4346
4347 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
4348 float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
4349 draw_list->AddCircleFilled(centre: sv_cursor_pos, radius: sv_cursor_rad, col: col32_no_alpha, num_segments: 12);
4350 draw_list->AddCircle(centre: sv_cursor_pos, radius: sv_cursor_rad+1, IM_COL32(128,128,128,255), num_segments: 12);
4351 draw_list->AddCircle(centre: sv_cursor_pos, radius: sv_cursor_rad, IM_COL32_WHITE, num_segments: 12);
4352
4353 // Render alpha bar
4354 if (alpha_bar)
4355 {
4356 float alpha = ImSaturate(f: col[3]);
4357 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
4358 RenderColorRectWithAlphaCheckerboard(p_min: bar1_bb.Min, p_max: bar1_bb.Max, IM_COL32(0,0,0,0), grid_step: bar1_bb.GetWidth() / 2.0f, grid_off: ImVec2(0.0f, 0.0f));
4359 draw_list->AddRectFilledMultiColor(a: bar1_bb.Min, b: bar1_bb.Max, col_upr_left: col32_no_alpha, col_upr_right: col32_no_alpha, col_bot_right: col32_no_alpha & ~IM_COL32_A_MASK, col_bot_left: col32_no_alpha & ~IM_COL32_A_MASK);
4360 float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
4361 RenderFrameBorder(p_min: bar1_bb.Min, p_max: bar1_bb.Max, rounding: 0.0f);
4362 RenderArrowsForVerticalBar(draw_list, pos: ImVec2(bar1_pos_x - 1, bar1_line_y), half_sz: ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bar_w: bars_width + 2.0f);
4363 }
4364
4365 EndGroup();
4366
4367 if (value_changed && memcmp(s1: backup_initial_col, s2: col, n: components * sizeof(float)) == 0)
4368 value_changed = false;
4369 if (value_changed)
4370 MarkItemEdited(id: window->DC.LastItemId);
4371
4372 PopID();
4373
4374 return value_changed;
4375}
4376
4377// A little colored square. Return true when clicked.
4378// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
4379// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
4380bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
4381{
4382 ImGuiWindow* window = GetCurrentWindow();
4383 if (window->SkipItems)
4384 return false;
4385
4386 ImGuiContext& g = *GImGui;
4387 const ImGuiID id = window->GetID(str: desc_id);
4388 float default_size = GetFrameHeight();
4389 if (size.x == 0.0f)
4390 size.x = default_size;
4391 if (size.y == 0.0f)
4392 size.y = default_size;
4393 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
4394 ItemSize(bb, text_offset_y: (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
4395 if (!ItemAdd(bb, id))
4396 return false;
4397
4398 bool hovered, held;
4399 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held);
4400
4401 if (flags & ImGuiColorEditFlags_NoAlpha)
4402 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
4403
4404 ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
4405 float grid_step = ImMin(lhs: size.x, rhs: size.y) / 2.99f;
4406 float rounding = ImMin(lhs: g.Style.FrameRounding, rhs: grid_step * 0.5f);
4407 ImRect bb_inner = bb;
4408 float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
4409 bb_inner.Expand(amount: off);
4410 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
4411 {
4412 float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
4413 RenderColorRectWithAlphaCheckerboard(p_min: ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), p_max: bb_inner.Max, col: GetColorU32(col), grid_step, grid_off: ImVec2(-grid_step + off, off), rounding, rounding_corners_flags: ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
4414 window->DrawList->AddRectFilled(a: bb_inner.Min, b: ImVec2(mid_x, bb_inner.Max.y), col: GetColorU32(col: col_without_alpha), rounding, rounding_corners_flags: ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
4415 }
4416 else
4417 {
4418 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
4419 ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
4420 if (col_source.w < 1.0f)
4421 RenderColorRectWithAlphaCheckerboard(p_min: bb_inner.Min, p_max: bb_inner.Max, col: GetColorU32(col: col_source), grid_step, grid_off: ImVec2(off, off), rounding);
4422 else
4423 window->DrawList->AddRectFilled(a: bb_inner.Min, b: bb_inner.Max, col: GetColorU32(col: col_source), rounding, rounding_corners_flags: ImDrawCornerFlags_All);
4424 }
4425 RenderNavHighlight(bb, id);
4426 if (g.Style.FrameBorderSize > 0.0f)
4427 RenderFrameBorder(p_min: bb.Min, p_max: bb.Max, rounding);
4428 else
4429 window->DrawList->AddRect(a: bb.Min, b: bb.Max, col: GetColorU32(idx: ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
4430
4431 // Drag and Drop Source
4432 // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
4433 if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
4434 {
4435 if (flags & ImGuiColorEditFlags_NoAlpha)
4436 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, data: &col, size: sizeof(float) * 3, cond: ImGuiCond_Once);
4437 else
4438 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, data: &col, size: sizeof(float) * 4, cond: ImGuiCond_Once);
4439 ColorButton(desc_id, col, flags);
4440 SameLine();
4441 TextUnformatted(text: "Color");
4442 EndDragDropSource();
4443 }
4444
4445 // Tooltip
4446 if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
4447 ColorTooltip(text: desc_id, col: &col.x, flags: flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
4448
4449 if (pressed)
4450 MarkItemEdited(id);
4451
4452 return pressed;
4453}
4454
4455void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
4456{
4457 ImGuiContext& g = *GImGui;
4458 if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
4459 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
4460 if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
4461 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
4462 if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
4463 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
4464 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
4465 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
4466 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
4467 g.ColorEditOptions = flags;
4468}
4469
4470// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4471void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
4472{
4473 ImGuiContext& g = *GImGui;
4474
4475 int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
4476 BeginTooltipEx(extra_flags: 0, override_previous_tooltip: true);
4477
4478 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
4479 if (text_end > text)
4480 {
4481 TextUnformatted(text, text_end);
4482 Separator();
4483 }
4484
4485 ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
4486 ColorButton(desc_id: "##preview", col: ImVec4(col[0], col[1], col[2], col[3]), flags: (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, size: sz);
4487 SameLine();
4488 if (flags & ImGuiColorEditFlags_NoAlpha)
4489 Text(fmt: "#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
4490 else
4491 Text(fmt: "#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
4492 EndTooltip();
4493}
4494
4495void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
4496{
4497 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
4498 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
4499 if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup(str_id: "context"))
4500 return;
4501 ImGuiContext& g = *GImGui;
4502 ImGuiColorEditFlags opts = g.ColorEditOptions;
4503 if (allow_opt_inputs)
4504 {
4505 if (RadioButton(label: "RGB", active: (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
4506 if (RadioButton(label: "HSV", active: (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
4507 if (RadioButton(label: "HEX", active: (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
4508 }
4509 if (allow_opt_datatype)
4510 {
4511 if (allow_opt_inputs) Separator();
4512 if (RadioButton(label: "0..255", active: (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
4513 if (RadioButton(label: "0.00..1.00", active: (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
4514 }
4515
4516 if (allow_opt_inputs || allow_opt_datatype)
4517 Separator();
4518 if (Button(label: "Copy as..", size_arg: ImVec2(-1,0)))
4519 OpenPopup(str_id: "Copy");
4520 if (BeginPopup(str_id: "Copy"))
4521 {
4522 int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
4523 char buf[64];
4524 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
4525 if (Selectable(label: buf))
4526 SetClipboardText(buf);
4527 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "(%d,%d,%d,%d)", cr, cg, cb, ca);
4528 if (Selectable(label: buf))
4529 SetClipboardText(buf);
4530 if (flags & ImGuiColorEditFlags_NoAlpha)
4531 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "0x%02X%02X%02X", cr, cg, cb);
4532 else
4533 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "0x%02X%02X%02X%02X", cr, cg, cb, ca);
4534 if (Selectable(label: buf))
4535 SetClipboardText(buf);
4536 EndPopup();
4537 }
4538
4539 g.ColorEditOptions = opts;
4540 EndPopup();
4541}
4542
4543void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
4544{
4545 bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
4546 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
4547 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup(str_id: "context"))
4548 return;
4549 ImGuiContext& g = *GImGui;
4550 if (allow_opt_picker)
4551 {
4552 ImVec2 picker_size(g.FontSize * 8, ImMax(lhs: g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), rhs: 1.0f)); // FIXME: Picker size copied from main picker function
4553 PushItemWidth(item_width: picker_size.x);
4554 for (int picker_type = 0; picker_type < 2; picker_type++)
4555 {
4556 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
4557 if (picker_type > 0) Separator();
4558 PushID(int_id: picker_type);
4559 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
4560 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
4561 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
4562 ImVec2 backup_pos = GetCursorScreenPos();
4563 if (Selectable(label: "##selectable", selected: false, flags: 0, size: picker_size)) // By default, Selectable() is closing popup
4564 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
4565 SetCursorScreenPos(backup_pos);
4566 ImVec4 dummy_ref_col;
4567 memcpy(dest: &dummy_ref_col.x, src: ref_col, n: sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
4568 ColorPicker4(label: "##dummypicker", col: &dummy_ref_col.x, flags: picker_flags);
4569 PopID();
4570 }
4571 PopItemWidth();
4572 }
4573 if (allow_opt_alpha_bar)
4574 {
4575 if (allow_opt_picker) Separator();
4576 CheckboxFlags(label: "Alpha Bar", flags: (unsigned int*)&g.ColorEditOptions, flags_value: ImGuiColorEditFlags_AlphaBar);
4577 }
4578 EndPopup();
4579}
4580
4581//-------------------------------------------------------------------------
4582// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
4583//-------------------------------------------------------------------------
4584// - TreeNode()
4585// - TreeNodeV()
4586// - TreeNodeEx()
4587// - TreeNodeExV()
4588// - TreeNodeBehavior() [Internal]
4589// - TreePush()
4590// - TreePop()
4591// - TreeAdvanceToLabelPos()
4592// - GetTreeNodeToLabelSpacing()
4593// - SetNextTreeNodeOpen()
4594// - CollapsingHeader()
4595//-------------------------------------------------------------------------
4596
4597bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
4598{
4599 va_list args;
4600 va_start(args, fmt);
4601 bool is_open = TreeNodeExV(str_id, flags: 0, fmt, args);
4602 va_end(args);
4603 return is_open;
4604}
4605
4606bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
4607{
4608 va_list args;
4609 va_start(args, fmt);
4610 bool is_open = TreeNodeExV(ptr_id, flags: 0, fmt, args);
4611 va_end(args);
4612 return is_open;
4613}
4614
4615bool ImGui::TreeNode(const char* label)
4616{
4617 ImGuiWindow* window = GetCurrentWindow();
4618 if (window->SkipItems)
4619 return false;
4620 return TreeNodeBehavior(id: window->GetID(str: label), flags: 0, label, NULL);
4621}
4622
4623bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
4624{
4625 return TreeNodeExV(str_id, flags: 0, fmt, args);
4626}
4627
4628bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
4629{
4630 return TreeNodeExV(ptr_id, flags: 0, fmt, args);
4631}
4632
4633bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
4634{
4635 ImGuiWindow* window = GetCurrentWindow();
4636 if (window->SkipItems)
4637 return false;
4638
4639 return TreeNodeBehavior(id: window->GetID(str: label), flags, label, NULL);
4640}
4641
4642bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
4643{
4644 va_list args;
4645 va_start(args, fmt);
4646 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
4647 va_end(args);
4648 return is_open;
4649}
4650
4651bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
4652{
4653 va_list args;
4654 va_start(args, fmt);
4655 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
4656 va_end(args);
4657 return is_open;
4658}
4659
4660bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
4661{
4662 ImGuiWindow* window = GetCurrentWindow();
4663 if (window->SkipItems)
4664 return false;
4665
4666 ImGuiContext& g = *GImGui;
4667 const char* label_end = g.TempBuffer + ImFormatStringV(buf: g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
4668 return TreeNodeBehavior(id: window->GetID(str: str_id), flags, label: g.TempBuffer, label_end);
4669}
4670
4671bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
4672{
4673 ImGuiWindow* window = GetCurrentWindow();
4674 if (window->SkipItems)
4675 return false;
4676
4677 ImGuiContext& g = *GImGui;
4678 const char* label_end = g.TempBuffer + ImFormatStringV(buf: g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
4679 return TreeNodeBehavior(id: window->GetID(ptr: ptr_id), flags, label: g.TempBuffer, label_end);
4680}
4681
4682bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
4683{
4684 if (flags & ImGuiTreeNodeFlags_Leaf)
4685 return true;
4686
4687 // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
4688 ImGuiContext& g = *GImGui;
4689 ImGuiWindow* window = g.CurrentWindow;
4690 ImGuiStorage* storage = window->DC.StateStorage;
4691
4692 bool is_open;
4693 if (g.NextTreeNodeOpenCond != 0)
4694 {
4695 if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
4696 {
4697 is_open = g.NextTreeNodeOpenVal;
4698 storage->SetInt(key: id, val: is_open);
4699 }
4700 else
4701 {
4702 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
4703 const int stored_value = storage->GetInt(key: id, default_val: -1);
4704 if (stored_value == -1)
4705 {
4706 is_open = g.NextTreeNodeOpenVal;
4707 storage->SetInt(key: id, val: is_open);
4708 }
4709 else
4710 {
4711 is_open = stored_value != 0;
4712 }
4713 }
4714 g.NextTreeNodeOpenCond = 0;
4715 }
4716 else
4717 {
4718 is_open = storage->GetInt(key: id, default_val: (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
4719 }
4720
4721 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
4722 // NB- If we are above max depth we still allow manually opened nodes to be logged.
4723 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
4724 is_open = true;
4725
4726 return is_open;
4727}
4728
4729bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
4730{
4731 ImGuiWindow* window = GetCurrentWindow();
4732 if (window->SkipItems)
4733 return false;
4734
4735 ImGuiContext& g = *GImGui;
4736 const ImGuiStyle& style = g.Style;
4737 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
4738 const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
4739
4740 if (!label_end)
4741 label_end = FindRenderedTextEnd(text: label);
4742 const ImVec2 label_size = CalcTextSize(text: label, text_end: label_end, hide_text_after_double_hash: false);
4743
4744 // We vertically grow up to current line height up the typical widget height.
4745 const float text_base_offset_y = ImMax(lhs: padding.y, rhs: window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
4746 const float frame_height = ImMax(lhs: ImMin(lhs: window->DC.CurrentLineSize.y, rhs: g.FontSize + style.FramePadding.y*2), rhs: label_size.y + padding.y*2);
4747 ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
4748 if (display_frame)
4749 {
4750 // Framed header expand a little outside the default padding
4751 frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
4752 frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
4753 }
4754
4755 const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing
4756 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
4757 ItemSize(size: ImVec2(text_width, frame_height), text_offset_y: text_base_offset_y);
4758
4759 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
4760 // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
4761 const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
4762 bool is_open = TreeNodeBehaviorIsOpen(id, flags);
4763
4764 // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
4765 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
4766 // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
4767 if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4768 window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
4769
4770 bool item_add = ItemAdd(bb: interact_bb, id);
4771 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
4772 window->DC.LastItemDisplayRect = frame_bb;
4773
4774 if (!item_add)
4775 {
4776 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4777 TreePushRawID(id);
4778 return is_open;
4779 }
4780
4781 // Flags that affects opening behavior:
4782 // - 0(default) ..................... single-click anywhere to open
4783 // - OpenOnDoubleClick .............. double-click anywhere to open
4784 // - OpenOnArrow .................... single-click on arrow to open
4785 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
4786 ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
4787 if (!(flags & ImGuiTreeNodeFlags_Leaf))
4788 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
4789 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
4790 button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
4791
4792 bool hovered, held, pressed = ButtonBehavior(bb: interact_bb, id, out_hovered: &hovered, out_held: &held, flags: button_flags);
4793 if (!(flags & ImGuiTreeNodeFlags_Leaf))
4794 {
4795 bool toggled = false;
4796 if (pressed)
4797 {
4798 toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
4799 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
4800 toggled |= IsMouseHoveringRect(r_min: interact_bb.Min, r_max: ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
4801 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
4802 toggled |= g.IO.MouseDoubleClicked[0];
4803 if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
4804 toggled = false;
4805 }
4806
4807 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
4808 {
4809 toggled = true;
4810 NavMoveRequestCancel();
4811 }
4812 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
4813 {
4814 toggled = true;
4815 NavMoveRequestCancel();
4816 }
4817
4818 if (toggled)
4819 {
4820 is_open = !is_open;
4821 window->DC.StateStorage->SetInt(key: id, val: is_open);
4822 }
4823 }
4824 if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
4825 SetItemAllowOverlap();
4826
4827 // Render
4828 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
4829 const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
4830 if (display_frame)
4831 {
4832 // Framed type
4833 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: col, border: true, rounding: style.FrameRounding);
4834 RenderNavHighlight(bb: frame_bb, id, flags: ImGuiNavHighlightFlags_TypeThin);
4835 RenderArrow(pos: frame_bb.Min + ImVec2(padding.x, text_base_offset_y), dir: is_open ? ImGuiDir_Down : ImGuiDir_Right, scale: 1.0f);
4836 if (g.LogEnabled)
4837 {
4838 // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
4839 const char log_prefix[] = "\n##";
4840 const char log_suffix[] = "##";
4841 LogRenderedText(ref_pos: &text_pos, text: log_prefix, text_end: log_prefix+3);
4842 RenderTextClipped(pos_min: text_pos, pos_max: frame_bb.Max, text: label, text_end: label_end, text_size_if_known: &label_size);
4843 LogRenderedText(ref_pos: &text_pos, text: log_suffix+1, text_end: log_suffix+3);
4844 }
4845 else
4846 {
4847 RenderTextClipped(pos_min: text_pos, pos_max: frame_bb.Max, text: label, text_end: label_end, text_size_if_known: &label_size);
4848 }
4849 }
4850 else
4851 {
4852 // Unframed typed for tree nodes
4853 if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
4854 {
4855 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: col, border: false);
4856 RenderNavHighlight(bb: frame_bb, id, flags: ImGuiNavHighlightFlags_TypeThin);
4857 }
4858
4859 if (flags & ImGuiTreeNodeFlags_Bullet)
4860 RenderBullet(pos: frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
4861 else if (!(flags & ImGuiTreeNodeFlags_Leaf))
4862 RenderArrow(pos: frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), dir: is_open ? ImGuiDir_Down : ImGuiDir_Right, scale: 0.70f);
4863 if (g.LogEnabled)
4864 LogRenderedText(ref_pos: &text_pos, text: ">");
4865 RenderText(pos: text_pos, text: label, text_end: label_end, hide_text_after_hash: false);
4866 }
4867
4868 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4869 TreePushRawID(id);
4870 return is_open;
4871}
4872
4873void ImGui::TreePush(const char* str_id)
4874{
4875 ImGuiWindow* window = GetCurrentWindow();
4876 Indent();
4877 window->DC.TreeDepth++;
4878 PushID(str_id: str_id ? str_id : "#TreePush");
4879}
4880
4881void ImGui::TreePush(const void* ptr_id)
4882{
4883 ImGuiWindow* window = GetCurrentWindow();
4884 Indent();
4885 window->DC.TreeDepth++;
4886 PushID(ptr_id: ptr_id ? ptr_id : (const void*)"#TreePush");
4887}
4888
4889void ImGui::TreePushRawID(ImGuiID id)
4890{
4891 ImGuiWindow* window = GetCurrentWindow();
4892 Indent();
4893 window->DC.TreeDepth++;
4894 window->IDStack.push_back(v: id);
4895}
4896
4897void ImGui::TreePop()
4898{
4899 ImGuiContext& g = *GImGui;
4900 ImGuiWindow* window = g.CurrentWindow;
4901 Unindent();
4902
4903 window->DC.TreeDepth--;
4904 if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
4905 if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
4906 {
4907 SetNavID(id: window->IDStack.back(), nav_layer: g.NavLayer);
4908 NavMoveRequestCancel();
4909 }
4910 window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
4911
4912 IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
4913 PopID();
4914}
4915
4916void ImGui::TreeAdvanceToLabelPos()
4917{
4918 ImGuiContext& g = *GImGui;
4919 g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
4920}
4921
4922// Horizontal distance preceding label when using TreeNode() or Bullet()
4923float ImGui::GetTreeNodeToLabelSpacing()
4924{
4925 ImGuiContext& g = *GImGui;
4926 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
4927}
4928
4929void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
4930{
4931 ImGuiContext& g = *GImGui;
4932 if (g.CurrentWindow->SkipItems)
4933 return;
4934 g.NextTreeNodeOpenVal = is_open;
4935 g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
4936}
4937
4938// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
4939// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
4940bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
4941{
4942 ImGuiWindow* window = GetCurrentWindow();
4943 if (window->SkipItems)
4944 return false;
4945
4946 return TreeNodeBehavior(id: window->GetID(str: label), flags: flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
4947}
4948
4949bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
4950{
4951 ImGuiWindow* window = GetCurrentWindow();
4952 if (window->SkipItems)
4953 return false;
4954
4955 if (p_open && !*p_open)
4956 return false;
4957
4958 ImGuiID id = window->GetID(str: label);
4959 bool is_open = TreeNodeBehavior(id, flags: flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
4960 if (p_open)
4961 {
4962 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
4963 ImGuiContext& g = *GImGui;
4964 ImGuiItemHoveredDataBackup last_item_backup;
4965 float button_radius = g.FontSize * 0.5f;
4966 ImVec2 button_center = ImVec2(ImMin(lhs: window->DC.LastItemRect.Max.x, rhs: window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
4967 if (CloseButton(id: window->GetID(ptr: (void*)(intptr_t)(id+1)), pos: button_center, radius: button_radius))
4968 *p_open = false;
4969 last_item_backup.Restore();
4970 }
4971
4972 return is_open;
4973}
4974
4975//-------------------------------------------------------------------------
4976// [SECTION] Widgets: Selectable
4977//-------------------------------------------------------------------------
4978// - Selectable()
4979//-------------------------------------------------------------------------
4980
4981// Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.
4982// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
4983bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
4984{
4985 ImGuiWindow* window = GetCurrentWindow();
4986 if (window->SkipItems)
4987 return false;
4988
4989 ImGuiContext& g = *GImGui;
4990 const ImGuiStyle& style = g.Style;
4991
4992 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
4993 PopClipRect();
4994
4995 ImGuiID id = window->GetID(str: label);
4996 ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
4997 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
4998 ImVec2 pos = window->DC.CursorPos;
4999 pos.y += window->DC.CurrentLineTextBaseOffset;
5000 ImRect bb_inner(pos, pos + size);
5001 ItemSize(bb: bb_inner);
5002
5003 // Fill horizontal space.
5004 ImVec2 window_padding = window->WindowPadding;
5005 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
5006 float w_draw = ImMax(lhs: label_size.x, rhs: window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
5007 ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
5008 ImRect bb(pos, pos + size_draw);
5009 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
5010 bb.Max.x += window_padding.x;
5011
5012 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
5013 float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
5014 float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
5015 float spacing_R = style.ItemSpacing.x - spacing_L;
5016 float spacing_D = style.ItemSpacing.y - spacing_U;
5017 bb.Min.x -= spacing_L;
5018 bb.Min.y -= spacing_U;
5019 bb.Max.x += spacing_R;
5020 bb.Max.y += spacing_D;
5021 if (!ItemAdd(bb, id: (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
5022 {
5023 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
5024 PushColumnClipRect();
5025 return false;
5026 }
5027
5028 // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
5029 ImGuiButtonFlags button_flags = 0;
5030 if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
5031 if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
5032 if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
5033 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
5034 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
5035 bool hovered, held;
5036 bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: button_flags);
5037 if (flags & ImGuiSelectableFlags_Disabled)
5038 selected = false;
5039
5040 // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
5041 if (pressed || hovered)
5042 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
5043 {
5044 g.NavDisableHighlight = true;
5045 SetNavID(id, nav_layer: window->DC.NavLayerCurrent);
5046 }
5047 if (pressed)
5048 MarkItemEdited(id);
5049
5050 // Render
5051 if (hovered || selected)
5052 {
5053 const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5054 RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: false, rounding: 0.0f);
5055 RenderNavHighlight(bb, id, flags: ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
5056 }
5057
5058 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
5059 {
5060 PushColumnClipRect();
5061 bb.Max.x -= (GetContentRegionMax().x - max_x);
5062 }
5063
5064 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(idx: ImGuiCol_Text, col: g.Style.Colors[ImGuiCol_TextDisabled]);
5065 RenderTextClipped(pos_min: bb_inner.Min, pos_max: bb.Max, text: label, NULL, text_size_if_known: &label_size, align: ImVec2(0.0f,0.0f));
5066 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
5067
5068 // Automatically close popups
5069 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
5070 CloseCurrentPopup();
5071 return pressed;
5072}
5073
5074bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
5075{
5076 if (Selectable(label, selected: *p_selected, flags, size_arg))
5077 {
5078 *p_selected = !*p_selected;
5079 return true;
5080 }
5081 return false;
5082}
5083
5084//-------------------------------------------------------------------------
5085// [SECTION] Widgets: ListBox
5086//-------------------------------------------------------------------------
5087// - ListBox()
5088// - ListBoxHeader()
5089// - ListBoxFooter()
5090//-------------------------------------------------------------------------
5091
5092// FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5093// Helper to calculate the size of a listbox and display a label on the right.
5094// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
5095bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
5096{
5097 ImGuiWindow* window = GetCurrentWindow();
5098 if (window->SkipItems)
5099 return false;
5100
5101 const ImGuiStyle& style = GetStyle();
5102 const ImGuiID id = GetID(str_id: label);
5103 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
5104
5105 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
5106 ImVec2 size = CalcItemSize(size: size_arg, default_x: CalcItemWidth(), default_y: GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
5107 ImVec2 frame_size = ImVec2(size.x, ImMax(lhs: size.y, rhs: label_size.y));
5108 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
5109 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
5110 window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
5111
5112 BeginGroup();
5113 if (label_size.x > 0)
5114 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label);
5115
5116 BeginChildFrame(id, size: frame_bb.GetSize());
5117 return true;
5118}
5119
5120// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5121bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
5122{
5123 // Size default to hold ~7.25 items.
5124 // We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar.
5125 // We don't add this extra bit if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
5126 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
5127 if (height_in_items < 0)
5128 height_in_items = ImMin(lhs: items_count, rhs: 7);
5129 const ImGuiStyle& style = GetStyle();
5130 float height_in_items_f = (height_in_items < items_count) ? (height_in_items + 0.25f) : (height_in_items + 0.00f);
5131
5132 // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
5133 ImVec2 size;
5134 size.x = 0.0f;
5135 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + style.FramePadding.y * 2.0f;
5136 return ListBoxHeader(label, size_arg: size);
5137}
5138
5139// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5140void ImGui::ListBoxFooter()
5141{
5142 ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
5143 const ImRect bb = parent_window->DC.LastItemRect;
5144 const ImGuiStyle& style = GetStyle();
5145
5146 EndChildFrame();
5147
5148 // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
5149 // We call SameLine() to restore DC.CurrentLine* data
5150 SameLine();
5151 parent_window->DC.CursorPos = bb.Min;
5152 ItemSize(bb, text_offset_y: style.FramePadding.y);
5153 EndGroup();
5154}
5155
5156bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
5157{
5158 const bool value_changed = ListBox(label, current_item, items_getter: Items_ArrayGetter, data: (void*)items, items_count, height_in_items: height_items);
5159 return value_changed;
5160}
5161
5162bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
5163{
5164 if (!ListBoxHeader(label, items_count, height_in_items))
5165 return false;
5166
5167 // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
5168 ImGuiContext& g = *GImGui;
5169 bool value_changed = false;
5170 ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
5171 while (clipper.Step())
5172 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
5173 {
5174 const bool item_selected = (i == *current_item);
5175 const char* item_text;
5176 if (!items_getter(data, i, &item_text))
5177 item_text = "*Unknown item*";
5178
5179 PushID(int_id: i);
5180 if (Selectable(label: item_text, selected: item_selected))
5181 {
5182 *current_item = i;
5183 value_changed = true;
5184 }
5185 if (item_selected)
5186 SetItemDefaultFocus();
5187 PopID();
5188 }
5189 ListBoxFooter();
5190 if (value_changed)
5191 MarkItemEdited(id: g.CurrentWindow->DC.LastItemId);
5192
5193 return value_changed;
5194}
5195
5196//-------------------------------------------------------------------------
5197// [SECTION] Widgets: PlotLines, PlotHistogram
5198//-------------------------------------------------------------------------
5199// - PlotEx() [Internal]
5200// - PlotLines()
5201// - PlotHistogram()
5202//-------------------------------------------------------------------------
5203
5204void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
5205{
5206 ImGuiWindow* window = GetCurrentWindow();
5207 if (window->SkipItems)
5208 return;
5209
5210 ImGuiContext& g = *GImGui;
5211 const ImGuiStyle& style = g.Style;
5212
5213 const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
5214 if (graph_size.x == 0.0f)
5215 graph_size.x = CalcItemWidth();
5216 if (graph_size.y == 0.0f)
5217 graph_size.y = label_size.y + (style.FramePadding.y * 2);
5218
5219 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
5220 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
5221 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));
5222 ItemSize(bb: total_bb, text_offset_y: style.FramePadding.y);
5223 if (!ItemAdd(bb: total_bb, id: 0, nav_bb: &frame_bb))
5224 return;
5225 const bool hovered = ItemHoverable(bb: inner_bb, id: 0);
5226
5227 // Determine scale from values if not specified
5228 if (scale_min == FLT_MAX || scale_max == FLT_MAX)
5229 {
5230 float v_min = FLT_MAX;
5231 float v_max = -FLT_MAX;
5232 for (int i = 0; i < values_count; i++)
5233 {
5234 const float v = values_getter(data, i);
5235 v_min = ImMin(lhs: v_min, rhs: v);
5236 v_max = ImMax(lhs: v_max, rhs: v);
5237 }
5238 if (scale_min == FLT_MAX)
5239 scale_min = v_min;
5240 if (scale_max == FLT_MAX)
5241 scale_max = v_max;
5242 }
5243
5244 RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: GetColorU32(idx: ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding);
5245
5246 if (values_count > 0)
5247 {
5248 int res_w = ImMin(lhs: (int)graph_size.x, rhs: values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
5249 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
5250
5251 // Tooltip on hover
5252 int v_hovered = -1;
5253 if (hovered)
5254 {
5255 const float t = ImClamp(v: (g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), mn: 0.0f, mx: 0.9999f);
5256 const int v_idx = (int)(t * item_count);
5257 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
5258
5259 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
5260 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
5261 if (plot_type == ImGuiPlotType_Lines)
5262 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
5263 else if (plot_type == ImGuiPlotType_Histogram)
5264 SetTooltip("%d: %8.4g", v_idx, v0);
5265 v_hovered = v_idx;
5266 }
5267
5268 const float t_step = 1.0f / (float)res_w;
5269 const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
5270
5271 float v0 = values_getter(data, (0 + values_offset) % values_count);
5272 float t0 = 0.0f;
5273 ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate(f: (v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
5274 float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
5275
5276 const ImU32 col_base = GetColorU32(idx: (plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
5277 const ImU32 col_hovered = GetColorU32(idx: (plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
5278
5279 for (int n = 0; n < res_w; n++)
5280 {
5281 const float t1 = t0 + t_step;
5282 const int v1_idx = (int)(t0 * item_count + 0.5f);
5283 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
5284 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
5285 const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate(f: (v1 - scale_min) * inv_scale) );
5286
5287 // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
5288 ImVec2 pos0 = ImLerp(a: inner_bb.Min, b: inner_bb.Max, t: tp0);
5289 ImVec2 pos1 = ImLerp(a: inner_bb.Min, b: inner_bb.Max, t: (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
5290 if (plot_type == ImGuiPlotType_Lines)
5291 {
5292 window->DrawList->AddLine(a: pos0, b: pos1, col: v_hovered == v1_idx ? col_hovered : col_base);
5293 }
5294 else if (plot_type == ImGuiPlotType_Histogram)
5295 {
5296 if (pos1.x >= pos0.x + 2.0f)
5297 pos1.x -= 1.0f;
5298 window->DrawList->AddRectFilled(a: pos0, b: pos1, col: v_hovered == v1_idx ? col_hovered : col_base);
5299 }
5300
5301 t0 = t1;
5302 tp0 = tp1;
5303 }
5304 }
5305
5306 // Text overlay
5307 if (overlay_text)
5308 RenderTextClipped(pos_min: ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), pos_max: frame_bb.Max, text: overlay_text, NULL, NULL, align: ImVec2(0.5f,0.0f));
5309
5310 if (label_size.x > 0.0f)
5311 RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), text: label);
5312}
5313
5314struct ImGuiPlotArrayGetterData
5315{
5316 const float* Values;
5317 int Stride;
5318
5319 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
5320};
5321
5322static float Plot_ArrayGetter(void* data, int idx)
5323{
5324 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
5325 const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
5326 return v;
5327}
5328
5329void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
5330{
5331 ImGuiPlotArrayGetterData data(values, stride);
5332 PlotEx(plot_type: ImGuiPlotType_Lines, label, values_getter: &Plot_ArrayGetter, data: (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5333}
5334
5335void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
5336{
5337 PlotEx(plot_type: ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5338}
5339
5340void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
5341{
5342 ImGuiPlotArrayGetterData data(values, stride);
5343 PlotEx(plot_type: ImGuiPlotType_Histogram, label, values_getter: &Plot_ArrayGetter, data: (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5344}
5345
5346void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
5347{
5348 PlotEx(plot_type: ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5349}
5350
5351//-------------------------------------------------------------------------
5352// [SECTION] Widgets: Value helpers
5353// Those is not very useful, legacy API.
5354//-------------------------------------------------------------------------
5355// - Value()
5356//-------------------------------------------------------------------------
5357
5358void ImGui::Value(const char* prefix, bool b)
5359{
5360 Text(fmt: "%s: %s", prefix, (b ? "true" : "false"));
5361}
5362
5363void ImGui::Value(const char* prefix, int v)
5364{
5365 Text(fmt: "%s: %d", prefix, v);
5366}
5367
5368void ImGui::Value(const char* prefix, unsigned int v)
5369{
5370 Text(fmt: "%s: %d", prefix, v);
5371}
5372
5373void ImGui::Value(const char* prefix, float v, const char* float_format)
5374{
5375 if (float_format)
5376 {
5377 char fmt[64];
5378 ImFormatString(buf: fmt, IM_ARRAYSIZE(fmt), fmt: "%%s: %s", float_format);
5379 Text(fmt, prefix, v);
5380 }
5381 else
5382 {
5383 Text(fmt: "%s: %.3f", prefix, v);
5384 }
5385}
5386
5387//-------------------------------------------------------------------------
5388// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
5389//-------------------------------------------------------------------------
5390// - ImGuiMenuColumns [Internal]
5391// - BeginMainMenuBar()
5392// - EndMainMenuBar()
5393// - BeginMenuBar()
5394// - EndMenuBar()
5395// - BeginMenu()
5396// - EndMenu()
5397// - MenuItem()
5398//-------------------------------------------------------------------------
5399
5400// Helpers for internal use
5401ImGuiMenuColumns::ImGuiMenuColumns()
5402{
5403 Count = 0;
5404 Spacing = Width = NextWidth = 0.0f;
5405 memset(s: Pos, c: 0, n: sizeof(Pos));
5406 memset(s: NextWidths, c: 0, n: sizeof(NextWidths));
5407}
5408
5409void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
5410{
5411 IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
5412 Count = count;
5413 Width = NextWidth = 0.0f;
5414 Spacing = spacing;
5415 if (clear) memset(s: NextWidths, c: 0, n: sizeof(NextWidths));
5416 for (int i = 0; i < Count; i++)
5417 {
5418 if (i > 0 && NextWidths[i] > 0.0f)
5419 Width += Spacing;
5420 Pos[i] = (float)(int)Width;
5421 Width += NextWidths[i];
5422 NextWidths[i] = 0.0f;
5423 }
5424}
5425
5426float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
5427{
5428 NextWidth = 0.0f;
5429 NextWidths[0] = ImMax(lhs: NextWidths[0], rhs: w0);
5430 NextWidths[1] = ImMax(lhs: NextWidths[1], rhs: w1);
5431 NextWidths[2] = ImMax(lhs: NextWidths[2], rhs: w2);
5432 for (int i = 0; i < 3; i++)
5433 NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
5434 return ImMax(lhs: Width, rhs: NextWidth);
5435}
5436
5437float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
5438{
5439 return ImMax(lhs: 0.0f, rhs: avail_w - Width);
5440}
5441
5442// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
5443bool ImGui::BeginMainMenuBar()
5444{
5445 ImGuiContext& g = *GImGui;
5446 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(lhs: g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, rhs: 0.0f));
5447 SetNextWindowPos(pos: ImVec2(0.0f, 0.0f));
5448 SetNextWindowSize(size: ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
5449 PushStyleVar(idx: ImGuiStyleVar_WindowRounding, val: 0.0f);
5450 PushStyleVar(idx: ImGuiStyleVar_WindowMinSize, val: ImVec2(0,0));
5451 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
5452 bool is_open = Begin(name: "##MainMenuBar", NULL, flags: window_flags) && BeginMenuBar();
5453 PopStyleVar(count: 2);
5454 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
5455 if (!is_open)
5456 {
5457 End();
5458 return false;
5459 }
5460 return true;
5461}
5462
5463void ImGui::EndMainMenuBar()
5464{
5465 EndMenuBar();
5466
5467 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
5468 ImGuiContext& g = *GImGui;
5469 if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
5470 FocusPreviousWindowIgnoringOne(ignore_window: g.NavWindow);
5471
5472 End();
5473}
5474
5475bool ImGui::BeginMenuBar()
5476{
5477 ImGuiWindow* window = GetCurrentWindow();
5478 if (window->SkipItems)
5479 return false;
5480 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
5481 return false;
5482
5483 IM_ASSERT(!window->DC.MenuBarAppending);
5484 BeginGroup(); // Backup position on layer 0
5485 PushID(str_id: "##menubar");
5486
5487 // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
5488 // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
5489 ImRect bar_rect = window->MenuBarRect();
5490 ImRect clip_rect(ImFloor(f: bar_rect.Min.x + 0.5f), ImFloor(f: bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(f: ImMax(lhs: bar_rect.Min.x, rhs: bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(f: bar_rect.Max.y + 0.5f));
5491 clip_rect.ClipWith(r: window->OuterRectClipped);
5492 PushClipRect(clip_rect_min: clip_rect.Min, clip_rect_max: clip_rect.Max, intersect_with_current_clip_rect: false);
5493
5494 window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
5495 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
5496 window->DC.NavLayerCurrent++;
5497 window->DC.NavLayerCurrentMask <<= 1;
5498 window->DC.MenuBarAppending = true;
5499 AlignTextToFramePadding();
5500 return true;
5501}
5502
5503void ImGui::EndMenuBar()
5504{
5505 ImGuiWindow* window = GetCurrentWindow();
5506 if (window->SkipItems)
5507 return;
5508 ImGuiContext& g = *GImGui;
5509
5510 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
5511 if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
5512 {
5513 ImGuiWindow* nav_earliest_child = g.NavWindow;
5514 while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
5515 nav_earliest_child = nav_earliest_child->ParentWindow;
5516 if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
5517 {
5518 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
5519 // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
5520 IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
5521 FocusWindow(window);
5522 SetNavIDWithRectRel(id: window->NavLastIds[1], nav_layer: 1, rect_rel: window->NavRectRel[1]);
5523 g.NavLayer = 1;
5524 g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
5525 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
5526 NavMoveRequestCancel();
5527 }
5528 }
5529
5530 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
5531 IM_ASSERT(window->DC.MenuBarAppending);
5532 PopClipRect();
5533 PopID();
5534 window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
5535 window->DC.GroupStack.back().AdvanceCursor = false;
5536 EndGroup(); // Restore position on layer 0
5537 window->DC.LayoutType = ImGuiLayoutType_Vertical;
5538 window->DC.NavLayerCurrent--;
5539 window->DC.NavLayerCurrentMask >>= 1;
5540 window->DC.MenuBarAppending = false;
5541}
5542
5543bool ImGui::BeginMenu(const char* label, bool enabled)
5544{
5545 ImGuiWindow* window = GetCurrentWindow();
5546 if (window->SkipItems)
5547 return false;
5548
5549 ImGuiContext& g = *GImGui;
5550 const ImGuiStyle& style = g.Style;
5551 const ImGuiID id = window->GetID(str: label);
5552
5553 ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
5554
5555 bool pressed;
5556 bool menu_is_open = IsPopupOpen(id);
5557 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
5558 ImGuiWindow* backed_nav_window = g.NavWindow;
5559 if (menuset_is_open)
5560 g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
5561
5562 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
5563 ImVec2 popup_pos, pos = window->DC.CursorPos;
5564 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
5565 {
5566 // Menu inside an horizontal menu bar
5567 // Selectable extend their highlight by half ItemSpacing in each direction.
5568 // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
5569 popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
5570 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
5571 PushStyleVar(idx: ImGuiStyleVar_ItemSpacing, val: style.ItemSpacing * 2.0f);
5572 float w = label_size.x;
5573 pressed = Selectable(label, selected: menu_is_open, flags: ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), size_arg: ImVec2(w, 0.0f));
5574 PopStyleVar();
5575 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
5576 }
5577 else
5578 {
5579 // Menu inside a menu
5580 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
5581 float w = window->MenuColumns.DeclColumns(w0: label_size.x, w1: 0.0f, w2: (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
5582 float extra_w = ImMax(lhs: 0.0f, rhs: GetContentRegionAvail().x - w);
5583 pressed = Selectable(label, selected: menu_is_open, flags: ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), size_arg: ImVec2(w, 0.0f));
5584 if (!enabled) PushStyleColor(idx: ImGuiCol_Text, col: g.Style.Colors[ImGuiCol_TextDisabled]);
5585 RenderArrow(pos: pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), dir: ImGuiDir_Right);
5586 if (!enabled) PopStyleColor();
5587 }
5588
5589 const bool hovered = enabled && ItemHoverable(bb: window->DC.LastItemRect, id);
5590 if (menuset_is_open)
5591 g.NavWindow = backed_nav_window;
5592
5593 bool want_open = false, want_close = false;
5594 if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5595 {
5596 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
5597 bool moving_within_opened_triangle = false;
5598 if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
5599 {
5600 if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
5601 {
5602 ImRect next_window_rect = next_window->Rect();
5603 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
5604 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
5605 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
5606 float extra = ImClamp(v: ImFabs(x: ta.x - tb.x) * 0.30f, mn: 5.0f, mx: 30.0f); // add a bit of extra slack.
5607 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
5608 tb.y = ta.y + ImMax(lhs: (tb.y - extra) - ta.y, rhs: -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
5609 tc.y = ta.y + ImMin(lhs: (tc.y + extra) - ta.y, rhs: +100.0f);
5610 moving_within_opened_triangle = ImTriangleContainsPoint(a: ta, b: tb, c: tc, p: g.IO.MousePos);
5611 //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
5612 }
5613 }
5614
5615 want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
5616 want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
5617
5618 if (g.NavActivateId == id)
5619 {
5620 want_close = menu_is_open;
5621 want_open = !menu_is_open;
5622 }
5623 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
5624 {
5625 want_open = true;
5626 NavMoveRequestCancel();
5627 }
5628 }
5629 else
5630 {
5631 // Menu bar
5632 if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
5633 {
5634 want_close = true;
5635 want_open = menu_is_open = false;
5636 }
5637 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
5638 {
5639 want_open = true;
5640 }
5641 else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
5642 {
5643 want_open = true;
5644 NavMoveRequestCancel();
5645 }
5646 }
5647
5648 if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
5649 want_close = true;
5650 if (want_close && IsPopupOpen(id))
5651 ClosePopupToLevel(remaining: g.CurrentPopupStack.Size);
5652
5653 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
5654 {
5655 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
5656 OpenPopup(str_id: label);
5657 return false;
5658 }
5659
5660 menu_is_open |= want_open;
5661 if (want_open)
5662 OpenPopup(str_id: label);
5663
5664 if (menu_is_open)
5665 {
5666 // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
5667 SetNextWindowPos(pos: popup_pos, cond: ImGuiCond_Always);
5668 ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
5669 if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5670 flags |= ImGuiWindowFlags_ChildWindow;
5671 menu_is_open = BeginPopupEx(id, extra_flags: flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5672 }
5673
5674 return menu_is_open;
5675}
5676
5677void ImGui::EndMenu()
5678{
5679 // Nav: When a left move request _within our child menu_ failed, close the menu.
5680 // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
5681 // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
5682 ImGuiContext& g = *GImGui;
5683 ImGuiWindow* window = g.CurrentWindow;
5684 if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
5685 {
5686 ClosePopupToLevel(remaining: g.OpenPopupStack.Size - 1);
5687 NavMoveRequestCancel();
5688 }
5689
5690 EndPopup();
5691}
5692
5693bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
5694{
5695 ImGuiWindow* window = GetCurrentWindow();
5696 if (window->SkipItems)
5697 return false;
5698
5699 ImGuiContext& g = *GImGui;
5700 ImGuiStyle& style = g.Style;
5701 ImVec2 pos = window->DC.CursorPos;
5702 ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true);
5703
5704 ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
5705 bool pressed;
5706 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
5707 {
5708 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
5709 // Note that in this situation we render neither the shortcut neither the selected tick mark
5710 float w = label_size.x;
5711 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
5712 PushStyleVar(idx: ImGuiStyleVar_ItemSpacing, val: style.ItemSpacing * 2.0f);
5713 pressed = Selectable(label, selected: false, flags, size_arg: ImVec2(w, 0.0f));
5714 PopStyleVar();
5715 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
5716 }
5717 else
5718 {
5719 ImVec2 shortcut_size = shortcut ? CalcTextSize(text: shortcut, NULL) : ImVec2(0.0f, 0.0f);
5720 float w = window->MenuColumns.DeclColumns(w0: label_size.x, w1: shortcut_size.x, w2: (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
5721 float extra_w = ImMax(lhs: 0.0f, rhs: GetContentRegionAvail().x - w);
5722 pressed = Selectable(label, selected: false, flags: flags | ImGuiSelectableFlags_DrawFillAvailWidth, size_arg: ImVec2(w, 0.0f));
5723 if (shortcut_size.x > 0.0f)
5724 {
5725 PushStyleColor(idx: ImGuiCol_Text, col: g.Style.Colors[ImGuiCol_TextDisabled]);
5726 RenderText(pos: pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), text: shortcut, NULL, hide_text_after_hash: false);
5727 PopStyleColor();
5728 }
5729 if (selected)
5730 RenderCheckMark(pos: pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), col: GetColorU32(idx: enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), sz: g.FontSize * 0.866f);
5731 }
5732 return pressed;
5733}
5734
5735bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
5736{
5737 if (MenuItem(label, shortcut, selected: p_selected ? *p_selected : false, enabled))
5738 {
5739 if (p_selected)
5740 *p_selected = !*p_selected;
5741 return true;
5742 }
5743 return false;
5744}
5745

source code of qt3d/src/3rdparty/imgui/imgui_widgets.cpp