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