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