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