imgui_widgets.cpp (367873B)
1// dear imgui, v1.76 2// (widgets code) 3 4/* 5 6Index of this file: 7 8// [SECTION] Forward Declarations 9// [SECTION] Widgets: Text, etc. 10// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) 11// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) 12// [SECTION] Widgets: ComboBox 13// [SECTION] Data Type and Data Formatting Helpers 14// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. 15// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. 16// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. 17// [SECTION] Widgets: InputText, InputTextMultiline 18// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. 19// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. 20// [SECTION] Widgets: Selectable 21// [SECTION] Widgets: ListBox 22// [SECTION] Widgets: PlotLines, PlotHistogram 23// [SECTION] Widgets: Value helpers 24// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. 25// [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 85static const signed char IM_S8_MIN = -128; 86static const signed char IM_S8_MAX = 127; 87static const unsigned char IM_U8_MIN = 0; 88static const unsigned char IM_U8_MAX = 0xFF; 89static const signed short IM_S16_MIN = -32768; 90static const signed short IM_S16_MAX = 32767; 91static const unsigned short IM_U16_MIN = 0; 92static const unsigned short IM_U16_MAX = 0xFFFF; 93static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); 94static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) 95static const ImU32 IM_U32_MIN = 0; 96static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) 97#ifdef LLONG_MIN 98static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); 99static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); 100#else 101static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; 102static const ImS64 IM_S64_MAX = 9223372036854775807LL; 103#endif 104static const ImU64 IM_U64_MIN = 0; 105#ifdef ULLONG_MAX 106static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); 107#else 108static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); 109#endif 110 111//------------------------------------------------------------------------- 112// [SECTION] Forward Declarations 113//------------------------------------------------------------------------- 114 115// For InputTextEx() 116static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data); 117static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); 118static 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 139void 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 242void ImGui::TextUnformatted(const char* text, const char* text_end) 243{ 244 TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); 245} 246 247void 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 255void 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 266void 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 274void 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 281void 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 289void 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 296void 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 304void 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 315void 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 324void 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 349void 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. 358void 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 461bool 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 640bool 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 680bool 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. 686bool 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) 698bool 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 720bool 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 750bool 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 757bool 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 787bool 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 812ImGuiID 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. 818ImRect 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 831void 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.. 866bool 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 957void 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. 985bool 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 1021bool 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 1072bool 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 1087bool 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.. 1139bool 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 1148void 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 1184void 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 1220void ImGui::Spacing() 1221{ 1222 ImGuiWindow* window = GetCurrentWindow(); 1223 if (window->SkipItems) 1224 return; 1225 ItemSize(ImVec2(0,0)); 1226} 1227 1228void 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 1239void 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 1255void 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 1267void 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 1324void 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. 1338bool 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 1395static 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. 1405void 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 1448static 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 1456bool 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 1564void ImGui::EndCombo() 1565{ 1566 EndPopup(); 1567} 1568 1569// Getter for the old Combo() API: const char*[] 1570static 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" 1579static 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. 1600bool 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. 1641bool 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" 1648bool 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 1673static 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}; 1691IM_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?! 1696static 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 1717const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) 1718{ 1719 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); 1720 return &GDataTypeInfo[data_type]; 1721} 1722 1723int 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 1746void 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.. 1798bool 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 1899static 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 1907template<typename TYPE> 1908static 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 1920template<typename TYPE, typename SIGNEDTYPE> 1921TYPE 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) 1958template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> 1959bool 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 2066bool 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. 2099bool 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 2173bool 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 2208bool 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 2213bool 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 2218bool 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 2223bool 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 2228bool 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 2253bool 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 2258bool 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 2263bool 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 2268bool 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 2273bool 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 2319template<typename TYPE, typename FLOATTYPE> 2320float 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. 2346template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> 2347bool 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. 2519bool 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. 2553bool 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 2630bool 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 2665bool 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 2670bool 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 2675bool 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 2680bool 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 2685bool 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 2695bool 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 2700bool 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 2705bool 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 2710bool 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 2715bool 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 2774bool 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 2779bool 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 '%' 2806const 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 2819const 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" 2841const 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. 2855int 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. 2879bool 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 2899bool 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. 2923bool 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 2995bool 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 3030bool 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 3036bool 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 3041bool 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 3046bool 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 3053bool 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 3061bool 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 3069bool 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 3077bool 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 3086bool 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 3093bool 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 3098bool 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 3103bool 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 3108bool 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 3123bool 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 3129bool 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 3134bool 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 3140static 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 3154static 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) 3200namespace ImStb 3201{ 3202 3203static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } 3204static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; } 3205static 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); } 3206static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } 3207static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; 3208static 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 3221static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } 3222static 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; } 3223static 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 3225static 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; } 3226static 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 3228static 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 3233static 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 3248static 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?) 3301static 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 3318void ImGuiInputTextState::OnKeyPressed(int key) 3319{ 3320 stb_textedit_key(this, &Stb, key); 3321 CursorFollow = true; 3322 CursorAnimReset(); 3323} 3324 3325ImGuiInputTextCallbackData::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. 3333void 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 3351void 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. 3384static 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) 3461bool 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 4257bool 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. 4265bool 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 4502bool 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() 4512static 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) 4525bool 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. 4904bool 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 4988void 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. 5007void 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 5041void 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 5092void 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 5145bool 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 5154bool 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 5163bool 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 5171bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) 5172{ 5173 return TreeNodeExV(str_id, 0, fmt, args); 5174} 5175 5176bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) 5177{ 5178 return TreeNodeExV(ptr_id, 0, fmt, args); 5179} 5180 5181bool 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 5190bool 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 5199bool 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 5208bool 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 5219bool 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 5230bool 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 5276bool 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 5470void 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 5478void 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 5486void ImGui::TreePushOverrideID(ImGuiID id) 5487{ 5488 ImGuiWindow* window = GetCurrentWindow(); 5489 Indent(); 5490 window->DC.TreeDepth++; 5491 window->IDStack.push_back(id); 5492} 5493 5494void 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() 5517float 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. 5524void 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(). 5536bool 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 5545bool 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. 5587bool 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 5709bool 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" 5733bool 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. 5768bool 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. 5787void 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 5803bool 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 5809bool 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 5851int 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 5969struct ImGuiPlotArrayGetterData 5970{ 5971 const float* Values; 5972 int Stride; 5973 5974 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } 5975}; 5976 5977static 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 5984void 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 5990void 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 5995void 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 6001void 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 6013void ImGui::Value(const char* prefix, bool b) 6014{ 6015 Text("%s: %s", prefix, (b ? "true" : "false")); 6016} 6017 6018void ImGui::Value(const char* prefix, int v) 6019{ 6020 Text("%s: %d", prefix, v); 6021} 6022 6023void ImGui::Value(const char* prefix, unsigned int v) 6024{ 6025 Text("%s: %d", prefix, v); 6026} 6027 6028void 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 6056ImGuiMenuColumns::ImGuiMenuColumns() 6057{ 6058 Spacing = Width = NextWidth = 0.0f; 6059 memset(Pos, 0, sizeof(Pos)); 6060 memset(NextWidths, 0, sizeof(NextWidths)); 6061} 6062 6063void 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 6081float 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 6092float 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. 6101bool 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 6129void 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. 6171bool 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 6191void 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 6204bool 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 6368void 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 6384bool 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 6433bool 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 6463namespace 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 6474ImGuiTabBar::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 6489static 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 6496static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref) 6497{ 6498 ImGuiContext& g = *GImGui; 6499 return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index); 6500} 6501 6502static 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 6510bool 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 6524bool 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 6576void 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. 6608static 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. 6778static 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 6793static float ImGui::TabBarCalcMaxTabWidth() 6794{ 6795 ImGuiContext& g = *GImGui; 6796 return g.FontSize * 20.0f; 6797} 6798 6799ImGuiTabItem* 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. 6809void 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 6819void 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 6835static 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 6841static 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 6861void 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 6869static 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 6923static 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 6970bool 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 6992void 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 7011bool 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() 7211void 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 7224ImVec2 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 7236void 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. 7263bool 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 7336int ImGui::GetColumnIndex() 7337{ 7338 ImGuiWindow* window = GetCurrentWindowRead(); 7339 return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0; 7340} 7341 7342int ImGui::GetColumnsCount() 7343{ 7344 ImGuiWindow* window = GetCurrentWindowRead(); 7345 return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1; 7346} 7347 7348float ImGui::GetColumnOffsetFromNorm(const ImGuiColumns* columns, float offset_norm) 7349{ 7350 return offset_norm * (columns->OffMaxX - columns->OffMinX); 7351} 7352 7353float ImGui::GetColumnNormFromOffset(const ImGuiColumns* columns, float offset) 7354{ 7355 return offset / (columns->OffMaxX - columns->OffMinX); 7356} 7357 7358static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f; 7359 7360static 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 7377float 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 7393static 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 7406float 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 7419void 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 7441void 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 7452void 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) 7464void 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 7477void 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 7487ImGuiColumns* 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 7500ImGuiID 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 7513void 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 7589void 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 7640void 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] 7716void 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