imgui_tables.cpp (212857B)
1 // dear imgui, v1.83 2 // (tables and columns code) 3 4 /* 5 6 Index of this file: 7 8 // [SECTION] Commentary 9 // [SECTION] Header mess 10 // [SECTION] Tables: Main code 11 // [SECTION] Tables: Simple accessors 12 // [SECTION] Tables: Row changes 13 // [SECTION] Tables: Columns changes 14 // [SECTION] Tables: Columns width management 15 // [SECTION] Tables: Drawing 16 // [SECTION] Tables: Sorting 17 // [SECTION] Tables: Headers 18 // [SECTION] Tables: Context Menu 19 // [SECTION] Tables: Settings (.ini data) 20 // [SECTION] Tables: Garbage Collection 21 // [SECTION] Tables: Debugging 22 // [SECTION] Columns, BeginColumns, EndColumns, etc. 23 24 */ 25 26 // Navigating this file: 27 // - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. 28 // - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. 29 30 //----------------------------------------------------------------------------- 31 // [SECTION] Commentary 32 //----------------------------------------------------------------------------- 33 34 //----------------------------------------------------------------------------- 35 // Typical tables call flow: (root level is generally public API): 36 //----------------------------------------------------------------------------- 37 // - BeginTable() user begin into a table 38 // | BeginChild() - (if ScrollX/ScrollY is set) 39 // | TableBeginInitMemory() - first time table is used 40 // | TableResetSettings() - on settings reset 41 // | TableLoadSettings() - on settings load 42 // | TableBeginApplyRequests() - apply queued resizing/reordering/hiding requests 43 // | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) 44 // | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width 45 // - TableSetupColumn() user submit columns details (optional) 46 // - TableSetupScrollFreeze() user submit scroll freeze information (optional) 47 //----------------------------------------------------------------------------- 48 // - TableUpdateLayout() [Internal] followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow(). 49 // | TableSetupDrawChannels() - setup ImDrawList channels 50 // | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission 51 // | TableDrawContextMenu() - draw right-click context menu 52 //----------------------------------------------------------------------------- 53 // - TableHeadersRow() or TableHeader() user submit a headers row (optional) 54 // | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction 55 // | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu 56 // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) 57 // - TableNextRow() user begin into a new row (also automatically called by TableHeadersRow()) 58 // | TableEndRow() - finish existing row 59 // | TableBeginRow() - add a new row 60 // - TableSetColumnIndex() / TableNextColumn() user begin into a cell 61 // | TableEndCell() - close existing column/cell 62 // | TableBeginCell() - enter into current column/cell 63 // - [...] user emit contents 64 //----------------------------------------------------------------------------- 65 // - EndTable() user ends the table 66 // | TableDrawBorders() - draw outer borders, inner vertical borders 67 // | TableMergeDrawChannels() - merge draw channels if clipping isn't required 68 // | EndChild() - (if ScrollX/ScrollY is set) 69 //----------------------------------------------------------------------------- 70 71 //----------------------------------------------------------------------------- 72 // TABLE SIZING 73 //----------------------------------------------------------------------------- 74 // (Read carefully because this is subtle but it does make sense!) 75 //----------------------------------------------------------------------------- 76 // About 'outer_size': 77 // Its meaning needs to differ slightly depending on if we are using ScrollX/ScrollY flags. 78 // Default value is ImVec2(0.0f, 0.0f). 79 // X 80 // - outer_size.x <= 0.0f -> Right-align from window/work-rect right-most edge. With -FLT_MIN or 0.0f will align exactly on right-most edge. 81 // - outer_size.x > 0.0f -> Set Fixed width. 82 // Y with ScrollX/ScrollY disabled: we output table directly in current window 83 // - outer_size.y < 0.0f -> Bottom-align (but will auto extend, unless _NoHostExtendY is set). Not meaningful is parent window can vertically scroll. 84 // - outer_size.y = 0.0f -> No minimum height (but will auto extend, unless _NoHostExtendY is set) 85 // - outer_size.y > 0.0f -> Set Minimum height (but will auto extend, unless _NoHostExtenY is set) 86 // Y with ScrollX/ScrollY enabled: using a child window for scrolling 87 // - outer_size.y < 0.0f -> Bottom-align. Not meaningful is parent window can vertically scroll. 88 // - outer_size.y = 0.0f -> Bottom-align, consistent with BeginChild(). Not recommended unless table is last item in parent window. 89 // - outer_size.y > 0.0f -> Set Exact height. Recommended when using Scrolling on any axis. 90 //----------------------------------------------------------------------------- 91 // Outer size is also affected by the NoHostExtendX/NoHostExtendY flags. 92 // Important to that note how the two flags have slightly different behaviors! 93 // - ImGuiTableFlags_NoHostExtendX -> Make outer width auto-fit to columns (overriding outer_size.x value). Only available when ScrollX/ScrollY are disabled and Stretch columns are not used. 94 // - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY is disabled. Data below the limit will be clipped and not visible. 95 // In theory ImGuiTableFlags_NoHostExtendY could be the default and any non-scrolling tables with outer_size.y != 0.0f would use exact height. 96 // This would be consistent but perhaps less useful and more confusing (as vertically clipped items are not easily noticeable) 97 //----------------------------------------------------------------------------- 98 // About 'inner_width': 99 // With ScrollX disabled: 100 // - inner_width -> *ignored* 101 // With ScrollX enabled: 102 // - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird 103 // - inner_width = 0.0f -> fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes Fixed columns. 104 // - inner_width > 0.0f -> override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise shrink down), Stretch columns share remaining space! 105 //----------------------------------------------------------------------------- 106 // Details: 107 // - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept 108 // of "available space" doesn't make sense. 109 // - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding 110 // of what the value does. 111 //----------------------------------------------------------------------------- 112 113 //----------------------------------------------------------------------------- 114 // COLUMNS SIZING POLICIES 115 //----------------------------------------------------------------------------- 116 // About overriding column sizing policy and width/weight with TableSetupColumn(): 117 // We use a default parameter of 'init_width_or_weight == -1'. 118 // - with ImGuiTableColumnFlags_WidthFixed, init_width <= 0 (default) --> width is automatic 119 // - with ImGuiTableColumnFlags_WidthFixed, init_width > 0 (explicit) --> width is custom 120 // - with ImGuiTableColumnFlags_WidthStretch, init_weight <= 0 (default) --> weight is 1.0f 121 // - with ImGuiTableColumnFlags_WidthStretch, init_weight > 0 (explicit) --> weight is custom 122 // Widths are specified _without_ CellPadding. If you specify a width of 100.0f, the column will be cover (100.0f + Padding * 2.0f) 123 // and you can fit a 100.0f wide item in it without clipping and with full padding. 124 //----------------------------------------------------------------------------- 125 // About default sizing policy (if you don't specify a ImGuiTableColumnFlags_WidthXXXX flag) 126 // - with Table policy ImGuiTableFlags_SizingFixedFit --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is equal to contents width 127 // - with Table policy ImGuiTableFlags_SizingFixedSame --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is max of all contents width 128 // - with Table policy ImGuiTableFlags_SizingStretchSame --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is 1.0f 129 // - with Table policy ImGuiTableFlags_SizingStretchWeight --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is proportional to contents 130 // Default Width and default Weight can be overridden when calling TableSetupColumn(). 131 //----------------------------------------------------------------------------- 132 // About mixing Fixed/Auto and Stretch columns together: 133 // - the typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns. 134 // - using mixed policies with ScrollX does not make much sense, as using Stretch columns with ScrollX does not make much sense in the first place! 135 // that is, unless 'inner_width' is passed to BeginTable() to explicitly provide a total width to layout columns in. 136 // - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the width of the maximum contents. 137 // - when using ImGuiTableFlags_SizingStretchSame with mixed columns, only the Stretch columns will match their weight/widths. 138 //----------------------------------------------------------------------------- 139 // About using column width: 140 // If a column is manual resizable or has a width specified with TableSetupColumn(): 141 // - you may use GetContentRegionAvail().x to query the width available in a given column. 142 // - right-side alignment features such as SetNextItemWidth(-x) or PushItemWidth(-x) will rely on this width. 143 // If the column is not resizable and has no width specified with TableSetupColumn(): 144 // - its width will be automatic and be set to the max of items submitted. 145 // - therefore you generally cannot have ALL items of the columns use e.g. SetNextItemWidth(-FLT_MIN). 146 // - but if the column has one or more items of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN). 147 //----------------------------------------------------------------------------- 148 149 150 //----------------------------------------------------------------------------- 151 // TABLES CLIPPING/CULLING 152 //----------------------------------------------------------------------------- 153 // About clipping/culling of Rows in Tables: 154 // - For large numbers of rows, it is recommended you use ImGuiListClipper to only submit visible rows. 155 // ImGuiListClipper is reliant on the fact that rows are of equal height. 156 // See 'Demo->Tables->Vertical Scrolling' or 'Demo->Tables->Advanced' for a demo of using the clipper. 157 // - Note that auto-resizing columns don't play well with using the clipper. 158 // By default a table with _ScrollX but without _Resizable will have column auto-resize. 159 // So, if you want to use the clipper, make sure to either enable _Resizable, either setup columns width explicitly with _WidthFixed. 160 //----------------------------------------------------------------------------- 161 // About clipping/culling of Columns in Tables: 162 // - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing 163 // width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know 164 // it is not going to contribute to row height. 165 // In many situations, you may skip submitting contents for every column but one (e.g. the first one). 166 // - Case A: column is not hidden by user, and at least partially in sight (most common case). 167 // - Case B: column is clipped / out of sight (because of scrolling or parent ClipRect): TableNextColumn() return false as a hint but we still allow layout output. 168 // - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.). 169 // 170 // [A] [B] [C] 171 // TableNextColumn(): true false false -> [userland] when TableNextColumn() / TableSetColumnIndex() return false, user can skip submitting items but only if the column doesn't contribute to row height. 172 // SkipItems: false false true -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no layout output. 173 // ClipRect: normal zero-width zero-width -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out mid-way. 174 // ImDrawList output: normal dummy dummy -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted (because cliprect is zero-width anyway). 175 // 176 // - We need to distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row. 177 // However, in the majority of cases, the contribution to row height is the same for all columns, or the tallest cells are known by the programmer. 178 //----------------------------------------------------------------------------- 179 // About clipping/culling of whole Tables: 180 // - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false. 181 //----------------------------------------------------------------------------- 182 183 //----------------------------------------------------------------------------- 184 // [SECTION] Header mess 185 //----------------------------------------------------------------------------- 186 187 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 188 #define _CRT_SECURE_NO_WARNINGS 189 #endif 190 191 #include "imgui.h" 192 #ifndef IMGUI_DISABLE 193 194 #ifndef IMGUI_DEFINE_MATH_OPERATORS 195 #define IMGUI_DEFINE_MATH_OPERATORS 196 #endif 197 #include "imgui_internal.h" 198 199 // System includes 200 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier 201 #include <stddef.h> // intptr_t 202 #else 203 #include <stdint.h> // intptr_t 204 #endif 205 206 // Visual Studio warnings 207 #ifdef _MSC_VER 208 #pragma warning (disable: 4127) // condition expression is constant 209 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen 210 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later 211 #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types 212 #endif 213 #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). 214 #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). 215 #endif 216 217 // Clang/GCC warnings with -Weverything 218 #if defined(__clang__) 219 #if __has_warning("-Wunknown-warning-option") 220 #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! 221 #endif 222 #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' 223 #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. 224 #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. 225 #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. 226 #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness 227 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 228 #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. 229 #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') 230 #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated 231 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision 232 #elif defined(__GNUC__) 233 #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind 234 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked 235 #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 236 #endif 237 238 //----------------------------------------------------------------------------- 239 // [SECTION] Tables: Main code 240 //----------------------------------------------------------------------------- 241 // - TableFixFlags() [Internal] 242 // - TableFindByID() [Internal] 243 // - BeginTable() 244 // - BeginTableEx() [Internal] 245 // - TableBeginInitMemory() [Internal] 246 // - TableBeginApplyRequests() [Internal] 247 // - TableSetupColumnFlags() [Internal] 248 // - TableUpdateLayout() [Internal] 249 // - TableUpdateBorders() [Internal] 250 // - EndTable() 251 // - TableSetupColumn() 252 // - TableSetupScrollFreeze() 253 //----------------------------------------------------------------------------- 254 255 // Configuration 256 static const int TABLE_DRAW_CHANNEL_BG0 = 0; 257 static const int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1; 258 static const int TABLE_DRAW_CHANNEL_NOCLIP = 2; // When using ImGuiTableFlags_NoClip (this becomes the last visible channel) 259 static const float TABLE_BORDER_SIZE = 1.0f; // FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering. 260 static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. 261 static const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f; // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. 262 263 // Helper 264 inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window) 265 { 266 // Adjust flags: set default sizing policy 267 if ((flags & ImGuiTableFlags_SizingMask_) == 0) 268 flags |= ((flags & ImGuiTableFlags_ScrollX) || (outer_window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) ? ImGuiTableFlags_SizingFixedFit : ImGuiTableFlags_SizingStretchSame; 269 270 // Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame 271 if ((flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) 272 flags |= ImGuiTableFlags_NoKeepColumnsVisible; 273 274 // Adjust flags: enforce borders when resizable 275 if (flags & ImGuiTableFlags_Resizable) 276 flags |= ImGuiTableFlags_BordersInnerV; 277 278 // Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on 279 if (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) 280 flags &= ~(ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY); 281 282 // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody 283 if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize) 284 flags &= ~ImGuiTableFlags_NoBordersInBody; 285 286 // Adjust flags: disable saved settings if there's nothing to save 287 if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0) 288 flags |= ImGuiTableFlags_NoSavedSettings; 289 290 // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) 291 #ifdef IMGUI_HAS_DOCK 292 ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop; 293 #else 294 ImGuiWindow* window_for_settings = outer_window->RootWindow; 295 #endif 296 if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings) 297 flags |= ImGuiTableFlags_NoSavedSettings; 298 299 return flags; 300 } 301 302 ImGuiTable* ImGui::TableFindByID(ImGuiID id) 303 { 304 ImGuiContext& g = *GImGui; 305 return g.Tables.GetByKey(id); 306 } 307 308 // Read about "TABLE SIZING" at the top of this file. 309 bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) 310 { 311 ImGuiID id = GetID(str_id); 312 return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width); 313 } 314 315 bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) 316 { 317 ImGuiContext& g = *GImGui; 318 ImGuiWindow* outer_window = GetCurrentWindow(); 319 if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible. 320 return false; 321 322 // Sanity checks 323 IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!"); 324 if (flags & ImGuiTableFlags_ScrollX) 325 IM_ASSERT(inner_width >= 0.0f); 326 327 // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve. 328 const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; 329 const ImVec2 avail_size = GetContentRegionAvail(); 330 ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); 331 ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); 332 if (use_child_window && IsClippedEx(outer_rect, 0, false)) 333 { 334 ItemSize(outer_rect); 335 return false; 336 } 337 338 // Acquire storage for the table 339 ImGuiTable* table = g.Tables.GetOrAddByKey(id); 340 const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; 341 const ImGuiID instance_id = id + instance_no; 342 const ImGuiTableFlags table_last_flags = table->Flags; 343 if (instance_no > 0) 344 IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); 345 346 // Acquire temporary buffers 347 const int table_idx = g.Tables.GetIndex(table); 348 g.CurrentTableStackIdx++; 349 if (g.CurrentTableStackIdx + 1 > g.TablesTempDataStack.Size) 350 g.TablesTempDataStack.resize(g.CurrentTableStackIdx + 1, ImGuiTableTempData()); 351 ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempDataStack[g.CurrentTableStackIdx]; 352 temp_data->TableIndex = table_idx; 353 table->DrawSplitter = &table->TempData->DrawSplitter; 354 table->DrawSplitter->Clear(); 355 356 // Fix flags 357 table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0; 358 flags = TableFixFlags(flags, outer_window); 359 360 // Initialize 361 table->ID = id; 362 table->Flags = flags; 363 table->InstanceCurrent = (ImS16)instance_no; 364 table->LastFrameActive = g.FrameCount; 365 table->OuterWindow = table->InnerWindow = outer_window; 366 table->ColumnsCount = columns_count; 367 table->IsLayoutLocked = false; 368 table->InnerWidth = inner_width; 369 temp_data->UserOuterSize = outer_size; 370 371 // When not using a child window, WorkRect.Max will grow as we append contents. 372 if (use_child_window) 373 { 374 // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent 375 // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) 376 ImVec2 override_content_size(FLT_MAX, FLT_MAX); 377 if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) 378 override_content_size.y = FLT_MIN; 379 380 // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and 381 // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align 382 // based on the right side of the child window work rect, which would require knowing ahead if we are going to 383 // have decoration taking horizontal spaces (typically a vertical scrollbar). 384 if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f) 385 override_content_size.x = inner_width; 386 387 if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) 388 SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f)); 389 390 // Reset scroll if we are reactivating it 391 if ((table_last_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) 392 SetNextWindowScroll(ImVec2(0.0f, 0.0f)); 393 394 // Create scrolling region (without border and zero window padding) 395 ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; 396 BeginChildEx(name, instance_id, outer_rect.GetSize(), false, child_flags); 397 table->InnerWindow = g.CurrentWindow; 398 table->WorkRect = table->InnerWindow->WorkRect; 399 table->OuterRect = table->InnerWindow->Rect(); 400 table->InnerRect = table->InnerWindow->InnerRect; 401 IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f); 402 } 403 else 404 { 405 // For non-scrolling tables, WorkRect == OuterRect == InnerRect. 406 // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable(). 407 table->WorkRect = table->OuterRect = table->InnerRect = outer_rect; 408 } 409 410 // Push a standardized ID for both child-using and not-child-using tables 411 PushOverrideID(instance_id); 412 413 // Backup a copy of host window members we will modify 414 ImGuiWindow* inner_window = table->InnerWindow; 415 table->HostIndentX = inner_window->DC.Indent.x; 416 table->HostClipRect = inner_window->ClipRect; 417 table->HostSkipItems = inner_window->SkipItems; 418 temp_data->HostBackupWorkRect = inner_window->WorkRect; 419 temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect; 420 temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; 421 temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize; 422 temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize; 423 temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos; 424 temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth; 425 temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; 426 inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); 427 428 // Padding and Spacing 429 // - None ........Content..... Pad .....Content........ 430 // - PadOuter | Pad ..Content..... Pad .....Content.. Pad | 431 // - PadInner ........Content.. Pad | Pad ..Content........ 432 // - PadOuter+PadInner | Pad ..Content.. Pad | Pad ..Content.. Pad | 433 const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0; 434 const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true; 435 const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f; 436 const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f; 437 const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f; 438 table->CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border; 439 table->CellSpacingX2 = inner_spacing_explicit; 440 table->CellPaddingX = inner_padding_explicit; 441 table->CellPaddingY = g.Style.CellPadding.y; 442 443 const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; 444 const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f; 445 table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX; 446 447 table->CurrentColumn = -1; 448 table->CurrentRow = -1; 449 table->RowBgColorCounter = 0; 450 table->LastRowFlags = ImGuiTableRowFlags_None; 451 table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; 452 table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width 453 table->InnerClipRect.ClipWithFull(table->HostClipRect); 454 table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y; 455 456 table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow 457 table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() 458 table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any 459 table->FreezeColumnsRequest = table->FreezeColumnsCount = 0; 460 table->IsUnfrozenRows = true; 461 table->DeclColumnsCount = 0; 462 463 // Using opaque colors facilitate overlapping elements of the grid 464 table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); 465 table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); 466 467 // Make table current 468 g.CurrentTable = table; 469 outer_window->DC.CurrentTableIdx = table_idx; 470 if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. 471 inner_window->DC.CurrentTableIdx = table_idx; 472 473 if ((table_last_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0) 474 table->IsResetDisplayOrderRequest = true; 475 476 // Mark as used 477 if (table_idx >= g.TablesLastTimeActive.Size) 478 g.TablesLastTimeActive.resize(table_idx + 1, -1.0f); 479 g.TablesLastTimeActive[table_idx] = (float)g.Time; 480 temp_data->LastTimeActive = (float)g.Time; 481 table->MemoryCompacted = false; 482 483 // Setup memory buffer (clear data if columns count changed) 484 ImGuiTableColumn* old_columns_to_preserve = NULL; 485 void* old_columns_raw_data = NULL; 486 const int old_columns_count = table->Columns.size(); 487 if (old_columns_count != 0 && old_columns_count != columns_count) 488 { 489 // Attempt to preserve width on column count change (#4046) 490 old_columns_to_preserve = table->Columns.Data; 491 old_columns_raw_data = table->RawData; 492 table->RawData = NULL; 493 } 494 if (table->RawData == NULL) 495 { 496 TableBeginInitMemory(table, columns_count); 497 table->IsInitializing = table->IsSettingsRequestLoad = true; 498 } 499 if (table->IsResetAllRequest) 500 TableResetSettings(table); 501 if (table->IsInitializing) 502 { 503 // Initialize 504 table->SettingsOffset = -1; 505 table->IsSortSpecsDirty = true; 506 table->InstanceInteracted = -1; 507 table->ContextPopupColumn = -1; 508 table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1; 509 table->AutoFitSingleColumn = -1; 510 table->HoveredColumnBody = table->HoveredColumnBorder = -1; 511 for (int n = 0; n < columns_count; n++) 512 { 513 ImGuiTableColumn* column = &table->Columns[n]; 514 if (old_columns_to_preserve && n < old_columns_count) 515 { 516 // FIXME: We don't attempt to preserve column order in this path. 517 *column = old_columns_to_preserve[n]; 518 } 519 else 520 { 521 float width_auto = column->WidthAuto; 522 *column = ImGuiTableColumn(); 523 column->WidthAuto = width_auto; 524 column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker 525 column->IsEnabled = column->IsEnabledNextFrame = true; 526 } 527 column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n; 528 } 529 } 530 if (old_columns_raw_data) 531 IM_FREE(old_columns_raw_data); 532 533 // Load settings 534 if (table->IsSettingsRequestLoad) 535 TableLoadSettings(table); 536 537 // Handle DPI/font resize 538 // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. 539 // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. 540 // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. 541 // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. 542 const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? 543 if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) 544 { 545 const float scale_factor = new_ref_scale_unit / table->RefScale; 546 //IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); 547 for (int n = 0; n < columns_count; n++) 548 table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; 549 } 550 table->RefScale = new_ref_scale_unit; 551 552 // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. 553 // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. 554 // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. 555 inner_window->SkipItems = true; 556 557 // Clear names 558 // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn() 559 if (table->ColumnsNames.Buf.Size > 0) 560 table->ColumnsNames.Buf.resize(0); 561 562 // Apply queued resizing/reordering/hiding requests 563 TableBeginApplyRequests(table); 564 565 return true; 566 } 567 568 // For reference, the average total _allocation count_ for a table is: 569 // + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables) 570 // + 1 (for table->RawData allocated below) 571 // + 1 (for table->ColumnsNames, if names are used) 572 // + 1 (for table->Splitter._Channels) 573 // + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels) 574 // Where active_channels_count is variable but often == columns_count or columns_count + 1, see TableSetupDrawChannels() for details. 575 // Unused channels don't perform their +2 allocations. 576 void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count) 577 { 578 // Allocate single buffer for our arrays 579 ImSpanAllocator<3> span_allocator; 580 span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn)); 581 span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx)); 582 span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4); 583 table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes()); 584 memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes()); 585 span_allocator.SetArenaBasePtr(table->RawData); 586 span_allocator.GetSpan(0, &table->Columns); 587 span_allocator.GetSpan(1, &table->DisplayOrderToIndex); 588 span_allocator.GetSpan(2, &table->RowCellData); 589 } 590 591 // Apply queued resizing/reordering/hiding requests 592 void ImGui::TableBeginApplyRequests(ImGuiTable* table) 593 { 594 // Handle resizing request 595 // (We process this at the first TableBegin of the frame) 596 // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling? 597 if (table->InstanceCurrent == 0) 598 { 599 if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) 600 TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth); 601 table->LastResizedColumn = table->ResizedColumn; 602 table->ResizedColumnNextWidth = FLT_MAX; 603 table->ResizedColumn = -1; 604 605 // Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy. 606 // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings. 607 if (table->AutoFitSingleColumn != -1) 608 { 609 TableSetColumnWidth(table->AutoFitSingleColumn, table->Columns[table->AutoFitSingleColumn].WidthAuto); 610 table->AutoFitSingleColumn = -1; 611 } 612 } 613 614 // Handle reordering request 615 // Note: we don't clear ReorderColumn after handling the request. 616 if (table->InstanceCurrent == 0) 617 { 618 if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) 619 table->ReorderColumn = -1; 620 table->HeldHeaderColumn = -1; 621 if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) 622 { 623 // We need to handle reordering across hidden columns. 624 // In the configuration below, moving C to the right of E will lead to: 625 // ... C [D] E ---> ... [D] E C (Column name/index) 626 // ... 2 3 4 ... 2 3 4 (Display order) 627 const int reorder_dir = table->ReorderColumnDir; 628 IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); 629 IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); 630 ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; 631 ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn]; 632 IM_UNUSED(dst_column); 633 const int src_order = src_column->DisplayOrder; 634 const int dst_order = dst_column->DisplayOrder; 635 src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order; 636 for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) 637 table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir; 638 IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); 639 640 // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], 641 // rebuild the later from the former. 642 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 643 table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; 644 table->ReorderColumnDir = 0; 645 table->IsSettingsDirty = true; 646 } 647 } 648 649 // Handle display order reset request 650 if (table->IsResetDisplayOrderRequest) 651 { 652 for (int n = 0; n < table->ColumnsCount; n++) 653 table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n; 654 table->IsResetDisplayOrderRequest = false; 655 table->IsSettingsDirty = true; 656 } 657 } 658 659 // Adjust flags: default width mode + stretch columns are not allowed when auto extending 660 static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in) 661 { 662 ImGuiTableColumnFlags flags = flags_in; 663 664 // Sizing Policy 665 if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) 666 { 667 const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); 668 if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame) 669 flags |= ImGuiTableColumnFlags_WidthFixed; 670 else 671 flags |= ImGuiTableColumnFlags_WidthStretch; 672 } 673 else 674 { 675 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. 676 } 677 678 // Resize 679 if ((table->Flags & ImGuiTableFlags_Resizable) == 0) 680 flags |= ImGuiTableColumnFlags_NoResize; 681 682 // Sorting 683 if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending)) 684 flags |= ImGuiTableColumnFlags_NoSort; 685 686 // Indentation 687 if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0) 688 flags |= (table->Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable; 689 690 // Alignment 691 //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0) 692 // flags |= ImGuiTableColumnFlags_AlignCenter; 693 //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used. 694 695 // Preserve status flags 696 column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_); 697 698 // Build an ordered list of available sort directions 699 column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0; 700 if (table->Flags & ImGuiTableFlags_Sortable) 701 { 702 int count = 0, mask = 0, list = 0; 703 if ((flags & ImGuiTableColumnFlags_PreferSortAscending) != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) { mask |= 1 << ImGuiSortDirection_Ascending; list |= ImGuiSortDirection_Ascending << (count << 1); count++; } 704 if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; } 705 if ((flags & ImGuiTableColumnFlags_PreferSortAscending) == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) { mask |= 1 << ImGuiSortDirection_Ascending; list |= ImGuiSortDirection_Ascending << (count << 1); count++; } 706 if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; } 707 if ((table->Flags & ImGuiTableFlags_SortTristate) || count == 0) { mask |= 1 << ImGuiSortDirection_None; count++; } 708 column->SortDirectionsAvailList = (ImU8)list; 709 column->SortDirectionsAvailMask = (ImU8)mask; 710 column->SortDirectionsAvailCount = (ImU8)count; 711 ImGui::TableFixColumnSortDirection(table, column); 712 } 713 } 714 715 // Layout columns for the frame. This is in essence the followup to BeginTable(). 716 // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. 717 // FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for _WidthAuto columns. 718 // Increase feedback side-effect with widgets relying on WorkRect.Max.x... Maybe provide a default distribution for _WidthAuto columns? 719 void ImGui::TableUpdateLayout(ImGuiTable* table) 720 { 721 ImGuiContext& g = *GImGui; 722 IM_ASSERT(table->IsLayoutLocked == false); 723 724 const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); 725 table->IsDefaultDisplayOrder = true; 726 table->ColumnsEnabledCount = 0; 727 table->EnabledMaskByIndex = 0x00; 728 table->EnabledMaskByDisplayOrder = 0x00; 729 table->LeftMostEnabledColumn = -1; 730 table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE 731 732 // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. 733 // Process columns in their visible orders as we are building the Prev/Next indices. 734 int count_fixed = 0; // Number of columns that have fixed sizing policies 735 int count_stretch = 0; // Number of columns that have stretch sizing policies 736 int prev_visible_column_idx = -1; 737 bool has_auto_fit_request = false; 738 bool has_resizable = false; 739 float stretch_sum_width_auto = 0.0f; 740 float fixed_max_width_auto = 0.0f; 741 for (int order_n = 0; order_n < table->ColumnsCount; order_n++) 742 { 743 const int column_n = table->DisplayOrderToIndex[order_n]; 744 if (column_n != order_n) 745 table->IsDefaultDisplayOrder = false; 746 ImGuiTableColumn* column = &table->Columns[column_n]; 747 748 // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame. 749 // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway. 750 // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect. 751 if (table->DeclColumnsCount <= column_n) 752 { 753 TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None); 754 column->NameOffset = -1; 755 column->UserID = 0; 756 column->InitStretchWeightOrWidth = -1.0f; 757 } 758 759 // Update Enabled state, mark settings/sortspecs dirty 760 if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) 761 column->IsEnabledNextFrame = true; 762 if (column->IsEnabled != column->IsEnabledNextFrame) 763 { 764 column->IsEnabled = column->IsEnabledNextFrame; 765 table->IsSettingsDirty = true; 766 if (!column->IsEnabled && column->SortOrder != -1) 767 table->IsSortSpecsDirty = true; 768 } 769 if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti)) 770 table->IsSortSpecsDirty = true; 771 772 // Auto-fit unsized columns 773 const bool start_auto_fit = (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f); 774 if (start_auto_fit) 775 column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames 776 777 if (!column->IsEnabled) 778 { 779 column->IndexWithinEnabledSet = -1; 780 continue; 781 } 782 783 // Mark as enabled and link to previous/next enabled column 784 column->PrevEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; 785 column->NextEnabledColumn = -1; 786 if (prev_visible_column_idx != -1) 787 table->Columns[prev_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n; 788 else 789 table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n; 790 column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; 791 table->EnabledMaskByIndex |= (ImU64)1 << column_n; 792 table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder; 793 prev_visible_column_idx = column_n; 794 IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); 795 796 // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping) 797 // Combine width from regular rows + width from headers unless requested not to. 798 if (!column->IsPreserveWidthAuto) 799 column->WidthAuto = TableGetColumnWidthAuto(table, column); 800 801 // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto) 802 const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; 803 if (column_is_resizable) 804 has_resizable = true; 805 if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f && !column_is_resizable) 806 column->WidthAuto = column->InitStretchWeightOrWidth; 807 808 if (column->AutoFitQueue != 0x00) 809 has_auto_fit_request = true; 810 if (column->Flags & ImGuiTableColumnFlags_WidthStretch) 811 { 812 stretch_sum_width_auto += column->WidthAuto; 813 count_stretch++; 814 } 815 else 816 { 817 fixed_max_width_auto = ImMax(fixed_max_width_auto, column->WidthAuto); 818 count_fixed++; 819 } 820 } 821 if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) 822 table->IsSortSpecsDirty = true; 823 table->RightMostEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; 824 IM_ASSERT(table->LeftMostEnabledColumn >= 0 && table->RightMostEnabledColumn >= 0); 825 826 // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible 827 // to avoid the column fitting having to wait until the first visible frame of the child container (may or not be a good thing). 828 // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time. 829 if (has_auto_fit_request && table->OuterWindow != table->InnerWindow) 830 table->InnerWindow->SkipItems = false; 831 if (has_auto_fit_request) 832 table->IsSettingsDirty = true; 833 834 // [Part 3] Fix column flags and record a few extra information. 835 float sum_width_requests = 0.0f; // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns but including spacing/padding. 836 float stretch_sum_weights = 0.0f; // Sum of all weights for stretch columns. 837 table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1; 838 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 839 { 840 if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) 841 continue; 842 ImGuiTableColumn* column = &table->Columns[column_n]; 843 844 const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; 845 if (column->Flags & ImGuiTableColumnFlags_WidthFixed) 846 { 847 // Apply same widths policy 848 float width_auto = column->WidthAuto; 849 if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable)) 850 width_auto = fixed_max_width_auto; 851 852 // Apply automatic width 853 // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) 854 if (column->AutoFitQueue != 0x00) 855 column->WidthRequest = width_auto; 856 else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n))) 857 column->WidthRequest = width_auto; 858 859 // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets 860 // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very 861 // large height (= first frame scrollbar display very off + clipper would skip lots of items). 862 // This is merely making the side-effect less extreme, but doesn't properly fixes it. 863 // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? 864 // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller. 865 if (column->AutoFitQueue > 0x01 && table->IsInitializing && !column->IsPreserveWidthAuto) 866 column->WidthRequest = ImMax(column->WidthRequest, table->MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale? 867 sum_width_requests += column->WidthRequest; 868 } 869 else 870 { 871 // Initialize stretch weight 872 if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f || !column_is_resizable) 873 { 874 if (column->InitStretchWeightOrWidth > 0.0f) 875 column->StretchWeight = column->InitStretchWeightOrWidth; 876 else if (table_sizing_policy == ImGuiTableFlags_SizingStretchProp) 877 column->StretchWeight = (column->WidthAuto / stretch_sum_width_auto) * count_stretch; 878 else 879 column->StretchWeight = 1.0f; 880 } 881 882 stretch_sum_weights += column->StretchWeight; 883 if (table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder > column->DisplayOrder) 884 table->LeftMostStretchedColumn = (ImGuiTableColumnIdx)column_n; 885 if (table->RightMostStretchedColumn == -1 || table->Columns[table->RightMostStretchedColumn].DisplayOrder < column->DisplayOrder) 886 table->RightMostStretchedColumn = (ImGuiTableColumnIdx)column_n; 887 } 888 column->IsPreserveWidthAuto = false; 889 sum_width_requests += table->CellPaddingX * 2.0f; 890 } 891 table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed; 892 893 // [Part 4] Apply final widths based on requested widths 894 const ImRect work_rect = table->WorkRect; 895 const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); 896 const float width_avail = ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth(); 897 const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests; 898 float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; 899 table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; 900 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 901 { 902 if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) 903 continue; 904 ImGuiTableColumn* column = &table->Columns[column_n]; 905 906 // Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest) 907 if (column->Flags & ImGuiTableColumnFlags_WidthStretch) 908 { 909 float weight_ratio = column->StretchWeight / stretch_sum_weights; 910 column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, table->MinColumnWidth) + 0.01f); 911 width_remaining_for_stretched_columns -= column->WidthRequest; 912 } 913 914 // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column 915 // See additional comments in TableSetColumnWidth(). 916 if (column->NextEnabledColumn == -1 && table->LeftMostStretchedColumn != -1) 917 column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; 918 919 // Assign final width, record width in case we will need to shrink 920 column->WidthGiven = ImFloor(ImMax(column->WidthRequest, table->MinColumnWidth)); 921 table->ColumnsGivenWidth += column->WidthGiven; 922 } 923 924 // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). 925 // Using right-to-left distribution (more likely to match resizing cursor). 926 if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths)) 927 for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) 928 { 929 if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) 930 continue; 931 ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; 932 if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) 933 continue; 934 column->WidthRequest += 1.0f; 935 column->WidthGiven += 1.0f; 936 width_remaining_for_stretched_columns -= 1.0f; 937 } 938 939 table->HoveredColumnBody = -1; 940 table->HoveredColumnBorder = -1; 941 const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table->LastOuterHeight)); 942 const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0); 943 944 // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column 945 // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping. 946 int visible_n = 0; 947 bool offset_x_frozen = (table->FreezeColumnsCount > 0); 948 float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; 949 ImRect host_clip_rect = table->InnerClipRect; 950 //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2; 951 table->VisibleMaskByIndex = 0x00; 952 table->RequestOutputMaskByIndex = 0x00; 953 for (int order_n = 0; order_n < table->ColumnsCount; order_n++) 954 { 955 const int column_n = table->DisplayOrderToIndex[order_n]; 956 ImGuiTableColumn* column = &table->Columns[column_n]; 957 958 column->NavLayerCurrent = (ImS8)((table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); 959 960 if (offset_x_frozen && table->FreezeColumnsCount == visible_n) 961 { 962 offset_x += work_rect.Min.x - table->OuterRect.Min.x; 963 offset_x_frozen = false; 964 } 965 966 // Clear status flags 967 column->Flags &= ~ImGuiTableColumnFlags_StatusMask_; 968 969 if ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) 970 { 971 // Hidden column: clear a few fields and we are done with it for the remainder of the function. 972 // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. 973 column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x; 974 column->WidthGiven = 0.0f; 975 column->ClipRect.Min.y = work_rect.Min.y; 976 column->ClipRect.Max.y = FLT_MAX; 977 column->ClipRect.ClipWithFull(host_clip_rect); 978 column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false; 979 column->IsSkipItems = true; 980 column->ItemWidth = 1.0f; 981 continue; 982 } 983 984 // Detect hovered column 985 if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x) 986 table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n; 987 988 // Lock start position 989 column->MinX = offset_x; 990 991 // Lock width based on start position and minimum/maximum width for this position 992 float max_width = TableGetMaxColumnWidth(table, column_n); 993 column->WidthGiven = ImMin(column->WidthGiven, max_width); 994 column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, table->MinColumnWidth)); 995 column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; 996 997 // Lock other positions 998 // - ClipRect.Min.x: Because merging draw commands doesn't compare min boundaries, we make ClipRect.Min.x match left bounds to be consistent regardless of merging. 999 // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) makes things more consistent when resizing down, tho slightly detrimental to visibility in very-small column. 1000 // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow. 1001 // - FIXME-TABLE: We want equal width columns to have equal (ClipRect.Max.x - WorkMinX) width, which means ClipRect.max.x cannot stray off host_clip_rect.Max.x else right-most column may appear shorter. 1002 column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1; 1003 column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max 1004 column->ItemWidth = ImFloor(column->WidthGiven * 0.65f); 1005 column->ClipRect.Min.x = column->MinX; 1006 column->ClipRect.Min.y = work_rect.Min.y; 1007 column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX; 1008 column->ClipRect.Max.y = FLT_MAX; 1009 column->ClipRect.ClipWithFull(host_clip_rect); 1010 1011 // Mark column as Clipped (not in sight) 1012 // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables. 1013 // FIXME-TABLE: Because InnerClipRect.Max.y is conservatively ==outer_window->ClipRect.Max.y, we never can mark columns _Above_ the scroll line as not IsVisibleY. 1014 // Taking advantage of LastOuterHeight would yield good results there... 1015 // FIXME-TABLE: Y clipping is disabled because it effectively means not submitting will reduce contents width which is fed to outer_window->DC.CursorMaxPos.x, 1016 // and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, but could be something else). 1017 // Possible solution to preserve last known content width for clipped column. Test 'table_reported_size' fails when enabling Y clipping and window is resized small. 1018 column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x); 1019 column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y); 1020 const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY; 1021 if (is_visible) 1022 table->VisibleMaskByIndex |= ((ImU64)1 << column_n); 1023 1024 // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. 1025 column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; 1026 if (column->IsRequestOutput) 1027 table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n); 1028 1029 // Mark column as SkipItems (ignoring all items/layout) 1030 column->IsSkipItems = !column->IsEnabled || table->HostSkipItems; 1031 if (column->IsSkipItems) 1032 IM_ASSERT(!is_visible); 1033 1034 // Update status flags 1035 column->Flags |= ImGuiTableColumnFlags_IsEnabled; 1036 if (is_visible) 1037 column->Flags |= ImGuiTableColumnFlags_IsVisible; 1038 if (column->SortOrder != -1) 1039 column->Flags |= ImGuiTableColumnFlags_IsSorted; 1040 if (table->HoveredColumnBody == column_n) 1041 column->Flags |= ImGuiTableColumnFlags_IsHovered; 1042 1043 // Alignment 1044 // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in 1045 // many cases (to be able to honor this we might be able to store a log of cells width, per row, for 1046 // visible rows, but nav/programmatic scroll would have visible artifacts.) 1047 //if (column->Flags & ImGuiTableColumnFlags_AlignRight) 1048 // column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen); 1049 //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) 1050 // column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); 1051 1052 // Reset content width variables 1053 column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX; 1054 column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX; 1055 1056 // Don't decrement auto-fit counters until container window got a chance to submit its items 1057 if (table->HostSkipItems == false) 1058 { 1059 column->AutoFitQueue >>= 1; 1060 column->CannotSkipItemsQueue >>= 1; 1061 } 1062 1063 if (visible_n < table->FreezeColumnsCount) 1064 host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x); 1065 1066 offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; 1067 visible_n++; 1068 } 1069 1070 // [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) 1071 // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either 1072 // because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu. 1073 const float unused_x1 = ImMax(table->WorkRect.Min.x, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x); 1074 if (is_hovering_table && table->HoveredColumnBody == -1) 1075 { 1076 if (g.IO.MousePos.x >= unused_x1) 1077 table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount; 1078 } 1079 if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable)) 1080 table->Flags &= ~ImGuiTableFlags_Resizable; 1081 1082 // [Part 8] Lock actual OuterRect/WorkRect right-most position. 1083 // This is done late to handle the case of fixed-columns tables not claiming more widths that they need. 1084 // Because of this we are careful with uses of WorkRect and InnerClipRect before this point. 1085 if (table->RightMostStretchedColumn != -1) 1086 table->Flags &= ~ImGuiTableFlags_NoHostExtendX; 1087 if (table->Flags & ImGuiTableFlags_NoHostExtendX) 1088 { 1089 table->OuterRect.Max.x = table->WorkRect.Max.x = unused_x1; 1090 table->InnerClipRect.Max.x = ImMin(table->InnerClipRect.Max.x, unused_x1); 1091 } 1092 table->InnerWindow->ParentWorkRect = table->WorkRect; 1093 table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); 1094 table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); 1095 1096 // [Part 9] Allocate draw channels and setup background cliprect 1097 TableSetupDrawChannels(table); 1098 1099 // [Part 10] Hit testing on borders 1100 if (table->Flags & ImGuiTableFlags_Resizable) 1101 TableUpdateBorders(table); 1102 table->LastFirstRowHeight = 0.0f; 1103 table->IsLayoutLocked = true; 1104 table->IsUsingHeaders = false; 1105 1106 // [Part 11] Context menu 1107 if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted) 1108 { 1109 const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); 1110 if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) 1111 { 1112 TableDrawContextMenu(table); 1113 EndPopup(); 1114 } 1115 else 1116 { 1117 table->IsContextPopupOpen = false; 1118 } 1119 } 1120 1121 // [Part 13] Sanitize and build sort specs before we have a change to use them for display. 1122 // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change) 1123 if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable)) 1124 TableSortSpecsBuild(table); 1125 1126 // Initial state 1127 ImGuiWindow* inner_window = table->InnerWindow; 1128 if (table->Flags & ImGuiTableFlags_NoClip) 1129 table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); 1130 else 1131 inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); 1132 } 1133 1134 // Process hit-testing on resizing borders. Actual size change will be applied in EndTable() 1135 // - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise 1136 // - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets 1137 // overlapping the same area. 1138 void ImGui::TableUpdateBorders(ImGuiTable* table) 1139 { 1140 ImGuiContext& g = *GImGui; 1141 IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); 1142 1143 // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and 1144 // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not 1145 // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). 1146 // Actual columns highlight/render will be performed in EndTable() and not be affected. 1147 const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; 1148 const float hit_y1 = table->OuterRect.Min.y; 1149 const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); 1150 const float hit_y2_head = hit_y1 + table->LastFirstRowHeight; 1151 1152 for (int order_n = 0; order_n < table->ColumnsCount; order_n++) 1153 { 1154 if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) 1155 continue; 1156 1157 const int column_n = table->DisplayOrderToIndex[order_n]; 1158 ImGuiTableColumn* column = &table->Columns[column_n]; 1159 if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) 1160 continue; 1161 1162 // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() 1163 const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body; 1164 if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) 1165 continue; 1166 1167 if (table->FreezeColumnsCount > 0) 1168 if (column->MaxX < table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsCount - 1]].MaxX) 1169 continue; 1170 1171 ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); 1172 ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); 1173 //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); 1174 KeepAliveID(column_id); 1175 1176 bool hovered = false, held = false; 1177 bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); 1178 if (pressed && IsMouseDoubleClicked(0)) 1179 { 1180 TableSetColumnWidthAutoSingle(table, column_n); 1181 ClearActiveID(); 1182 held = hovered = false; 1183 } 1184 if (held) 1185 { 1186 if (table->LastResizedColumn == -1) 1187 table->ResizeLockMinContentsX2 = table->RightMostEnabledColumn != -1 ? table->Columns[table->RightMostEnabledColumn].MaxX : -FLT_MAX; 1188 table->ResizedColumn = (ImGuiTableColumnIdx)column_n; 1189 table->InstanceInteracted = table->InstanceCurrent; 1190 } 1191 if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) 1192 { 1193 table->HoveredColumnBorder = (ImGuiTableColumnIdx)column_n; 1194 SetMouseCursor(ImGuiMouseCursor_ResizeEW); 1195 } 1196 } 1197 } 1198 1199 void ImGui::EndTable() 1200 { 1201 ImGuiContext& g = *GImGui; 1202 ImGuiTable* table = g.CurrentTable; 1203 IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); 1204 1205 // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some 1206 // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) 1207 //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); 1208 1209 // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our 1210 // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. 1211 if (!table->IsLayoutLocked) 1212 TableUpdateLayout(table); 1213 1214 const ImGuiTableFlags flags = table->Flags; 1215 ImGuiWindow* inner_window = table->InnerWindow; 1216 ImGuiWindow* outer_window = table->OuterWindow; 1217 ImGuiTableTempData* temp_data = table->TempData; 1218 IM_ASSERT(inner_window == g.CurrentWindow); 1219 IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); 1220 1221 if (table->IsInsideRow) 1222 TableEndRow(table); 1223 1224 // Context menu in columns body 1225 if (flags & ImGuiTableFlags_ContextMenuInBody) 1226 if (table->HoveredColumnBody != -1 && !IsAnyItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) 1227 TableOpenContextMenu((int)table->HoveredColumnBody); 1228 1229 // Finalize table height 1230 inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize; 1231 inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize; 1232 inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos; 1233 const float inner_content_max_y = table->RowPosY2; 1234 IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y); 1235 if (inner_window != outer_window) 1236 inner_window->DC.CursorMaxPos.y = inner_content_max_y; 1237 else if (!(flags & ImGuiTableFlags_NoHostExtendY)) 1238 table->OuterRect.Max.y = table->InnerRect.Max.y = ImMax(table->OuterRect.Max.y, inner_content_max_y); // Patch OuterRect/InnerRect height 1239 table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); 1240 table->LastOuterHeight = table->OuterRect.GetHeight(); 1241 1242 // Setup inner scrolling range 1243 // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y, 1244 // but since the later is likely to be impossible to do we'd rather update both axises together. 1245 if (table->Flags & ImGuiTableFlags_ScrollX) 1246 { 1247 const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; 1248 float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x; 1249 if (table->RightMostEnabledColumn != -1) 1250 max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); 1251 if (table->ResizedColumn != -1) 1252 max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); 1253 table->InnerWindow->DC.CursorMaxPos.x = max_pos_x; 1254 } 1255 1256 // Pop clipping rect 1257 if (!(flags & ImGuiTableFlags_NoClip)) 1258 inner_window->DrawList->PopClipRect(); 1259 inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); 1260 1261 // Draw borders 1262 if ((flags & ImGuiTableFlags_Borders) != 0) 1263 TableDrawBorders(table); 1264 1265 #if 0 1266 // Strip out dummy channel draw calls 1267 // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out) 1268 // Pros: remove draw calls which will have no effect. since they'll have zero-size cliprect they may be early out anyway. 1269 // Cons: making it harder for users watching metrics/debugger to spot the wasted vertices. 1270 if (table->DummyDrawChannel != (ImGuiTableColumnIdx)-1) 1271 { 1272 ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel]; 1273 dummy_channel->_CmdBuffer.resize(0); 1274 dummy_channel->_IdxBuffer.resize(0); 1275 } 1276 #endif 1277 1278 // Flatten channels and merge draw calls 1279 ImDrawListSplitter* splitter = table->DrawSplitter; 1280 splitter->SetCurrentChannel(inner_window->DrawList, 0); 1281 if ((table->Flags & ImGuiTableFlags_NoClip) == 0) 1282 TableMergeDrawChannels(table); 1283 splitter->Merge(inner_window->DrawList); 1284 1285 // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable() 1286 const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); 1287 table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; 1288 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 1289 if (table->EnabledMaskByIndex & ((ImU64)1 << column_n)) 1290 { 1291 ImGuiTableColumn* column = &table->Columns[column_n]; 1292 if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) 1293 table->ColumnsAutoFitWidth += column->WidthRequest; 1294 else 1295 table->ColumnsAutoFitWidth += TableGetColumnWidthAuto(table, column); 1296 } 1297 1298 // Update scroll 1299 if ((table->Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window) 1300 { 1301 inner_window->Scroll.x = 0.0f; 1302 } 1303 else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceCurrent) 1304 { 1305 // When releasing a column being resized, scroll to keep the resulting column in sight 1306 const float neighbor_width_to_keep_visible = table->MinColumnWidth + table->CellPaddingX * 2.0f; 1307 ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn]; 1308 if (column->MaxX < table->InnerClipRect.Min.x) 1309 SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f); 1310 else if (column->MaxX > table->InnerClipRect.Max.x) 1311 SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f); 1312 } 1313 1314 // Apply resizing/dragging at the end of the frame 1315 if (table->ResizedColumn != -1 && table->InstanceCurrent == table->InstanceInteracted) 1316 { 1317 ImGuiTableColumn* column = &table->Columns[table->ResizedColumn]; 1318 const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS); 1319 const float new_width = ImFloor(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f); 1320 table->ResizedColumnNextWidth = new_width; 1321 } 1322 1323 // Pop from id stack 1324 IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); 1325 IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!"); 1326 PopID(); 1327 1328 // Restore window data that we modified 1329 const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos; 1330 inner_window->WorkRect = temp_data->HostBackupWorkRect; 1331 inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect; 1332 inner_window->SkipItems = table->HostSkipItems; 1333 outer_window->DC.CursorPos = table->OuterRect.Min; 1334 outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth; 1335 outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize; 1336 outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset; 1337 1338 // Layout in outer window 1339 // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding 1340 // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414) 1341 if (inner_window != outer_window) 1342 { 1343 EndChild(); 1344 } 1345 else 1346 { 1347 ItemSize(table->OuterRect.GetSize()); 1348 ItemAdd(table->OuterRect, 0); 1349 } 1350 1351 // Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar 1352 if (table->Flags & ImGuiTableFlags_NoHostExtendX) 1353 { 1354 // FIXME-TABLE: Could we remove this section? 1355 // ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents 1356 IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0); 1357 outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth); 1358 } 1359 else if (temp_data->UserOuterSize.x <= 0.0f) 1360 { 1361 const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f; 1362 outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x); 1363 outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth)); 1364 } 1365 else 1366 { 1367 outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x); 1368 } 1369 if (temp_data->UserOuterSize.y <= 0.0f) 1370 { 1371 const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f; 1372 outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y); 1373 outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y)); 1374 } 1375 else 1376 { 1377 // OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set) 1378 outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table->OuterRect.Max.y); 1379 } 1380 1381 // Save settings 1382 if (table->IsSettingsDirty) 1383 TableSaveSettings(table); 1384 table->IsInitializing = false; 1385 1386 // Clear or restore current table, if any 1387 IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table); 1388 IM_ASSERT(g.CurrentTableStackIdx >= 0); 1389 g.CurrentTableStackIdx--; 1390 temp_data = g.CurrentTableStackIdx >= 0 ? &g.TablesTempDataStack[g.CurrentTableStackIdx] : NULL; 1391 g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL; 1392 if (g.CurrentTable) 1393 { 1394 g.CurrentTable->TempData = temp_data; 1395 g.CurrentTable->DrawSplitter = &temp_data->DrawSplitter; 1396 } 1397 outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; 1398 } 1399 1400 // See "COLUMN SIZING POLICIES" comments at the top of this file 1401 // If (init_width_or_weight <= 0.0f) it is ignored 1402 void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) 1403 { 1404 ImGuiContext& g = *GImGui; 1405 ImGuiTable* table = g.CurrentTable; 1406 IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); 1407 IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); 1408 IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); 1409 if (table->DeclColumnsCount >= table->ColumnsCount) 1410 { 1411 IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, "Called TableSetupColumn() too many times!"); 1412 return; 1413 } 1414 1415 ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; 1416 table->DeclColumnsCount++; 1417 1418 // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa. 1419 // Give a grace to users of ImGuiTableFlags_ScrollX. 1420 if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0) 1421 IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column."); 1422 1423 // When passing a width automatically enforce WidthFixed policy 1424 // (whereas TableSetupColumnFlags would default to WidthAuto if table is not Resizable) 1425 if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f) 1426 if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) 1427 flags |= ImGuiTableColumnFlags_WidthFixed; 1428 1429 TableSetupColumnFlags(table, column, flags); 1430 column->UserID = user_id; 1431 flags = column->Flags; 1432 1433 // Initialize defaults 1434 column->InitStretchWeightOrWidth = init_width_or_weight; 1435 if (table->IsInitializing) 1436 { 1437 // Init width or weight 1438 if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) 1439 { 1440 if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) 1441 column->WidthRequest = init_width_or_weight; 1442 if (flags & ImGuiTableColumnFlags_WidthStretch) 1443 column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; 1444 1445 // Disable auto-fit if an explicit width/weight has been specified 1446 if (init_width_or_weight > 0.0f) 1447 column->AutoFitQueue = 0x00; 1448 } 1449 1450 // Init default visibility/sort state 1451 if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) 1452 column->IsEnabled = column->IsEnabledNextFrame = false; 1453 if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) 1454 { 1455 column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. 1456 column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); 1457 } 1458 } 1459 1460 // Store name (append with zero-terminator in contiguous buffer) 1461 column->NameOffset = -1; 1462 if (label != NULL && label[0] != 0) 1463 { 1464 column->NameOffset = (ImS16)table->ColumnsNames.size(); 1465 table->ColumnsNames.append(label, label + strlen(label) + 1); 1466 } 1467 } 1468 1469 // [Public] 1470 void ImGui::TableSetupScrollFreeze(int columns, int rows) 1471 { 1472 ImGuiContext& g = *GImGui; 1473 ImGuiTable* table = g.CurrentTable; 1474 IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); 1475 IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); 1476 IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); 1477 IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit 1478 1479 table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)columns : 0; 1480 table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; 1481 table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0; 1482 table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; 1483 table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b 1484 } 1485 1486 //----------------------------------------------------------------------------- 1487 // [SECTION] Tables: Simple accessors 1488 //----------------------------------------------------------------------------- 1489 // - TableGetColumnCount() 1490 // - TableGetColumnName() 1491 // - TableGetColumnName() [Internal] 1492 // - TableSetColumnEnabled() [Internal] 1493 // - TableGetColumnFlags() 1494 // - TableGetCellBgRect() [Internal] 1495 // - TableGetColumnResizeID() [Internal] 1496 // - TableGetHoveredColumn() [Internal] 1497 // - TableSetBgColor() 1498 //----------------------------------------------------------------------------- 1499 1500 int ImGui::TableGetColumnCount() 1501 { 1502 ImGuiContext& g = *GImGui; 1503 ImGuiTable* table = g.CurrentTable; 1504 return table ? table->ColumnsCount : 0; 1505 } 1506 1507 const char* ImGui::TableGetColumnName(int column_n) 1508 { 1509 ImGuiContext& g = *GImGui; 1510 ImGuiTable* table = g.CurrentTable; 1511 if (!table) 1512 return NULL; 1513 if (column_n < 0) 1514 column_n = table->CurrentColumn; 1515 return TableGetColumnName(table, column_n); 1516 } 1517 1518 const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) 1519 { 1520 if (table->IsLayoutLocked == false && column_n >= table->DeclColumnsCount) 1521 return ""; // NameOffset is invalid at this point 1522 const ImGuiTableColumn* column = &table->Columns[column_n]; 1523 if (column->NameOffset == -1) 1524 return ""; 1525 return &table->ColumnsNames.Buf[column->NameOffset]; 1526 } 1527 1528 // Request enabling/disabling a column (often perceived as "showing/hiding" from users point of view) 1529 // Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) 1530 // Request will be applied during next layout, which happens on the first call to TableNextRow() after BeginTable() 1531 // For the getter you can use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled) 1532 void ImGui::TableSetColumnEnabled(int column_n, bool enabled) 1533 { 1534 ImGuiContext& g = *GImGui; 1535 ImGuiTable* table = g.CurrentTable; 1536 IM_ASSERT(table != NULL); 1537 if (!table) 1538 return; 1539 if (column_n < 0) 1540 column_n = table->CurrentColumn; 1541 IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); 1542 ImGuiTableColumn* column = &table->Columns[column_n]; 1543 column->IsEnabledNextFrame = enabled; 1544 } 1545 1546 // We allow querying for an extra column in order to poll the IsHovered state of the right-most section 1547 ImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n) 1548 { 1549 ImGuiContext& g = *GImGui; 1550 ImGuiTable* table = g.CurrentTable; 1551 if (!table) 1552 return ImGuiTableColumnFlags_None; 1553 if (column_n < 0) 1554 column_n = table->CurrentColumn; 1555 if (column_n == table->ColumnsCount) 1556 return (table->HoveredColumnBody == column_n) ? ImGuiTableColumnFlags_IsHovered : ImGuiTableColumnFlags_None; 1557 return table->Columns[column_n].Flags; 1558 } 1559 1560 // Return the cell rectangle based on currently known height. 1561 // - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations. 1562 // The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it. 1563 // - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right 1564 // columns report a small offset so their CellBgRect can extend up to the outer border. 1565 ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n) 1566 { 1567 const ImGuiTableColumn* column = &table->Columns[column_n]; 1568 float x1 = column->MinX; 1569 float x2 = column->MaxX; 1570 if (column->PrevEnabledColumn == -1) 1571 x1 -= table->CellSpacingX1; 1572 if (column->NextEnabledColumn == -1) 1573 x2 += table->CellSpacingX2; 1574 return ImRect(x1, table->RowPosY1, x2, table->RowPosY2); 1575 } 1576 1577 // Return the resizing ID for the right-side of the given column. 1578 ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no) 1579 { 1580 IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); 1581 ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n; 1582 return id; 1583 } 1584 1585 // Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. 1586 int ImGui::TableGetHoveredColumn() 1587 { 1588 ImGuiContext& g = *GImGui; 1589 ImGuiTable* table = g.CurrentTable; 1590 if (!table) 1591 return -1; 1592 return (int)table->HoveredColumnBody; 1593 } 1594 1595 void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n) 1596 { 1597 ImGuiContext& g = *GImGui; 1598 ImGuiTable* table = g.CurrentTable; 1599 IM_ASSERT(target != ImGuiTableBgTarget_None); 1600 1601 if (color == IM_COL32_DISABLE) 1602 color = 0; 1603 1604 // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. 1605 switch (target) 1606 { 1607 case ImGuiTableBgTarget_CellBg: 1608 { 1609 if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard 1610 return; 1611 if (column_n == -1) 1612 column_n = table->CurrentColumn; 1613 if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) 1614 return; 1615 if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) 1616 table->RowCellDataCurrent++; 1617 ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCurrent]; 1618 cell_data->BgColor = color; 1619 cell_data->Column = (ImGuiTableColumnIdx)column_n; 1620 break; 1621 } 1622 case ImGuiTableBgTarget_RowBg0: 1623 case ImGuiTableBgTarget_RowBg1: 1624 { 1625 if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard 1626 return; 1627 IM_ASSERT(column_n == -1); 1628 int bg_idx = (target == ImGuiTableBgTarget_RowBg1) ? 1 : 0; 1629 table->RowBgColor[bg_idx] = color; 1630 break; 1631 } 1632 default: 1633 IM_ASSERT(0); 1634 } 1635 } 1636 1637 //------------------------------------------------------------------------- 1638 // [SECTION] Tables: Row changes 1639 //------------------------------------------------------------------------- 1640 // - TableGetRowIndex() 1641 // - TableNextRow() 1642 // - TableBeginRow() [Internal] 1643 // - TableEndRow() [Internal] 1644 //------------------------------------------------------------------------- 1645 1646 // [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows 1647 int ImGui::TableGetRowIndex() 1648 { 1649 ImGuiContext& g = *GImGui; 1650 ImGuiTable* table = g.CurrentTable; 1651 if (!table) 1652 return 0; 1653 return table->CurrentRow; 1654 } 1655 1656 // [Public] Starts into the first cell of a new row 1657 void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) 1658 { 1659 ImGuiContext& g = *GImGui; 1660 ImGuiTable* table = g.CurrentTable; 1661 1662 if (!table->IsLayoutLocked) 1663 TableUpdateLayout(table); 1664 if (table->IsInsideRow) 1665 TableEndRow(table); 1666 1667 table->LastRowFlags = table->RowFlags; 1668 table->RowFlags = row_flags; 1669 table->RowMinHeight = row_min_height; 1670 TableBeginRow(table); 1671 1672 // We honor min_row_height requested by user, but cannot guarantee per-row maximum height, 1673 // because that would essentially require a unique clipping rectangle per-cell. 1674 table->RowPosY2 += table->CellPaddingY * 2.0f; 1675 table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); 1676 1677 // Disable output until user calls TableNextColumn() 1678 table->InnerWindow->SkipItems = true; 1679 } 1680 1681 // [Internal] Called by TableNextRow() 1682 void ImGui::TableBeginRow(ImGuiTable* table) 1683 { 1684 ImGuiWindow* window = table->InnerWindow; 1685 IM_ASSERT(!table->IsInsideRow); 1686 1687 // New row 1688 table->CurrentRow++; 1689 table->CurrentColumn = -1; 1690 table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; 1691 table->RowCellDataCurrent = -1; 1692 table->IsInsideRow = true; 1693 1694 // Begin frozen rows 1695 float next_y1 = table->RowPosY2; 1696 if (table->CurrentRow == 0 && table->FreezeRowsCount > 0) 1697 next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y; 1698 1699 table->RowPosY1 = table->RowPosY2 = next_y1; 1700 table->RowTextBaseline = 0.0f; 1701 table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent 1702 window->DC.PrevLineTextBaseOffset = 0.0f; 1703 window->DC.CursorMaxPos.y = next_y1; 1704 1705 // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. 1706 if (table->RowFlags & ImGuiTableRowFlags_Headers) 1707 { 1708 TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg)); 1709 if (table->CurrentRow == 0) 1710 table->IsUsingHeaders = true; 1711 } 1712 } 1713 1714 // [Internal] Called by TableNextRow() 1715 void ImGui::TableEndRow(ImGuiTable* table) 1716 { 1717 ImGuiContext& g = *GImGui; 1718 ImGuiWindow* window = g.CurrentWindow; 1719 IM_ASSERT(window == table->InnerWindow); 1720 IM_ASSERT(table->IsInsideRow); 1721 1722 if (table->CurrentColumn != -1) 1723 TableEndCell(table); 1724 1725 // Logging 1726 if (g.LogEnabled) 1727 LogRenderedText(NULL, "|"); 1728 1729 // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is 1730 // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. 1731 window->DC.CursorPos.y = table->RowPosY2; 1732 1733 // Row background fill 1734 const float bg_y1 = table->RowPosY1; 1735 const float bg_y2 = table->RowPosY2; 1736 const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount); 1737 const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest); 1738 if (table->CurrentRow == 0) 1739 table->LastFirstRowHeight = bg_y2 - bg_y1; 1740 1741 const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y); 1742 if (is_visible) 1743 { 1744 // Decide of background color for the row 1745 ImU32 bg_col0 = 0; 1746 ImU32 bg_col1 = 0; 1747 if (table->RowBgColor[0] != IM_COL32_DISABLE) 1748 bg_col0 = table->RowBgColor[0]; 1749 else if (table->Flags & ImGuiTableFlags_RowBg) 1750 bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); 1751 if (table->RowBgColor[1] != IM_COL32_DISABLE) 1752 bg_col1 = table->RowBgColor[1]; 1753 1754 // Decide of top border color 1755 ImU32 border_col = 0; 1756 const float border_size = TABLE_BORDER_SIZE; 1757 if (table->CurrentRow > 0 || table->InnerWindow == table->OuterWindow) 1758 if (table->Flags & ImGuiTableFlags_BordersInnerH) 1759 border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; 1760 1761 const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; 1762 const bool draw_strong_bottom_border = unfreeze_rows_actual; 1763 if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) 1764 { 1765 // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is 1766 // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. 1767 if ((table->Flags & ImGuiTableFlags_NoClip) == 0) 1768 window->DrawList->_CmdHeader.ClipRect = table->Bg0ClipRectForDrawCmd.ToVec4(); 1769 table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0); 1770 } 1771 1772 // Draw row background 1773 // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle 1774 if (bg_col0 || bg_col1) 1775 { 1776 ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); 1777 row_rect.ClipWith(table->BgClipRect); 1778 if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y) 1779 window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0); 1780 if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y) 1781 window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1); 1782 } 1783 1784 // Draw cell background color 1785 if (draw_cell_bg_color) 1786 { 1787 ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCurrent]; 1788 for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) 1789 { 1790 const ImGuiTableColumn* column = &table->Columns[cell_data->Column]; 1791 ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column); 1792 cell_bg_rect.ClipWith(table->BgClipRect); 1793 cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x); // So that first column after frozen one gets clipped 1794 cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX); 1795 window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor); 1796 } 1797 } 1798 1799 // Draw top border 1800 if (border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) 1801 window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size); 1802 1803 // Draw bottom border at the row unfreezing mark (always strong) 1804 if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) 1805 window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); 1806 } 1807 1808 // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) 1809 // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and 1810 // get the new cursor position. 1811 if (unfreeze_rows_request) 1812 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 1813 { 1814 ImGuiTableColumn* column = &table->Columns[column_n]; 1815 column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); 1816 } 1817 if (unfreeze_rows_actual) 1818 { 1819 IM_ASSERT(table->IsUnfrozenRows == false); 1820 table->IsUnfrozenRows = true; 1821 1822 // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect 1823 float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); 1824 table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); 1825 table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; 1826 table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; 1827 IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); 1828 1829 float row_height = table->RowPosY2 - table->RowPosY1; 1830 table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; 1831 table->RowPosY1 = table->RowPosY2 - row_height; 1832 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 1833 { 1834 ImGuiTableColumn* column = &table->Columns[column_n]; 1835 column->DrawChannelCurrent = column->DrawChannelUnfrozen; 1836 column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; 1837 } 1838 1839 // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y 1840 SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); 1841 table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); 1842 } 1843 1844 if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) 1845 table->RowBgColorCounter++; 1846 table->IsInsideRow = false; 1847 } 1848 1849 //------------------------------------------------------------------------- 1850 // [SECTION] Tables: Columns changes 1851 //------------------------------------------------------------------------- 1852 // - TableGetColumnIndex() 1853 // - TableSetColumnIndex() 1854 // - TableNextColumn() 1855 // - TableBeginCell() [Internal] 1856 // - TableEndCell() [Internal] 1857 //------------------------------------------------------------------------- 1858 1859 int ImGui::TableGetColumnIndex() 1860 { 1861 ImGuiContext& g = *GImGui; 1862 ImGuiTable* table = g.CurrentTable; 1863 if (!table) 1864 return 0; 1865 return table->CurrentColumn; 1866 } 1867 1868 // [Public] Append into a specific column 1869 bool ImGui::TableSetColumnIndex(int column_n) 1870 { 1871 ImGuiContext& g = *GImGui; 1872 ImGuiTable* table = g.CurrentTable; 1873 if (!table) 1874 return false; 1875 1876 if (table->CurrentColumn != column_n) 1877 { 1878 if (table->CurrentColumn != -1) 1879 TableEndCell(table); 1880 IM_ASSERT(column_n >= 0 && table->ColumnsCount); 1881 TableBeginCell(table, column_n); 1882 } 1883 1884 // Return whether the column is visible. User may choose to skip submitting items based on this return value, 1885 // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. 1886 return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; 1887 } 1888 1889 // [Public] Append into the next column, wrap and create a new row when already on last column 1890 bool ImGui::TableNextColumn() 1891 { 1892 ImGuiContext& g = *GImGui; 1893 ImGuiTable* table = g.CurrentTable; 1894 if (!table) 1895 return false; 1896 1897 if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) 1898 { 1899 if (table->CurrentColumn != -1) 1900 TableEndCell(table); 1901 TableBeginCell(table, table->CurrentColumn + 1); 1902 } 1903 else 1904 { 1905 TableNextRow(); 1906 TableBeginCell(table, 0); 1907 } 1908 1909 // Return whether the column is visible. User may choose to skip submitting items based on this return value, 1910 // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. 1911 int column_n = table->CurrentColumn; 1912 return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; 1913 } 1914 1915 1916 // [Internal] Called by TableSetColumnIndex()/TableNextColumn() 1917 // This is called very frequently, so we need to be mindful of unnecessary overhead. 1918 // FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. 1919 void ImGui::TableBeginCell(ImGuiTable* table, int column_n) 1920 { 1921 ImGuiTableColumn* column = &table->Columns[column_n]; 1922 ImGuiWindow* window = table->InnerWindow; 1923 table->CurrentColumn = column_n; 1924 1925 // Start position is roughly ~~ CellRect.Min + CellPadding + Indent 1926 float start_x = column->WorkMinX; 1927 if (column->Flags & ImGuiTableColumnFlags_IndentEnable) 1928 start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row. 1929 1930 window->DC.CursorPos.x = start_x; 1931 window->DC.CursorPos.y = table->RowPosY1 + table->CellPaddingY; 1932 window->DC.CursorMaxPos.x = window->DC.CursorPos.x; 1933 window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT 1934 window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; 1935 window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent; 1936 1937 window->WorkRect.Min.y = window->DC.CursorPos.y; 1938 window->WorkRect.Min.x = column->WorkMinX; 1939 window->WorkRect.Max.x = column->WorkMaxX; 1940 window->DC.ItemWidth = column->ItemWidth; 1941 1942 // To allow ImGuiListClipper to function we propagate our row height 1943 if (!column->IsEnabled) 1944 window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); 1945 1946 window->SkipItems = column->IsSkipItems; 1947 if (column->IsSkipItems) 1948 { 1949 window->DC.LastItemId = 0; 1950 window->DC.LastItemStatusFlags = 0; 1951 } 1952 1953 if (table->Flags & ImGuiTableFlags_NoClip) 1954 { 1955 // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. 1956 table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); 1957 //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP); 1958 } 1959 else 1960 { 1961 // FIXME-TABLE: Could avoid this if draw channel is dummy channel? 1962 SetWindowClipRectBeforeSetChannel(window, column->ClipRect); 1963 table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); 1964 } 1965 1966 // Logging 1967 ImGuiContext& g = *GImGui; 1968 if (g.LogEnabled && !column->IsSkipItems) 1969 { 1970 LogRenderedText(&window->DC.CursorPos, "|"); 1971 g.LogLinePosY = FLT_MAX; 1972 } 1973 } 1974 1975 // [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn() 1976 void ImGui::TableEndCell(ImGuiTable* table) 1977 { 1978 ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; 1979 ImGuiWindow* window = table->InnerWindow; 1980 1981 // Report maximum position so we can infer content size per column. 1982 float* p_max_pos_x; 1983 if (table->RowFlags & ImGuiTableRowFlags_Headers) 1984 p_max_pos_x = &column->ContentMaxXHeadersUsed; // Useful in case user submit contents in header row that is not a TableHeader() call 1985 else 1986 p_max_pos_x = table->IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen; 1987 *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x); 1988 table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY); 1989 column->ItemWidth = window->DC.ItemWidth; 1990 1991 // Propagate text baseline for the entire row 1992 // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. 1993 table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset); 1994 } 1995 1996 //------------------------------------------------------------------------- 1997 // [SECTION] Tables: Columns width management 1998 //------------------------------------------------------------------------- 1999 // - TableGetMaxColumnWidth() [Internal] 2000 // - TableGetColumnWidthAuto() [Internal] 2001 // - TableSetColumnWidth() 2002 // - TableSetColumnWidthAutoSingle() [Internal] 2003 // - TableSetColumnWidthAutoAll() [Internal] 2004 // - TableUpdateColumnsWeightFromWidth() [Internal] 2005 //------------------------------------------------------------------------- 2006 2007 // Maximum column content width given current layout. Use column->MinX so this value on a per-column basis. 2008 float ImGui::TableGetMaxColumnWidth(const ImGuiTable* table, int column_n) 2009 { 2010 const ImGuiTableColumn* column = &table->Columns[column_n]; 2011 float max_width = FLT_MAX; 2012 const float min_column_distance = table->MinColumnWidth + table->CellPaddingX * 2.0f + table->CellSpacingX1 + table->CellSpacingX2; 2013 if (table->Flags & ImGuiTableFlags_ScrollX) 2014 { 2015 // Frozen columns can't reach beyond visible width else scrolling will naturally break. 2016 if (column->DisplayOrder < table->FreezeColumnsRequest) 2017 { 2018 max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX; 2019 max_width = max_width - table->OuterPaddingX - table->CellPaddingX - table->CellSpacingX2; 2020 } 2021 } 2022 else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0) 2023 { 2024 // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make 2025 // sure they are all visible. Because of this we also know that all of the columns will always fit in 2026 // table->WorkRect and therefore in table->InnerRect (because ScrollX is off) 2027 // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match. 2028 // See "table_width_distrib" and "table_width_keep_visible" tests 2029 max_width = table->WorkRect.Max.x - (table->ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - column->MinX; 2030 //max_width -= table->CellSpacingX1; 2031 max_width -= table->CellSpacingX2; 2032 max_width -= table->CellPaddingX * 2.0f; 2033 max_width -= table->OuterPaddingX; 2034 } 2035 return max_width; 2036 } 2037 2038 // Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field 2039 float ImGui::TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column) 2040 { 2041 const float content_width_body = ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX; 2042 const float content_width_headers = column->ContentMaxXHeadersIdeal - column->WorkMinX; 2043 float width_auto = content_width_body; 2044 if (!(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) 2045 width_auto = ImMax(width_auto, content_width_headers); 2046 2047 // Non-resizable fixed columns preserve their requested width 2048 if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f) 2049 if (!(table->Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize)) 2050 width_auto = column->InitStretchWeightOrWidth; 2051 2052 return ImMax(width_auto, table->MinColumnWidth); 2053 } 2054 2055 // 'width' = inner column width, without padding 2056 void ImGui::TableSetColumnWidth(int column_n, float width) 2057 { 2058 ImGuiContext& g = *GImGui; 2059 ImGuiTable* table = g.CurrentTable; 2060 IM_ASSERT(table != NULL && table->IsLayoutLocked == false); 2061 IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); 2062 ImGuiTableColumn* column_0 = &table->Columns[column_n]; 2063 float column_0_width = width; 2064 2065 // Apply constraints early 2066 // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) 2067 IM_ASSERT(table->MinColumnWidth > 0.0f); 2068 const float min_width = table->MinColumnWidth; 2069 const float max_width = ImMax(min_width, TableGetMaxColumnWidth(table, column_n)); 2070 column_0_width = ImClamp(column_0_width, min_width, max_width); 2071 if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width) 2072 return; 2073 2074 //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width); 2075 ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : NULL; 2076 2077 // In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns. 2078 // - All fixed: easy. 2079 // - All stretch: easy. 2080 // - One or more fixed + one stretch: easy. 2081 // - One or more fixed + more than one stretch: tricky. 2082 // Qt when manual resize is enabled only support a single _trailing_ stretch column. 2083 2084 // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1. 2085 // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user. 2086 // Scenarios: 2087 // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset. 2088 // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered. 2089 // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Stretch column will always be minimal size. 2090 // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) 2091 // - W1 W2 W3 resize from W1| or W2| --> ok 2092 // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) 2093 // - W1 F2 F3 resize from F3| --> ok: no-op (disabled by Resize Rule 1) 2094 // - W1 F2 resize from F2| --> ok: no-op (disabled by Resize Rule 1) 2095 // - W1 W2 F3 resize from W1| or W2| --> ok 2096 // - W1 F2 W3 resize from W1| or F2| --> ok 2097 // - F1 W2 F3 resize from W2| --> ok 2098 // - F1 W3 F2 resize from W3| --> ok 2099 // - W1 F2 F3 resize from W1| --> ok: equivalent to resizing |F2. F3 will not move. 2100 // - W1 F2 F3 resize from F2| --> ok 2101 // All resizes from a Wx columns are locking other columns. 2102 2103 // Possible improvements: 2104 // - W1 W2 W3 resize W1| --> to not be stuck, both W2 and W3 would stretch down. Seems possible to fix. Would be most beneficial to simplify resize of all-weighted columns. 2105 // - W3 F1 F2 resize W3| --> to not be stuck past F1|, both F1 and F2 would need to stretch down, which would be lossy or ambiguous. Seems hard to fix. 2106 2107 // [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). 2108 2109 // If we have all Fixed columns OR resizing a Fixed column that doesn't come after a Stretch one, we can do an offsetting resize. 2110 // This is the preferred resize path 2111 if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) 2112 if (!column_1 || table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder >= column_0->DisplayOrder) 2113 { 2114 column_0->WidthRequest = column_0_width; 2115 table->IsSettingsDirty = true; 2116 return; 2117 } 2118 2119 // We can also use previous column if there's no next one (this is used when doing an auto-fit on the right-most stretch column) 2120 if (column_1 == NULL) 2121 column_1 = (column_0->PrevEnabledColumn != -1) ? &table->Columns[column_0->PrevEnabledColumn] : NULL; 2122 if (column_1 == NULL) 2123 return; 2124 2125 // Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column. 2126 // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) 2127 float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); 2128 column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; 2129 IM_ASSERT(column_0_width > 0.0f && column_1_width > 0.0f); 2130 column_0->WidthRequest = column_0_width; 2131 column_1->WidthRequest = column_1_width; 2132 if ((column_0->Flags | column_1->Flags) & ImGuiTableColumnFlags_WidthStretch) 2133 TableUpdateColumnsWeightFromWidth(table); 2134 table->IsSettingsDirty = true; 2135 } 2136 2137 // Disable clipping then auto-fit, will take 2 frames 2138 // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) 2139 void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n) 2140 { 2141 // Single auto width uses auto-fit 2142 ImGuiTableColumn* column = &table->Columns[column_n]; 2143 if (!column->IsEnabled) 2144 return; 2145 column->CannotSkipItemsQueue = (1 << 0); 2146 table->AutoFitSingleColumn = (ImGuiTableColumnIdx)column_n; 2147 } 2148 2149 void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table) 2150 { 2151 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2152 { 2153 ImGuiTableColumn* column = &table->Columns[column_n]; 2154 if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Cannot reset weight of hidden stretch column 2155 continue; 2156 column->CannotSkipItemsQueue = (1 << 0); 2157 column->AutoFitQueue = (1 << 1); 2158 } 2159 } 2160 2161 void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) 2162 { 2163 IM_ASSERT(table->LeftMostStretchedColumn != -1 && table->RightMostStretchedColumn != -1); 2164 2165 // Measure existing quantity 2166 float visible_weight = 0.0f; 2167 float visible_width = 0.0f; 2168 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2169 { 2170 ImGuiTableColumn* column = &table->Columns[column_n]; 2171 if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) 2172 continue; 2173 IM_ASSERT(column->StretchWeight > 0.0f); 2174 visible_weight += column->StretchWeight; 2175 visible_width += column->WidthRequest; 2176 } 2177 IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f); 2178 2179 // Apply new weights 2180 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2181 { 2182 ImGuiTableColumn* column = &table->Columns[column_n]; 2183 if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) 2184 continue; 2185 column->StretchWeight = (column->WidthRequest / visible_width) * visible_weight; 2186 IM_ASSERT(column->StretchWeight > 0.0f); 2187 } 2188 } 2189 2190 //------------------------------------------------------------------------- 2191 // [SECTION] Tables: Drawing 2192 //------------------------------------------------------------------------- 2193 // - TablePushBackgroundChannel() [Internal] 2194 // - TablePopBackgroundChannel() [Internal] 2195 // - TableSetupDrawChannels() [Internal] 2196 // - TableMergeDrawChannels() [Internal] 2197 // - TableDrawBorders() [Internal] 2198 //------------------------------------------------------------------------- 2199 2200 // Bg2 is used by Selectable (and possibly other widgets) to render to the background. 2201 // Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect. 2202 void ImGui::TablePushBackgroundChannel() 2203 { 2204 ImGuiContext& g = *GImGui; 2205 ImGuiWindow* window = g.CurrentWindow; 2206 ImGuiTable* table = g.CurrentTable; 2207 2208 // Optimization: avoid SetCurrentChannel() + PushClipRect() 2209 table->HostBackupInnerClipRect = window->ClipRect; 2210 SetWindowClipRectBeforeSetChannel(window, table->Bg2ClipRectForDrawCmd); 2211 table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent); 2212 } 2213 2214 void ImGui::TablePopBackgroundChannel() 2215 { 2216 ImGuiContext& g = *GImGui; 2217 ImGuiWindow* window = g.CurrentWindow; 2218 ImGuiTable* table = g.CurrentTable; 2219 ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; 2220 2221 // Optimization: avoid PopClipRect() + SetCurrentChannel() 2222 SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); 2223 table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); 2224 } 2225 2226 // Allocate draw channels. Called by TableUpdateLayout() 2227 // - We allocate them following storage order instead of display order so reordering columns won't needlessly 2228 // increase overall dormant memory cost. 2229 // - We isolate headers draw commands in their own channels instead of just altering clip rects. 2230 // This is in order to facilitate merging of draw commands. 2231 // - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels. 2232 // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other 2233 // channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. 2234 // - We allocate 1 or 2 background draw channels. This is because we know TablePushBackgroundChannel() is only used for 2235 // horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4). 2236 // Draw channel allocation (before merging): 2237 // - NoClip --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call) 2238 // - Clip --> 2+D+N channels 2239 // - FreezeRows --> 2+D+N*2 (unless scrolling value is zero) 2240 // - FreezeRows || FreezeColunns --> 3+D+N*2 (unless scrolling value is zero) 2241 // Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0. 2242 void ImGui::TableSetupDrawChannels(ImGuiTable* table) 2243 { 2244 const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; 2245 const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount; 2246 const int channels_for_bg = 1 + 1 * freeze_row_multiplier; 2247 const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0; 2248 const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; 2249 table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total); 2250 table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1); 2251 table->Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN; 2252 table->Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN); 2253 2254 int draw_channel_current = 2; 2255 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2256 { 2257 ImGuiTableColumn* column = &table->Columns[column_n]; 2258 if (column->IsVisibleX && column->IsVisibleY) 2259 { 2260 column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current); 2261 column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0)); 2262 if (!(table->Flags & ImGuiTableFlags_NoClip)) 2263 draw_channel_current++; 2264 } 2265 else 2266 { 2267 column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel; 2268 } 2269 column->DrawChannelCurrent = column->DrawChannelFrozen; 2270 } 2271 2272 // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default. 2273 // All our cell highlight are manually clipped with BgClipRect. When unfreezing it will be made smaller to fit scrolling rect. 2274 // (This technically isn't part of setting up draw channels, but is reasonably related to be done here) 2275 table->BgClipRect = table->InnerClipRect; 2276 table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect; 2277 table->Bg2ClipRectForDrawCmd = table->HostClipRect; 2278 IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y); 2279 } 2280 2281 // This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). 2282 // For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect, 2283 // actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels(). 2284 // 2285 // Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve 2286 // this we merge their clip rect and make them contiguous in the channel list, so they can be merged 2287 // by the call to DrawSplitter.Merge() following to the call to this function. 2288 // We reorder draw commands by arranging them into a maximum of 4 distinct groups: 2289 // 2290 // 1 group: 2 groups: 2 groups: 4 groups: 2291 // [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze 2292 // [ .. ] or no scroll [ 2. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll 2293 // 2294 // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). 2295 // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group 2296 // based on its position (within frozen rows/columns groups or not). 2297 // At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect. 2298 // This function assume that each column are pointing to a distinct draw channel, 2299 // otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask. 2300 // 2301 // Column channels will not be merged into one of the 1-4 groups in the following cases: 2302 // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). 2303 // Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds 2304 // matches, by e.g. calling SetCursorScreenPos(). 2305 // - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here.. 2306 // we could do better but it's going to be rare and probably not worth the hassle. 2307 // Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd. 2308 // 2309 // This function is particularly tricky to understand.. take a breath. 2310 void ImGui::TableMergeDrawChannels(ImGuiTable* table) 2311 { 2312 ImGuiContext& g = *GImGui; 2313 ImDrawListSplitter* splitter = table->DrawSplitter; 2314 const bool has_freeze_v = (table->FreezeRowsCount > 0); 2315 const bool has_freeze_h = (table->FreezeColumnsCount > 0); 2316 IM_ASSERT(splitter->_Current == 0); 2317 2318 // Track which groups we are going to attempt to merge, and which channels goes into each group. 2319 struct MergeGroup 2320 { 2321 ImRect ClipRect; 2322 int ChannelsCount; 2323 ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> ChannelsMask; 2324 2325 MergeGroup() { ChannelsCount = 0; } 2326 }; 2327 int merge_group_mask = 0x00; 2328 MergeGroup merge_groups[4]; 2329 2330 // 1. Scan channels and take note of those which can be merged 2331 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2332 { 2333 if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) 2334 continue; 2335 ImGuiTableColumn* column = &table->Columns[column_n]; 2336 2337 const int merge_group_sub_count = has_freeze_v ? 2 : 1; 2338 for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++) 2339 { 2340 const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen; 2341 2342 // Don't attempt to merge if there are multiple draw calls within the column 2343 ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; 2344 if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0) 2345 src_channel->_CmdBuffer.pop_back(); 2346 if (src_channel->_CmdBuffer.Size != 1) 2347 continue; 2348 2349 // Find out the width of this merge group and check if it will fit in our column 2350 // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it) 2351 if (!(column->Flags & ImGuiTableColumnFlags_NoClip)) 2352 { 2353 float content_max_x; 2354 if (!has_freeze_v) 2355 content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze 2356 else if (merge_group_sub_n == 0) 2357 content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed); // Row freeze: use width before freeze 2358 else 2359 content_max_x = column->ContentMaxXUnfrozen; // Row freeze: use width after freeze 2360 if (content_max_x > column->ClipRect.Max.x) 2361 continue; 2362 } 2363 2364 const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2); 2365 IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); 2366 MergeGroup* merge_group = &merge_groups[merge_group_n]; 2367 if (merge_group->ChannelsCount == 0) 2368 merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); 2369 merge_group->ChannelsMask.SetBit(channel_no); 2370 merge_group->ChannelsCount++; 2371 merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); 2372 merge_group_mask |= (1 << merge_group_n); 2373 } 2374 2375 // Invalidate current draw channel 2376 // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data) 2377 column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx)-1; 2378 } 2379 2380 // [DEBUG] Display merge groups 2381 #if 0 2382 if (g.IO.KeyShift) 2383 for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) 2384 { 2385 MergeGroup* merge_group = &merge_groups[merge_group_n]; 2386 if (merge_group->ChannelsCount == 0) 2387 continue; 2388 char buf[32]; 2389 ImFormatString(buf, 32, "MG%d:%d", merge_group_n, merge_group->ChannelsCount); 2390 ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4); 2391 ImVec2 text_size = CalcTextSize(buf, NULL); 2392 GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255)); 2393 GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL); 2394 GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255)); 2395 } 2396 #endif 2397 2398 // 2. Rewrite channel list in our preferred order 2399 if (merge_group_mask != 0) 2400 { 2401 // We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels(). 2402 const int LEADING_DRAW_CHANNELS = 2; 2403 g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized 2404 ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; 2405 ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> remaining_mask; // We need 132-bit of storage 2406 remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count); 2407 remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen); 2408 IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN); 2409 int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS); 2410 //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect; 2411 ImRect host_rect = table->HostClipRect; 2412 for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) 2413 { 2414 if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) 2415 { 2416 MergeGroup* merge_group = &merge_groups[merge_group_n]; 2417 ImRect merge_clip_rect = merge_group->ClipRect; 2418 2419 // Extend outer-most clip limits to match those of host, so draw calls can be merged even if 2420 // outer-most columns have some outer padding offsetting them from their parent ClipRect. 2421 // The principal cases this is dealing with are: 2422 // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge 2423 // - Columns can use padding and have left-most ClipRect.Min.x and right-most ClipRect.Max.x != from host ClipRect -> will extend and match host ClipRect -> will merge 2424 // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit 2425 // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect. 2426 if ((merge_group_n & 1) == 0 || !has_freeze_h) 2427 merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x); 2428 if ((merge_group_n & 2) == 0 || !has_freeze_v) 2429 merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y); 2430 if ((merge_group_n & 1) != 0) 2431 merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x); 2432 if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) 2433 merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y); 2434 #if 0 2435 GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f); 2436 GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); 2437 GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); 2438 #endif 2439 remaining_count -= merge_group->ChannelsCount; 2440 for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) 2441 remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; 2442 for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) 2443 { 2444 // Copy + overwrite new clip rect 2445 if (!merge_group->ChannelsMask.TestBit(n)) 2446 continue; 2447 merge_group->ChannelsMask.ClearBit(n); 2448 merge_channels_count--; 2449 2450 ImDrawChannel* channel = &splitter->_Channels[n]; 2451 IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); 2452 channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4(); 2453 memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); 2454 } 2455 } 2456 2457 // Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1) 2458 if (merge_group_n == 1 && has_freeze_v) 2459 memcpy(dst_tmp++, &splitter->_Channels[table->Bg2DrawChannelUnfrozen], sizeof(ImDrawChannel)); 2460 } 2461 2462 // Append unmergeable channels that we didn't reorder at the end of the list 2463 for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) 2464 { 2465 if (!remaining_mask.TestBit(n)) 2466 continue; 2467 ImDrawChannel* channel = &splitter->_Channels[n]; 2468 memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); 2469 remaining_count--; 2470 } 2471 IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); 2472 memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel)); 2473 } 2474 } 2475 2476 // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) 2477 void ImGui::TableDrawBorders(ImGuiTable* table) 2478 { 2479 ImGuiWindow* inner_window = table->InnerWindow; 2480 if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect)) 2481 return; 2482 2483 ImDrawList* inner_drawlist = inner_window->DrawList; 2484 table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); 2485 inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false); 2486 2487 // Draw inner border and resizing feedback 2488 const float border_size = TABLE_BORDER_SIZE; 2489 const float draw_y1 = table->InnerRect.Min.y; 2490 const float draw_y2_body = table->InnerRect.Max.y; 2491 const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight) : draw_y1; 2492 if (table->Flags & ImGuiTableFlags_BordersInnerV) 2493 { 2494 for (int order_n = 0; order_n < table->ColumnsCount; order_n++) 2495 { 2496 if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) 2497 continue; 2498 2499 const int column_n = table->DisplayOrderToIndex[order_n]; 2500 ImGuiTableColumn* column = &table->Columns[column_n]; 2501 const bool is_hovered = (table->HoveredColumnBorder == column_n); 2502 const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); 2503 const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; 2504 const bool is_frozen_separator = (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1); 2505 if (column->MaxX > table->InnerClipRect.Max.x && !is_resized) 2506 continue; 2507 2508 // Decide whether right-most column is visible 2509 if (column->NextEnabledColumn == -1 && !is_resizable) 2510 if ((table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame || (table->Flags & ImGuiTableFlags_NoHostExtendX)) 2511 continue; 2512 if (column->MaxX <= column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. 2513 continue; 2514 2515 // Draw in outer window so right-most column won't be clipped 2516 // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling. 2517 ImU32 col; 2518 float draw_y2; 2519 if (is_hovered || is_resized || is_frozen_separator) 2520 { 2521 draw_y2 = draw_y2_body; 2522 col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : table->BorderColorStrong; 2523 } 2524 else 2525 { 2526 draw_y2 = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body; 2527 col = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? table->BorderColorStrong : table->BorderColorLight; 2528 } 2529 2530 if (draw_y2 > draw_y1) 2531 inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); 2532 } 2533 } 2534 2535 // Draw outer border 2536 // FIXME: could use AddRect or explicit VLine/HLine helper? 2537 if (table->Flags & ImGuiTableFlags_BordersOuter) 2538 { 2539 // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call 2540 // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their 2541 // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part 2542 // of it in inner window, and the part that's over scrollbars in the outer window..) 2543 // Either solution currently won't allow us to use a larger border size: the border would clipped. 2544 const ImRect outer_border = table->OuterRect; 2545 const ImU32 outer_col = table->BorderColorStrong; 2546 if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) 2547 { 2548 inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); 2549 } 2550 else if (table->Flags & ImGuiTableFlags_BordersOuterV) 2551 { 2552 inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); 2553 inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); 2554 } 2555 else if (table->Flags & ImGuiTableFlags_BordersOuterH) 2556 { 2557 inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); 2558 inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); 2559 } 2560 } 2561 if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) 2562 { 2563 // Draw bottom-most row border 2564 const float border_y = table->RowPosY2; 2565 if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) 2566 inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); 2567 } 2568 2569 inner_drawlist->PopClipRect(); 2570 } 2571 2572 //------------------------------------------------------------------------- 2573 // [SECTION] Tables: Sorting 2574 //------------------------------------------------------------------------- 2575 // - TableGetSortSpecs() 2576 // - TableFixColumnSortDirection() [Internal] 2577 // - TableGetColumnNextSortDirection() [Internal] 2578 // - TableSetColumnSortDirection() [Internal] 2579 // - TableSortSpecsSanitize() [Internal] 2580 // - TableSortSpecsBuild() [Internal] 2581 //------------------------------------------------------------------------- 2582 2583 // Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) 2584 // You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since 2585 // last call, or the first time. 2586 // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! 2587 ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() 2588 { 2589 ImGuiContext& g = *GImGui; 2590 ImGuiTable* table = g.CurrentTable; 2591 IM_ASSERT(table != NULL); 2592 2593 if (!(table->Flags & ImGuiTableFlags_Sortable)) 2594 return NULL; 2595 2596 // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths. 2597 if (!table->IsLayoutLocked) 2598 TableUpdateLayout(table); 2599 2600 if (table->IsSortSpecsDirty) 2601 TableSortSpecsBuild(table); 2602 2603 return &table->SortSpecs; 2604 } 2605 2606 static inline ImGuiSortDirection TableGetColumnAvailSortDirection(ImGuiTableColumn* column, int n) 2607 { 2608 IM_ASSERT(n < column->SortDirectionsAvailCount); 2609 return (column->SortDirectionsAvailList >> (n << 1)) & 0x03; 2610 } 2611 2612 // Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending) 2613 void ImGui::TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column) 2614 { 2615 if (column->SortOrder == -1 || (column->SortDirectionsAvailMask & (1 << column->SortDirection)) != 0) 2616 return; 2617 column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0); 2618 table->IsSortSpecsDirty = true; 2619 } 2620 2621 // Calculate next sort direction that would be set after clicking the column 2622 // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. 2623 // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. 2624 IM_STATIC_ASSERT(ImGuiSortDirection_None == 0 && ImGuiSortDirection_Ascending == 1 && ImGuiSortDirection_Descending == 2); 2625 ImGuiSortDirection ImGui::TableGetColumnNextSortDirection(ImGuiTableColumn* column) 2626 { 2627 IM_ASSERT(column->SortDirectionsAvailCount > 0); 2628 if (column->SortOrder == -1) 2629 return TableGetColumnAvailSortDirection(column, 0); 2630 for (int n = 0; n < 3; n++) 2631 if (column->SortDirection == TableGetColumnAvailSortDirection(column, n)) 2632 return TableGetColumnAvailSortDirection(column, (n + 1) % column->SortDirectionsAvailCount); 2633 IM_ASSERT(0); 2634 return ImGuiSortDirection_None; 2635 } 2636 2637 // Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert 2638 // the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. 2639 void ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs) 2640 { 2641 ImGuiContext& g = *GImGui; 2642 ImGuiTable* table = g.CurrentTable; 2643 2644 if (!(table->Flags & ImGuiTableFlags_SortMulti)) 2645 append_to_sort_specs = false; 2646 if (!(table->Flags & ImGuiTableFlags_SortTristate)) 2647 IM_ASSERT(sort_direction != ImGuiSortDirection_None); 2648 2649 ImGuiTableColumnIdx sort_order_max = 0; 2650 if (append_to_sort_specs) 2651 for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) 2652 sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder); 2653 2654 ImGuiTableColumn* column = &table->Columns[column_n]; 2655 column->SortDirection = (ImU8)sort_direction; 2656 if (column->SortDirection == ImGuiSortDirection_None) 2657 column->SortOrder = -1; 2658 else if (column->SortOrder == -1 || !append_to_sort_specs) 2659 column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0; 2660 2661 for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) 2662 { 2663 ImGuiTableColumn* other_column = &table->Columns[other_column_n]; 2664 if (other_column != column && !append_to_sort_specs) 2665 other_column->SortOrder = -1; 2666 TableFixColumnSortDirection(table, other_column); 2667 } 2668 table->IsSettingsDirty = true; 2669 table->IsSortSpecsDirty = true; 2670 } 2671 2672 void ImGui::TableSortSpecsSanitize(ImGuiTable* table) 2673 { 2674 IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); 2675 2676 // Clear SortOrder from hidden column and verify that there's no gap or duplicate. 2677 int sort_order_count = 0; 2678 ImU64 sort_order_mask = 0x00; 2679 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2680 { 2681 ImGuiTableColumn* column = &table->Columns[column_n]; 2682 if (column->SortOrder != -1 && !column->IsEnabled) 2683 column->SortOrder = -1; 2684 if (column->SortOrder == -1) 2685 continue; 2686 sort_order_count++; 2687 sort_order_mask |= ((ImU64)1 << column->SortOrder); 2688 IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8); 2689 } 2690 2691 const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1); 2692 const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_SortMulti); 2693 if (need_fix_linearize || need_fix_single_sort_order) 2694 { 2695 ImU64 fixed_mask = 0x00; 2696 for (int sort_n = 0; sort_n < sort_order_count; sort_n++) 2697 { 2698 // Fix: Rewrite sort order fields if needed so they have no gap or duplicate. 2699 // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1) 2700 int column_with_smallest_sort_order = -1; 2701 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2702 if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1) 2703 if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder) 2704 column_with_smallest_sort_order = column_n; 2705 IM_ASSERT(column_with_smallest_sort_order != -1); 2706 fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order); 2707 table->Columns[column_with_smallest_sort_order].SortOrder = (ImGuiTableColumnIdx)sort_n; 2708 2709 // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. 2710 if (need_fix_single_sort_order) 2711 { 2712 sort_order_count = 1; 2713 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2714 if (column_n != column_with_smallest_sort_order) 2715 table->Columns[column_n].SortOrder = -1; 2716 break; 2717 } 2718 } 2719 } 2720 2721 // Fallback default sort order (if no column had the ImGuiTableColumnFlags_DefaultSort flag) 2722 if (sort_order_count == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) 2723 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2724 { 2725 ImGuiTableColumn* column = &table->Columns[column_n]; 2726 if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort)) 2727 { 2728 sort_order_count = 1; 2729 column->SortOrder = 0; 2730 column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0); 2731 break; 2732 } 2733 } 2734 2735 table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count; 2736 } 2737 2738 void ImGui::TableSortSpecsBuild(ImGuiTable* table) 2739 { 2740 IM_ASSERT(table->IsSortSpecsDirty); 2741 TableSortSpecsSanitize(table); 2742 2743 // Write output 2744 ImGuiTableTempData* temp_data = table->TempData; 2745 temp_data->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount); 2746 ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &temp_data->SortSpecsSingle : temp_data->SortSpecsMulti.Data; 2747 if (sort_specs != NULL) 2748 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 2749 { 2750 ImGuiTableColumn* column = &table->Columns[column_n]; 2751 if (column->SortOrder == -1) 2752 continue; 2753 IM_ASSERT(column->SortOrder < table->SortSpecsCount); 2754 ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder]; 2755 sort_spec->ColumnUserID = column->UserID; 2756 sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; 2757 sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; 2758 sort_spec->SortDirection = column->SortDirection; 2759 } 2760 table->SortSpecs.Specs = sort_specs; 2761 table->SortSpecs.SpecsCount = table->SortSpecsCount; 2762 table->SortSpecs.SpecsDirty = true; // Mark as dirty for user 2763 table->IsSortSpecsDirty = false; // Mark as not dirty for us 2764 } 2765 2766 //------------------------------------------------------------------------- 2767 // [SECTION] Tables: Headers 2768 //------------------------------------------------------------------------- 2769 // - TableGetHeaderRowHeight() [Internal] 2770 // - TableHeadersRow() 2771 // - TableHeader() 2772 //------------------------------------------------------------------------- 2773 2774 float ImGui::TableGetHeaderRowHeight() 2775 { 2776 // Caring for a minor edge case: 2777 // Calculate row height, for the unlikely case that some labels may be taller than others. 2778 // If we didn't do that, uneven header height would highlight but smaller one before the tallest wouldn't catch input for all height. 2779 // In your custom header row you may omit this all together and just call TableNextRow() without a height... 2780 float row_height = GetTextLineHeight(); 2781 int columns_count = TableGetColumnCount(); 2782 for (int column_n = 0; column_n < columns_count; column_n++) 2783 if (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_IsEnabled) 2784 row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); 2785 row_height += GetStyle().CellPadding.y * 2.0f; 2786 return row_height; 2787 } 2788 2789 // [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). 2790 // The intent is that advanced users willing to create customized headers would not need to use this helper 2791 // and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets. 2792 // See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this. 2793 // This code is constructed to not make much use of internal functions, as it is intended to be a template to copy. 2794 // FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public. 2795 void ImGui::TableHeadersRow() 2796 { 2797 ImGuiContext& g = *GImGui; 2798 ImGuiTable* table = g.CurrentTable; 2799 IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); 2800 2801 // Layout if not already done (this is automatically done by TableNextRow, we do it here solely to facilitate stepping in debugger as it is frequent to step in TableUpdateLayout) 2802 if (!table->IsLayoutLocked) 2803 TableUpdateLayout(table); 2804 2805 // Open row 2806 const float row_y1 = GetCursorScreenPos().y; 2807 const float row_height = TableGetHeaderRowHeight(); 2808 TableNextRow(ImGuiTableRowFlags_Headers, row_height); 2809 if (table->HostSkipItems) // Merely an optimization, you may skip in your own code. 2810 return; 2811 2812 const int columns_count = TableGetColumnCount(); 2813 for (int column_n = 0; column_n < columns_count; column_n++) 2814 { 2815 if (!TableSetColumnIndex(column_n)) 2816 continue; 2817 2818 // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) 2819 // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide 2820 // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. 2821 const char* name = TableGetColumnName(column_n); 2822 PushID(table->InstanceCurrent * table->ColumnsCount + column_n); 2823 TableHeader(name); 2824 PopID(); 2825 } 2826 2827 // Allow opening popup from the right-most section after the last column. 2828 ImVec2 mouse_pos = ImGui::GetMousePos(); 2829 if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) 2830 if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height) 2831 TableOpenContextMenu(-1); // Will open a non-column-specific popup. 2832 } 2833 2834 // Emit a column header (text + optional sort order) 2835 // We cpu-clip text here so that all columns headers can be merged into a same draw call. 2836 // Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader() 2837 void ImGui::TableHeader(const char* label) 2838 { 2839 ImGuiContext& g = *GImGui; 2840 ImGuiWindow* window = g.CurrentWindow; 2841 if (window->SkipItems) 2842 return; 2843 2844 ImGuiTable* table = g.CurrentTable; 2845 IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); 2846 IM_ASSERT(table->CurrentColumn != -1); 2847 const int column_n = table->CurrentColumn; 2848 ImGuiTableColumn* column = &table->Columns[column_n]; 2849 2850 // Label 2851 if (label == NULL) 2852 label = ""; 2853 const char* label_end = FindRenderedTextEnd(label); 2854 ImVec2 label_size = CalcTextSize(label, label_end, true); 2855 ImVec2 label_pos = window->DC.CursorPos; 2856 2857 // If we already got a row height, there's use that. 2858 // FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect? 2859 ImRect cell_r = TableGetCellBgRect(table, column_n); 2860 float label_height = ImMax(label_size.y, table->RowMinHeight - table->CellPaddingY * 2.0f); 2861 2862 // Calculate ideal size for sort order arrow 2863 float w_arrow = 0.0f; 2864 float w_sort_text = 0.0f; 2865 char sort_order_suf[4] = ""; 2866 const float ARROW_SCALE = 0.65f; 2867 if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) 2868 { 2869 w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x); 2870 if (column->SortOrder > 0) 2871 { 2872 ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1); 2873 w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; 2874 } 2875 } 2876 2877 // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. 2878 float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; 2879 column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, column->WorkMaxX); 2880 column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x); 2881 2882 // Keep header highlighted when context menu is open. 2883 const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); 2884 ImGuiID id = window->GetID(label); 2885 ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f)); 2886 ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal 2887 if (!ItemAdd(bb, id)) 2888 return; 2889 2890 //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] 2891 //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] 2892 2893 // Using AllowItemOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items. 2894 bool hovered, held; 2895 bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_AllowItemOverlap); 2896 if (g.ActiveId != id) 2897 SetItemAllowOverlap(); 2898 if (held || hovered || selected) 2899 { 2900 const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); 2901 //RenderFrame(bb.Min, bb.Max, col, false, 0.0f); 2902 TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn); 2903 RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); 2904 } 2905 else 2906 { 2907 // Submit single cell bg color in the case we didn't submit a full header row 2908 if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) 2909 TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); 2910 } 2911 if (held) 2912 table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; 2913 window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; 2914 2915 // Drag and drop to re-order columns. 2916 // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. 2917 if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) 2918 { 2919 // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x 2920 table->ReorderColumn = (ImGuiTableColumnIdx)column_n; 2921 table->InstanceInteracted = table->InstanceCurrent; 2922 2923 // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. 2924 if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) 2925 if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL) 2926 if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) 2927 if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) 2928 table->ReorderColumnDir = -1; 2929 if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) 2930 if (ImGuiTableColumn* next_column = (column->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL) 2931 if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) 2932 if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) 2933 table->ReorderColumnDir = +1; 2934 } 2935 2936 // Sort order arrow 2937 const float ellipsis_max = cell_r.Max.x - w_arrow - w_sort_text; 2938 if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) 2939 { 2940 if (column->SortOrder != -1) 2941 { 2942 float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text); 2943 float y = label_pos.y; 2944 if (column->SortOrder > 0) 2945 { 2946 PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f)); 2947 RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf); 2948 PopStyleColor(); 2949 x += w_sort_text; 2950 } 2951 RenderArrow(window->DrawList, ImVec2(x, y), GetColorU32(ImGuiCol_Text), column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE); 2952 } 2953 2954 // Handle clicking on column header to adjust Sort Order 2955 if (pressed && table->ReorderColumn != column_n) 2956 { 2957 ImGuiSortDirection sort_direction = TableGetColumnNextSortDirection(column); 2958 TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift); 2959 } 2960 } 2961 2962 // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will 2963 // be merged into a single draw call. 2964 //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); 2965 RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); 2966 2967 const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); 2968 if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay) 2969 SetTooltip("%.*s", (int)(label_end - label), label); 2970 2971 // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden 2972 if (IsMouseReleased(1) && IsItemHovered()) 2973 TableOpenContextMenu(column_n); 2974 } 2975 2976 //------------------------------------------------------------------------- 2977 // [SECTION] Tables: Context Menu 2978 //------------------------------------------------------------------------- 2979 // - TableOpenContextMenu() [Internal] 2980 // - TableDrawContextMenu() [Internal] 2981 //------------------------------------------------------------------------- 2982 2983 // Use -1 to open menu not specific to a given column. 2984 void ImGui::TableOpenContextMenu(int column_n) 2985 { 2986 ImGuiContext& g = *GImGui; 2987 ImGuiTable* table = g.CurrentTable; 2988 if (column_n == -1 && table->CurrentColumn != -1) // When called within a column automatically use this one (for consistency) 2989 column_n = table->CurrentColumn; 2990 if (column_n == table->ColumnsCount) // To facilitate using with TableGetHoveredColumn() 2991 column_n = -1; 2992 IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount); 2993 if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) 2994 { 2995 table->IsContextPopupOpen = true; 2996 table->ContextPopupColumn = (ImGuiTableColumnIdx)column_n; 2997 table->InstanceInteracted = table->InstanceCurrent; 2998 const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); 2999 OpenPopupEx(context_menu_id, ImGuiPopupFlags_None); 3000 } 3001 } 3002 3003 // Output context menu into current window (generally a popup) 3004 // FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? 3005 void ImGui::TableDrawContextMenu(ImGuiTable* table) 3006 { 3007 ImGuiContext& g = *GImGui; 3008 ImGuiWindow* window = g.CurrentWindow; 3009 if (window->SkipItems) 3010 return; 3011 3012 bool want_separator = false; 3013 const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; 3014 ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL; 3015 3016 // Sizing 3017 if (table->Flags & ImGuiTableFlags_Resizable) 3018 { 3019 if (column != NULL) 3020 { 3021 const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled; 3022 if (MenuItem("Size column to fit###SizeOne", NULL, false, can_resize)) 3023 TableSetColumnWidthAutoSingle(table, column_n); 3024 } 3025 3026 const char* size_all_desc; 3027 if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame) 3028 size_all_desc = "Size all columns to fit###SizeAll"; // All fixed 3029 else 3030 size_all_desc = "Size all columns to default###SizeAll"; // All stretch or mixed 3031 if (MenuItem(size_all_desc, NULL)) 3032 TableSetColumnWidthAutoAll(table); 3033 want_separator = true; 3034 } 3035 3036 // Ordering 3037 if (table->Flags & ImGuiTableFlags_Reorderable) 3038 { 3039 if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder)) 3040 table->IsResetDisplayOrderRequest = true; 3041 want_separator = true; 3042 } 3043 3044 // Reset all (should work but seems unnecessary/noisy to expose?) 3045 //if (MenuItem("Reset all")) 3046 // table->IsResetAllRequest = true; 3047 3048 // Sorting 3049 // (modify TableOpenContextMenu() to add _Sortable flag if enabling this) 3050 #if 0 3051 if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0) 3052 { 3053 if (want_separator) 3054 Separator(); 3055 want_separator = true; 3056 3057 bool append_to_sort_specs = g.IO.KeyShift; 3058 if (MenuItem("Sort in Ascending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0)) 3059 TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs); 3060 if (MenuItem("Sort in Descending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0)) 3061 TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs); 3062 } 3063 #endif 3064 3065 // Hiding / Visibility 3066 if (table->Flags & ImGuiTableFlags_Hideable) 3067 { 3068 if (want_separator) 3069 Separator(); 3070 want_separator = true; 3071 3072 PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); 3073 for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) 3074 { 3075 ImGuiTableColumn* other_column = &table->Columns[other_column_n]; 3076 const char* name = TableGetColumnName(table, other_column_n); 3077 if (name == NULL || name[0] == 0) 3078 name = "<Unknown>"; 3079 3080 // Make sure we can't hide the last active column 3081 bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; 3082 if (other_column->IsEnabled && table->ColumnsEnabledCount <= 1) 3083 menu_item_active = false; 3084 if (MenuItem(name, NULL, other_column->IsEnabled, menu_item_active)) 3085 other_column->IsEnabledNextFrame = !other_column->IsEnabled; 3086 } 3087 PopItemFlag(); 3088 } 3089 } 3090 3091 //------------------------------------------------------------------------- 3092 // [SECTION] Tables: Settings (.ini data) 3093 //------------------------------------------------------------------------- 3094 // FIXME: The binding/finding/creating flow are too confusing. 3095 //------------------------------------------------------------------------- 3096 // - TableSettingsInit() [Internal] 3097 // - TableSettingsCalcChunkSize() [Internal] 3098 // - TableSettingsCreate() [Internal] 3099 // - TableSettingsFindByID() [Internal] 3100 // - TableGetBoundSettings() [Internal] 3101 // - TableResetSettings() 3102 // - TableSaveSettings() [Internal] 3103 // - TableLoadSettings() [Internal] 3104 // - TableSettingsHandler_ClearAll() [Internal] 3105 // - TableSettingsHandler_ApplyAll() [Internal] 3106 // - TableSettingsHandler_ReadOpen() [Internal] 3107 // - TableSettingsHandler_ReadLine() [Internal] 3108 // - TableSettingsHandler_WriteAll() [Internal] 3109 // - TableSettingsInstallHandler() [Internal] 3110 //------------------------------------------------------------------------- 3111 // [Init] 1: TableSettingsHandler_ReadXXXX() Load and parse .ini file into TableSettings. 3112 // [Main] 2: TableLoadSettings() When table is created, bind Table to TableSettings, serialize TableSettings data into Table. 3113 // [Main] 3: TableSaveSettings() When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty. 3114 // [Main] 4: TableSettingsHandler_WriteAll() When .ini file is dirty (which can come from other source), save TableSettings into .ini file. 3115 //------------------------------------------------------------------------- 3116 3117 // Clear and initialize empty settings instance 3118 static void TableSettingsInit(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max) 3119 { 3120 IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); 3121 ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings(); 3122 for (int n = 0; n < columns_count_max; n++, settings_column++) 3123 IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); 3124 settings->ID = id; 3125 settings->ColumnsCount = (ImGuiTableColumnIdx)columns_count; 3126 settings->ColumnsCountMax = (ImGuiTableColumnIdx)columns_count_max; 3127 settings->WantApply = true; 3128 } 3129 3130 static size_t TableSettingsCalcChunkSize(int columns_count) 3131 { 3132 return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings); 3133 } 3134 3135 ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) 3136 { 3137 ImGuiContext& g = *GImGui; 3138 ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count)); 3139 TableSettingsInit(settings, id, columns_count, columns_count); 3140 return settings; 3141 } 3142 3143 // Find existing settings 3144 ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) 3145 { 3146 // FIXME-OPT: Might want to store a lookup map for this? 3147 ImGuiContext& g = *GImGui; 3148 for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) 3149 if (settings->ID == id) 3150 return settings; 3151 return NULL; 3152 } 3153 3154 // Get settings for a given table, NULL if none 3155 ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) 3156 { 3157 if (table->SettingsOffset != -1) 3158 { 3159 ImGuiContext& g = *GImGui; 3160 ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); 3161 IM_ASSERT(settings->ID == table->ID); 3162 if (settings->ColumnsCountMax >= table->ColumnsCount) 3163 return settings; // OK 3164 settings->ID = 0; // Invalidate storage, we won't fit because of a count change 3165 } 3166 return NULL; 3167 } 3168 3169 // Restore initial state of table (with or without saved settings) 3170 void ImGui::TableResetSettings(ImGuiTable* table) 3171 { 3172 table->IsInitializing = table->IsSettingsDirty = true; 3173 table->IsResetAllRequest = false; 3174 table->IsSettingsRequestLoad = false; // Don't reload from ini 3175 table->SettingsLoadedFlags = ImGuiTableFlags_None; // Mark as nothing loaded so our initialized data becomes authoritative 3176 } 3177 3178 void ImGui::TableSaveSettings(ImGuiTable* table) 3179 { 3180 table->IsSettingsDirty = false; 3181 if (table->Flags & ImGuiTableFlags_NoSavedSettings) 3182 return; 3183 3184 // Bind or create settings data 3185 ImGuiContext& g = *GImGui; 3186 ImGuiTableSettings* settings = TableGetBoundSettings(table); 3187 if (settings == NULL) 3188 { 3189 settings = TableSettingsCreate(table->ID, table->ColumnsCount); 3190 table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); 3191 } 3192 settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount; 3193 3194 // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings 3195 IM_ASSERT(settings->ID == table->ID); 3196 IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount); 3197 ImGuiTableColumn* column = table->Columns.Data; 3198 ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); 3199 3200 bool save_ref_scale = false; 3201 settings->SaveFlags = ImGuiTableFlags_None; 3202 for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) 3203 { 3204 const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; 3205 column_settings->WidthOrWeight = width_or_weight; 3206 column_settings->Index = (ImGuiTableColumnIdx)n; 3207 column_settings->DisplayOrder = column->DisplayOrder; 3208 column_settings->SortOrder = column->SortOrder; 3209 column_settings->SortDirection = column->SortDirection; 3210 column_settings->IsEnabled = column->IsEnabled; 3211 column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; 3212 if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) 3213 save_ref_scale = true; 3214 3215 // We skip saving some data in the .ini file when they are unnecessary to restore our state. 3216 // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f. 3217 // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. 3218 if (width_or_weight != column->InitStretchWeightOrWidth) 3219 settings->SaveFlags |= ImGuiTableFlags_Resizable; 3220 if (column->DisplayOrder != n) 3221 settings->SaveFlags |= ImGuiTableFlags_Reorderable; 3222 if (column->SortOrder != -1) 3223 settings->SaveFlags |= ImGuiTableFlags_Sortable; 3224 if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) 3225 settings->SaveFlags |= ImGuiTableFlags_Hideable; 3226 } 3227 settings->SaveFlags &= table->Flags; 3228 settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; 3229 3230 MarkIniSettingsDirty(); 3231 } 3232 3233 void ImGui::TableLoadSettings(ImGuiTable* table) 3234 { 3235 ImGuiContext& g = *GImGui; 3236 table->IsSettingsRequestLoad = false; 3237 if (table->Flags & ImGuiTableFlags_NoSavedSettings) 3238 return; 3239 3240 // Bind settings 3241 ImGuiTableSettings* settings; 3242 if (table->SettingsOffset == -1) 3243 { 3244 settings = TableSettingsFindByID(table->ID); 3245 if (settings == NULL) 3246 return; 3247 if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return... 3248 table->IsSettingsDirty = true; 3249 table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); 3250 } 3251 else 3252 { 3253 settings = TableGetBoundSettings(table); 3254 } 3255 3256 table->SettingsLoadedFlags = settings->SaveFlags; 3257 table->RefScale = settings->RefScale; 3258 3259 // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn 3260 ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); 3261 ImU64 display_order_mask = 0; 3262 for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) 3263 { 3264 int column_n = column_settings->Index; 3265 if (column_n < 0 || column_n >= table->ColumnsCount) 3266 continue; 3267 3268 ImGuiTableColumn* column = &table->Columns[column_n]; 3269 if (settings->SaveFlags & ImGuiTableFlags_Resizable) 3270 { 3271 if (column_settings->IsStretch) 3272 column->StretchWeight = column_settings->WidthOrWeight; 3273 else 3274 column->WidthRequest = column_settings->WidthOrWeight; 3275 column->AutoFitQueue = 0x00; 3276 } 3277 if (settings->SaveFlags & ImGuiTableFlags_Reorderable) 3278 column->DisplayOrder = column_settings->DisplayOrder; 3279 else 3280 column->DisplayOrder = (ImGuiTableColumnIdx)column_n; 3281 display_order_mask |= (ImU64)1 << column->DisplayOrder; 3282 column->IsEnabled = column->IsEnabledNextFrame = column_settings->IsEnabled; 3283 column->SortOrder = column_settings->SortOrder; 3284 column->SortDirection = column_settings->SortDirection; 3285 } 3286 3287 // Validate and fix invalid display order data 3288 const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1; 3289 if (display_order_mask != expected_display_order_mask) 3290 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 3291 table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n; 3292 3293 // Rebuild index 3294 for (int column_n = 0; column_n < table->ColumnsCount; column_n++) 3295 table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; 3296 } 3297 3298 static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) 3299 { 3300 ImGuiContext& g = *ctx; 3301 for (int i = 0; i != g.Tables.GetSize(); i++) 3302 g.Tables.GetByIndex(i)->SettingsOffset = -1; 3303 g.SettingsTables.clear(); 3304 } 3305 3306 // Apply to existing windows (if any) 3307 static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) 3308 { 3309 ImGuiContext& g = *ctx; 3310 for (int i = 0; i != g.Tables.GetSize(); i++) 3311 { 3312 ImGuiTable* table = g.Tables.GetByIndex(i); 3313 table->IsSettingsRequestLoad = true; 3314 table->SettingsOffset = -1; 3315 } 3316 } 3317 3318 static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) 3319 { 3320 ImGuiID id = 0; 3321 int columns_count = 0; 3322 if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) 3323 return NULL; 3324 3325 if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id)) 3326 { 3327 if (settings->ColumnsCountMax >= columns_count) 3328 { 3329 TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle 3330 return settings; 3331 } 3332 settings->ID = 0; // Invalidate storage, we won't fit because of a count change 3333 } 3334 return ImGui::TableSettingsCreate(id, columns_count); 3335 } 3336 3337 static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) 3338 { 3339 // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" 3340 ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; 3341 float f = 0.0f; 3342 int column_n = 0, r = 0, n = 0; 3343 3344 if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } 3345 3346 if (sscanf(line, "Column %d%n", &column_n, &r) == 1) 3347 { 3348 if (column_n < 0 || column_n >= settings->ColumnsCount) 3349 return; 3350 line = ImStrSkipBlank(line + r); 3351 char c = 0; 3352 ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; 3353 column->Index = (ImGuiTableColumnIdx)column_n; 3354 if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } 3355 if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } 3356 if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } 3357 if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } 3358 if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } 3359 if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } 3360 } 3361 } 3362 3363 static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) 3364 { 3365 ImGuiContext& g = *ctx; 3366 for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) 3367 { 3368 if (settings->ID == 0) // Skip ditched settings 3369 continue; 3370 3371 // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped 3372 // (e.g. Order was unchanged) 3373 const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; 3374 const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; 3375 const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; 3376 const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; 3377 if (!save_size && !save_visible && !save_order && !save_sort) 3378 continue; 3379 3380 buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve 3381 buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); 3382 if (settings->RefScale != 0.0f) 3383 buf->appendf("RefScale=%g\n", settings->RefScale); 3384 ImGuiTableColumnSettings* column = settings->GetColumnSettings(); 3385 for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) 3386 { 3387 // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" 3388 bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1); 3389 if (!save_column) 3390 continue; 3391 buf->appendf("Column %-2d", column_n); 3392 if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); 3393 if (save_size && column->IsStretch) buf->appendf(" Weight=%.4f", column->WidthOrWeight); 3394 if (save_size && !column->IsStretch) buf->appendf(" Width=%d", (int)column->WidthOrWeight); 3395 if (save_visible) buf->appendf(" Visible=%d", column->IsEnabled); 3396 if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); 3397 if (save_sort && column->SortOrder != -1) buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); 3398 buf->append("\n"); 3399 } 3400 buf->append("\n"); 3401 } 3402 } 3403 3404 void ImGui::TableSettingsInstallHandler(ImGuiContext* context) 3405 { 3406 ImGuiContext& g = *context; 3407 ImGuiSettingsHandler ini_handler; 3408 ini_handler.TypeName = "Table"; 3409 ini_handler.TypeHash = ImHashStr("Table"); 3410 ini_handler.ClearAllFn = TableSettingsHandler_ClearAll; 3411 ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; 3412 ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; 3413 ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll; 3414 ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; 3415 g.SettingsHandlers.push_back(ini_handler); 3416 } 3417 3418 //------------------------------------------------------------------------- 3419 // [SECTION] Tables: Garbage Collection 3420 //------------------------------------------------------------------------- 3421 // - TableRemove() [Internal] 3422 // - TableGcCompactTransientBuffers() [Internal] 3423 // - TableGcCompactSettings() [Internal] 3424 //------------------------------------------------------------------------- 3425 3426 // Remove Table (currently only used by TestEngine) 3427 void ImGui::TableRemove(ImGuiTable* table) 3428 { 3429 //IMGUI_DEBUG_LOG("TableRemove() id=0x%08X\n", table->ID); 3430 ImGuiContext& g = *GImGui; 3431 int table_idx = g.Tables.GetIndex(table); 3432 //memset(table->RawData.Data, 0, table->RawData.size_in_bytes()); 3433 //memset(table, 0, sizeof(ImGuiTable)); 3434 g.Tables.Remove(table->ID, table); 3435 g.TablesLastTimeActive[table_idx] = -1.0f; 3436 } 3437 3438 // Free up/compact internal Table buffers for when it gets unused 3439 void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) 3440 { 3441 //IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID); 3442 ImGuiContext& g = *GImGui; 3443 IM_ASSERT(table->MemoryCompacted == false); 3444 table->SortSpecs.Specs = NULL; 3445 table->IsSortSpecsDirty = true; 3446 table->ColumnsNames.clear(); 3447 table->MemoryCompacted = true; 3448 for (int n = 0; n < table->ColumnsCount; n++) 3449 table->Columns[n].NameOffset = -1; 3450 g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f; 3451 } 3452 3453 void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data) 3454 { 3455 temp_data->DrawSplitter.ClearFreeMemory(); 3456 temp_data->SortSpecsMulti.clear(); 3457 temp_data->LastTimeActive = -1.0f; 3458 } 3459 3460 // Compact and remove unused settings data (currently only used by TestEngine) 3461 void ImGui::TableGcCompactSettings() 3462 { 3463 ImGuiContext& g = *GImGui; 3464 int required_memory = 0; 3465 for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) 3466 if (settings->ID != 0) 3467 required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount); 3468 if (required_memory == g.SettingsTables.Buf.Size) 3469 return; 3470 ImChunkStream<ImGuiTableSettings> new_chunk_stream; 3471 new_chunk_stream.Buf.reserve(required_memory); 3472 for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) 3473 if (settings->ID != 0) 3474 memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount)); 3475 g.SettingsTables.swap(new_chunk_stream); 3476 } 3477 3478 3479 //------------------------------------------------------------------------- 3480 // [SECTION] Tables: Debugging 3481 //------------------------------------------------------------------------- 3482 // - DebugNodeTable() [Internal] 3483 //------------------------------------------------------------------------- 3484 3485 #ifndef IMGUI_DISABLE_METRICS_WINDOW 3486 3487 static const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_policy) 3488 { 3489 sizing_policy &= ImGuiTableFlags_SizingMask_; 3490 if (sizing_policy == ImGuiTableFlags_SizingFixedFit) { return "FixedFit"; } 3491 if (sizing_policy == ImGuiTableFlags_SizingFixedSame) { return "FixedSame"; } 3492 if (sizing_policy == ImGuiTableFlags_SizingStretchProp) { return "StretchProp"; } 3493 if (sizing_policy == ImGuiTableFlags_SizingStretchSame) { return "StretchSame"; } 3494 return "N/A"; 3495 } 3496 3497 void ImGui::DebugNodeTable(ImGuiTable* table) 3498 { 3499 char buf[512]; 3500 char* p = buf; 3501 const char* buf_end = buf + IM_ARRAYSIZE(buf); 3502 const bool is_active = (table->LastFrameActive >= ImGui::GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. 3503 ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*"); 3504 if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } 3505 bool open = TreeNode(table, "%s", buf); 3506 if (!is_active) { PopStyleColor(); } 3507 if (IsItemHovered()) 3508 GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); 3509 if (IsItemVisible() && table->HoveredColumnBody != -1) 3510 GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255)); 3511 if (!open) 3512 return; 3513 bool clear_settings = SmallButton("Clear settings"); 3514 BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f) Sizing: '%s'", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight(), DebugNodeTableGetSizingPolicyDesc(table->Flags)); 3515 BulletText("ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsGivenWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); 3516 BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX); 3517 BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); 3518 BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); 3519 //BulletText("BgDrawChannels: %d/%d", 0, table->BgDrawChannelUnfrozen); 3520 float sum_weights = 0.0f; 3521 for (int n = 0; n < table->ColumnsCount; n++) 3522 if (table->Columns[n].Flags & ImGuiTableColumnFlags_WidthStretch) 3523 sum_weights += table->Columns[n].StretchWeight; 3524 for (int n = 0; n < table->ColumnsCount; n++) 3525 { 3526 ImGuiTableColumn* column = &table->Columns[n]; 3527 const char* name = TableGetColumnName(table, n); 3528 ImFormatString(buf, IM_ARRAYSIZE(buf), 3529 "Column %d order %d '%s': offset %+.2f to %+.2f%s\n" 3530 "Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n" 3531 "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n" 3532 "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n" 3533 "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n" 3534 "Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s..", 3535 n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, (n < table->FreezeColumnsRequest) ? " (Frozen)" : "", 3536 column->IsEnabled, column->IsVisibleX, column->IsVisibleY, column->IsRequestOutput, column->IsSkipItems, column->DrawChannelFrozen, column->DrawChannelUnfrozen, 3537 column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight, column->StretchWeight > 0.0f ? (column->StretchWeight / sum_weights) * 100.0f : 0.0f, 3538 column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x, 3539 column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX, 3540 column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" : "", column->UserID, column->Flags, 3541 (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", 3542 (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", 3543 (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); 3544 Bullet(); 3545 Selectable(buf); 3546 if (IsItemHovered()) 3547 { 3548 ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y); 3549 GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255)); 3550 } 3551 } 3552 if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) 3553 DebugNodeTableSettings(settings); 3554 if (clear_settings) 3555 table->IsResetAllRequest = true; 3556 TreePop(); 3557 } 3558 3559 void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) 3560 { 3561 if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount)) 3562 return; 3563 BulletText("SaveFlags: 0x%08X", settings->SaveFlags); 3564 BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); 3565 for (int n = 0; n < settings->ColumnsCount; n++) 3566 { 3567 ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; 3568 ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; 3569 BulletText("Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X", 3570 n, column_settings->DisplayOrder, column_settings->SortOrder, 3571 (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", 3572 column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->UserID); 3573 } 3574 TreePop(); 3575 } 3576 3577 #else // #ifndef IMGUI_DISABLE_METRICS_WINDOW 3578 3579 void ImGui::DebugNodeTable(ImGuiTable*) {} 3580 void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} 3581 3582 #endif 3583 3584 3585 //------------------------------------------------------------------------- 3586 // [SECTION] Columns, BeginColumns, EndColumns, etc. 3587 // (This is a legacy API, prefer using BeginTable/EndTable!) 3588 //------------------------------------------------------------------------- 3589 // FIXME: sizing is lossy when columns width is very small (default width may turn negative etc.) 3590 //------------------------------------------------------------------------- 3591 // - SetWindowClipRectBeforeSetChannel() [Internal] 3592 // - GetColumnIndex() 3593 // - GetColumnsCount() 3594 // - GetColumnOffset() 3595 // - GetColumnWidth() 3596 // - SetColumnOffset() 3597 // - SetColumnWidth() 3598 // - PushColumnClipRect() [Internal] 3599 // - PushColumnsBackground() [Internal] 3600 // - PopColumnsBackground() [Internal] 3601 // - FindOrCreateColumns() [Internal] 3602 // - GetColumnsID() [Internal] 3603 // - BeginColumns() 3604 // - NextColumn() 3605 // - EndColumns() 3606 // - Columns() 3607 //------------------------------------------------------------------------- 3608 3609 // [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences, 3610 // they would meddle many times with the underlying ImDrawCmd. 3611 // Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let 3612 // the subsequent single call to SetCurrentChannel() does it things once. 3613 void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect) 3614 { 3615 ImVec4 clip_rect_vec4 = clip_rect.ToVec4(); 3616 window->ClipRect = clip_rect; 3617 window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4; 3618 window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4; 3619 } 3620 3621 int ImGui::GetColumnIndex() 3622 { 3623 ImGuiWindow* window = GetCurrentWindowRead(); 3624 return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0; 3625 } 3626 3627 int ImGui::GetColumnsCount() 3628 { 3629 ImGuiWindow* window = GetCurrentWindowRead(); 3630 return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1; 3631 } 3632 3633 float ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm) 3634 { 3635 return offset_norm * (columns->OffMaxX - columns->OffMinX); 3636 } 3637 3638 float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset) 3639 { 3640 return offset / (columns->OffMaxX - columns->OffMinX); 3641 } 3642 3643 static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f; 3644 3645 static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) 3646 { 3647 // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing 3648 // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. 3649 ImGuiContext& g = *GImGui; 3650 ImGuiWindow* window = g.CurrentWindow; 3651 IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. 3652 IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); 3653 3654 float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x; 3655 x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); 3656 if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths)) 3657 x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); 3658 3659 return x; 3660 } 3661 3662 float ImGui::GetColumnOffset(int column_index) 3663 { 3664 ImGuiWindow* window = GetCurrentWindowRead(); 3665 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3666 if (columns == NULL) 3667 return 0.0f; 3668 3669 if (column_index < 0) 3670 column_index = columns->Current; 3671 IM_ASSERT(column_index < columns->Columns.Size); 3672 3673 const float t = columns->Columns[column_index].OffsetNorm; 3674 const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t); 3675 return x_offset; 3676 } 3677 3678 static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false) 3679 { 3680 if (column_index < 0) 3681 column_index = columns->Current; 3682 3683 float offset_norm; 3684 if (before_resize) 3685 offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; 3686 else 3687 offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; 3688 return ImGui::GetColumnOffsetFromNorm(columns, offset_norm); 3689 } 3690 3691 float ImGui::GetColumnWidth(int column_index) 3692 { 3693 ImGuiContext& g = *GImGui; 3694 ImGuiWindow* window = g.CurrentWindow; 3695 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3696 if (columns == NULL) 3697 return GetContentRegionAvail().x; 3698 3699 if (column_index < 0) 3700 column_index = columns->Current; 3701 return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); 3702 } 3703 3704 void ImGui::SetColumnOffset(int column_index, float offset) 3705 { 3706 ImGuiContext& g = *GImGui; 3707 ImGuiWindow* window = g.CurrentWindow; 3708 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3709 IM_ASSERT(columns != NULL); 3710 3711 if (column_index < 0) 3712 column_index = columns->Current; 3713 IM_ASSERT(column_index < columns->Columns.Size); 3714 3715 const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1); 3716 const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; 3717 3718 if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow)) 3719 offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); 3720 columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX); 3721 3722 if (preserve_width) 3723 SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); 3724 } 3725 3726 void ImGui::SetColumnWidth(int column_index, float width) 3727 { 3728 ImGuiWindow* window = GetCurrentWindowRead(); 3729 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3730 IM_ASSERT(columns != NULL); 3731 3732 if (column_index < 0) 3733 column_index = columns->Current; 3734 SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); 3735 } 3736 3737 void ImGui::PushColumnClipRect(int column_index) 3738 { 3739 ImGuiWindow* window = GetCurrentWindowRead(); 3740 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3741 if (column_index < 0) 3742 column_index = columns->Current; 3743 3744 ImGuiOldColumnData* column = &columns->Columns[column_index]; 3745 PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); 3746 } 3747 3748 // Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns) 3749 void ImGui::PushColumnsBackground() 3750 { 3751 ImGuiWindow* window = GetCurrentWindowRead(); 3752 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3753 if (columns->Count == 1) 3754 return; 3755 3756 // Optimization: avoid SetCurrentChannel() + PushClipRect() 3757 columns->HostBackupClipRect = window->ClipRect; 3758 SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect); 3759 columns->Splitter.SetCurrentChannel(window->DrawList, 0); 3760 } 3761 3762 void ImGui::PopColumnsBackground() 3763 { 3764 ImGuiWindow* window = GetCurrentWindowRead(); 3765 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3766 if (columns->Count == 1) 3767 return; 3768 3769 // Optimization: avoid PopClipRect() + SetCurrentChannel() 3770 SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect); 3771 columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); 3772 } 3773 3774 ImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id) 3775 { 3776 // We have few columns per window so for now we don't need bother much with turning this into a faster lookup. 3777 for (int n = 0; n < window->ColumnsStorage.Size; n++) 3778 if (window->ColumnsStorage[n].ID == id) 3779 return &window->ColumnsStorage[n]; 3780 3781 window->ColumnsStorage.push_back(ImGuiOldColumns()); 3782 ImGuiOldColumns* columns = &window->ColumnsStorage.back(); 3783 columns->ID = id; 3784 return columns; 3785 } 3786 3787 ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count) 3788 { 3789 ImGuiWindow* window = GetCurrentWindow(); 3790 3791 // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. 3792 // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. 3793 PushID(0x11223347 + (str_id ? 0 : columns_count)); 3794 ImGuiID id = window->GetID(str_id ? str_id : "columns"); 3795 PopID(); 3796 3797 return id; 3798 } 3799 3800 void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags) 3801 { 3802 ImGuiContext& g = *GImGui; 3803 ImGuiWindow* window = GetCurrentWindow(); 3804 3805 IM_ASSERT(columns_count >= 1); 3806 IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported 3807 3808 // Acquire storage for the columns set 3809 ImGuiID id = GetColumnsID(str_id, columns_count); 3810 ImGuiOldColumns* columns = FindOrCreateColumns(window, id); 3811 IM_ASSERT(columns->ID == id); 3812 columns->Current = 0; 3813 columns->Count = columns_count; 3814 columns->Flags = flags; 3815 window->DC.CurrentColumns = columns; 3816 3817 columns->HostCursorPosY = window->DC.CursorPos.y; 3818 columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x; 3819 columns->HostInitialClipRect = window->ClipRect; 3820 columns->HostBackupParentWorkRect = window->ParentWorkRect; 3821 window->ParentWorkRect = window->WorkRect; 3822 3823 // Set state for first column 3824 // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect 3825 const float column_padding = g.Style.ItemSpacing.x; 3826 const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize)); 3827 const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f); 3828 const float max_2 = window->WorkRect.Max.x + half_clip_extend_x; 3829 columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f); 3830 columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f); 3831 columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; 3832 3833 // Clear data if columns count changed 3834 if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) 3835 columns->Columns.resize(0); 3836 3837 // Initialize default widths 3838 columns->IsFirstFrame = (columns->Columns.Size == 0); 3839 if (columns->Columns.Size == 0) 3840 { 3841 columns->Columns.reserve(columns_count + 1); 3842 for (int n = 0; n < columns_count + 1; n++) 3843 { 3844 ImGuiOldColumnData column; 3845 column.OffsetNorm = n / (float)columns_count; 3846 columns->Columns.push_back(column); 3847 } 3848 } 3849 3850 for (int n = 0; n < columns_count; n++) 3851 { 3852 // Compute clipping rectangle 3853 ImGuiOldColumnData* column = &columns->Columns[n]; 3854 float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n)); 3855 float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f); 3856 column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); 3857 column->ClipRect.ClipWithFull(window->ClipRect); 3858 } 3859 3860 if (columns->Count > 1) 3861 { 3862 columns->Splitter.Split(window->DrawList, 1 + columns->Count); 3863 columns->Splitter.SetCurrentChannel(window->DrawList, 1); 3864 PushColumnClipRect(0); 3865 } 3866 3867 // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user. 3868 float offset_0 = GetColumnOffset(columns->Current); 3869 float offset_1 = GetColumnOffset(columns->Current + 1); 3870 float width = offset_1 - offset_0; 3871 PushItemWidth(width * 0.65f); 3872 window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); 3873 window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); 3874 window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; 3875 } 3876 3877 void ImGui::NextColumn() 3878 { 3879 ImGuiWindow* window = GetCurrentWindow(); 3880 if (window->SkipItems || window->DC.CurrentColumns == NULL) 3881 return; 3882 3883 ImGuiContext& g = *GImGui; 3884 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3885 3886 if (columns->Count == 1) 3887 { 3888 window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); 3889 IM_ASSERT(columns->Current == 0); 3890 return; 3891 } 3892 3893 // Next column 3894 if (++columns->Current == columns->Count) 3895 columns->Current = 0; 3896 3897 PopItemWidth(); 3898 3899 // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect() 3900 // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them), 3901 ImGuiOldColumnData* column = &columns->Columns[columns->Current]; 3902 SetWindowClipRectBeforeSetChannel(window, column->ClipRect); 3903 columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); 3904 3905 const float column_padding = g.Style.ItemSpacing.x; 3906 columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); 3907 if (columns->Current > 0) 3908 { 3909 // Columns 1+ ignore IndentX (by canceling it out) 3910 // FIXME-COLUMNS: Unnecessary, could be locked? 3911 window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding; 3912 } 3913 else 3914 { 3915 // New row/line: column 0 honor IndentX. 3916 window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); 3917 columns->LineMinY = columns->LineMaxY; 3918 } 3919 window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); 3920 window->DC.CursorPos.y = columns->LineMinY; 3921 window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); 3922 window->DC.CurrLineTextBaseOffset = 0.0f; 3923 3924 // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup. 3925 float offset_0 = GetColumnOffset(columns->Current); 3926 float offset_1 = GetColumnOffset(columns->Current + 1); 3927 float width = offset_1 - offset_0; 3928 PushItemWidth(width * 0.65f); 3929 window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; 3930 } 3931 3932 void ImGui::EndColumns() 3933 { 3934 ImGuiContext& g = *GImGui; 3935 ImGuiWindow* window = GetCurrentWindow(); 3936 ImGuiOldColumns* columns = window->DC.CurrentColumns; 3937 IM_ASSERT(columns != NULL); 3938 3939 PopItemWidth(); 3940 if (columns->Count > 1) 3941 { 3942 PopClipRect(); 3943 columns->Splitter.Merge(window->DrawList); 3944 } 3945 3946 const ImGuiOldColumnFlags flags = columns->Flags; 3947 columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); 3948 window->DC.CursorPos.y = columns->LineMaxY; 3949 if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize)) 3950 window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent 3951 3952 // Draw columns borders and handle resize 3953 // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy 3954 bool is_being_resized = false; 3955 if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems) 3956 { 3957 // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers. 3958 const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y); 3959 const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y); 3960 int dragging_column = -1; 3961 for (int n = 1; n < columns->Count; n++) 3962 { 3963 ImGuiOldColumnData* column = &columns->Columns[n]; 3964 float x = window->Pos.x + GetColumnOffset(n); 3965 const ImGuiID column_id = columns->ID + ImGuiID(n); 3966 const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH; 3967 const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); 3968 KeepAliveID(column_id); 3969 if (IsClippedEx(column_hit_rect, column_id, false)) 3970 continue; 3971 3972 bool hovered = false, held = false; 3973 if (!(flags & ImGuiOldColumnFlags_NoResize)) 3974 { 3975 ButtonBehavior(column_hit_rect, column_id, &hovered, &held); 3976 if (hovered || held) 3977 g.MouseCursor = ImGuiMouseCursor_ResizeEW; 3978 if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize)) 3979 dragging_column = n; 3980 } 3981 3982 // Draw column 3983 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); 3984 const float xi = IM_FLOOR(x); 3985 window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); 3986 } 3987 3988 // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. 3989 if (dragging_column != -1) 3990 { 3991 if (!columns->IsBeingResized) 3992 for (int n = 0; n < columns->Count + 1; n++) 3993 columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; 3994 columns->IsBeingResized = is_being_resized = true; 3995 float x = GetDraggedColumnOffset(columns, dragging_column); 3996 SetColumnOffset(dragging_column, x); 3997 } 3998 } 3999 columns->IsBeingResized = is_being_resized; 4000 4001 window->WorkRect = window->ParentWorkRect; 4002 window->ParentWorkRect = columns->HostBackupParentWorkRect; 4003 window->DC.CurrentColumns = NULL; 4004 window->DC.ColumnsOffset.x = 0.0f; 4005 window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); 4006 } 4007 4008 void ImGui::Columns(int columns_count, const char* id, bool border) 4009 { 4010 ImGuiWindow* window = GetCurrentWindow(); 4011 IM_ASSERT(columns_count >= 1); 4012 4013 ImGuiOldColumnFlags flags = (border ? 0 : ImGuiOldColumnFlags_NoBorder); 4014 //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior 4015 ImGuiOldColumns* columns = window->DC.CurrentColumns; 4016 if (columns != NULL && columns->Count == columns_count && columns->Flags == flags) 4017 return; 4018 4019 if (columns != NULL) 4020 EndColumns(); 4021 4022 if (columns_count != 1) 4023 BeginColumns(id, columns_count, flags); 4024 } 4025 4026 //------------------------------------------------------------------------- 4027 4028 #endif // #ifndef IMGUI_DISABLE