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