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