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