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