From 0e3ba37e6d7253d5d9fb00856016b3f60dce09f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 8 Jan 2021 18:08:35 +0100 Subject: [PATCH] Tables: Tidying up. Shuffle some columns fields to facilitate debugging + comments + demo tweaks + metrics highlight. --- imgui_demo.cpp | 25 ++++++----- imgui_internal.h | 18 ++++---- imgui_tables.cpp | 105 +++++++++++++++++++++++++---------------------- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 33e5c476..912fa709 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3514,12 +3514,12 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", &flags, ImGuiTableFlags_BordersOuter); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", &flags, ImGuiTableFlags_BordersInner); ImGui::Unindent(); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); ImGui::AlignTextToFramePadding(); ImGui::Text("Cell contents:"); ImGui::SameLine(); ImGui::RadioButton("Text", &contents_type, CT_Text); ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); ImGui::Checkbox("Display headers", &display_headers); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); PopStyleCompact(); if (ImGui::BeginTable("##table1", 3, flags)) @@ -3563,7 +3563,7 @@ static void ShowDemoWindowTables() PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); - ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well."); + ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well, this is why the resize borders are still showing when unchecking this."); PopStyleCompact(); if (ImGui::BeginTable("##table1", 3, flags)) @@ -3670,7 +3670,9 @@ static void ShowDemoWindowTables() ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Reorderable, hideable, with headers")) { - HelpMarker("Click and drag column headers to reorder columns.\n\nYou can also right-click on a header to open a context menu."); + HelpMarker( + "Click and drag column headers to reorder columns.\n\n" + "Right-click on a header to open a context menu."); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); @@ -3733,7 +3735,8 @@ static void ShowDemoWindowTables() "- BorderOuterV\n" "- any form of row selection\n" "Because of this, activating BorderOuterV sets the default to PadOuterX. Using PadOuterX or NoPadOuterX you can override the default.\n\n" - "Actual padding values are using style.CellPadding."); + "Actual padding values are using style.CellPadding.\n\n" + "In this demo we don't show horizontal borders to emphasis how they don't affect default horizontal padding."); static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV; PushStyleCompact(); @@ -3787,7 +3790,7 @@ static void ShowDemoWindowTables() HelpMarker("Setting style.CellPadding to (0,0) or a custom value."); static ImGuiTableFlags flags2 = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; static ImVec2 cell_padding(0.0f, 0.0f); - static bool show_widget_frame_bg = false; + static bool show_widget_frame_bg = true; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags2, ImGuiTableFlags_Borders); @@ -3833,14 +3836,15 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Sizing policies")) { HelpMarker("This section allows you to interact and see the effect of various sizing policies depending on whether Scroll is enabled and the contents of your columns."); - enum ContentsType { CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText }; - static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; - static int contents_type = CT_LongText; + + enum ContentsType { CT_ShowWidth, CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText }; + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable; + static int contents_type = CT_ShowWidth; static int column_count = 3; PushStyleCompact(); ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30); - ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); + ImGui::Combo("Contents", &contents_type, "Show width\0Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); if (contents_type == CT_FillButton) { ImGui::SameLine(); @@ -3878,7 +3882,8 @@ static void ShowDemoWindowTables() switch (contents_type) { case CT_ShortText: ImGui::TextUnformatted(label); break; - case CT_LongText: ImGui::Text("Some longer text %d,%d\nOver two lines..", column, row); break; + case CT_LongText: ImGui::Text("Some %s text %d,%d\nOver two lines..", column == 0 ? "long" : "longeeer", column, row); break; + case CT_ShowWidth: ImGui::Text("W: %.1f", ImGui::GetContentRegionAvail().x); break; case CT_Button: ImGui::Button(label); break; case CT_FillButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); break; diff --git a/imgui_internal.h b/imgui_internal.h index d1a47870..f800bb34 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1910,19 +1910,19 @@ typedef ImU8 ImGuiTableDrawChannelIdx; // This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped". struct ImGuiTableColumn { - ImRect ClipRect; // Clipping rectangle for the column - ImGuiID UserID; // Optional, value passed to TableSetupColumn() ImGuiTableColumnFlags Flags; // Flags after some patching (not directly same as provided by user). See ImGuiTableColumnFlags_ + float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space. float MinX; // Absolute positions float MaxX; - float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). - float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. - float WidthAuto; // Automatic width float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout() - float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space. - float WorkMinX; // Start position for the frame, currently ~(MinX + CellPaddingX) - float WorkMaxX; - float ItemWidth; + float WidthAuto; // Automatic width + float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. + float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). + ImRect ClipRect; // Clipping rectangle for the column + ImGuiID UserID; // Optional, value passed to TableSetupColumn() + float WorkMinX; // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column + float WorkMaxX; // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2) + float ItemWidth; // Current item width for the column, preserved across rows float ContentMaxXFrozen; // Contents maximum position for frozen rows (apart from headers), from which we can infer content width. float ContentMaxXUnfrozen; float ContentMaxXHeadersUsed; // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls diff --git a/imgui_tables.cpp b/imgui_tables.cpp index cdfd5243..21bef434 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -5,6 +5,8 @@ Index of this file: +// [SECTION] Commentary +// [SECTION] Header mess // [SECTION] Tables: Main code // [SECTION] Tables: Row changes // [SECTION] Tables: Columns changes @@ -24,6 +26,10 @@ Index of this file: // - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. // - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +//----------------------------------------------------------------------------- +// [SECTION] Commentary +//----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Typical tables call flow: (root level is generally public API): //----------------------------------------------------------------------------- @@ -150,6 +156,10 @@ Index of this file: // - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false. //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// [SECTION] Header mess +//----------------------------------------------------------------------------- + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif @@ -169,10 +179,6 @@ Index of this file: #include // intptr_t #endif -//------------------------------------------------------------------------- -// Warnings -//------------------------------------------------------------------------- - // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant @@ -597,8 +603,13 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I else flags |= ImGuiTableColumnFlags_WidthStretch; } - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. - if (flags & ImGuiTableColumnFlags_WidthAuto) + else + { + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. + } + + // Resize + if ((flags & ImGuiTableColumnFlags_WidthAuto) != 0 || (table->Flags & ImGuiTableFlags_Resizable) == 0) flags |= ImGuiTableColumnFlags_NoResize; // Sorting @@ -652,7 +663,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. // Process columns in their visible orders as we are building the Prev/Next indices. int last_visible_column_idx = -1; - bool want_auto_fit = false; + bool has_auto_fit_request = false; + bool has_resizable = false; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { const int column_n = table->DisplayOrderToIndex[order_n]; @@ -660,11 +672,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsDefaultDisplayOrder = false; ImGuiTableColumn* column = &table->Columns[column_n]; - // Clear column settings if not submitted by user. - // Currently we make it mandatory to call TableSetupColumn() every frame. - // It would easily work without but we're ready to guarantee it since e.g. names need resubmission anyway. - // In theory we could be calling TableSetupColumn() here with dummy values it should yield the same effect. - if (column_n >= table->DeclColumnsCount) + // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame. + // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway. + // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect. + if (table->DeclColumnsCount <= column_n) { TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None); column->NameOffset = -1; @@ -672,6 +683,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->InitStretchWeightOrWidth = -1.0f; } + // Update Enabled state, mark settings/sortspecs dirty if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) column->IsEnabledNextFrame = true; if (column->IsEnabled != column->IsEnabledNextFrame) @@ -684,16 +696,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti)) table->IsSortSpecsDirty = true; - bool start_auto_fit = false; - if (column->Flags & (ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAuto)) - start_auto_fit = column->WidthRequest < 0.0f; - else - start_auto_fit = column->StretchWeight < 0.0f; + // Auto-fit unsized columns + const bool start_auto_fit = (column->Flags & (ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAuto)) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f); if (start_auto_fit) column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames - ImU64 index_mask = (ImU64)1 << column_n; - ImU64 display_order_mask = (ImU64)1 << column->DisplayOrder; if (!column->IsEnabled) { column->IndexWithinEnabledSet = -1; @@ -705,10 +712,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->NextEnabledColumn = -1; if (last_visible_column_idx != -1) table->Columns[last_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n; - column->IndexWithinEnabledSet = table->ColumnsEnabledCount; - table->ColumnsEnabledCount++; - table->EnabledMaskByIndex |= index_mask; - table->EnabledMaskByDisplayOrder |= display_order_mask; + column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; + table->EnabledMaskByIndex |= (ImU64)1 << column_n; + table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder; last_visible_column_idx = column_n; IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); @@ -717,8 +723,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (!column->IsPreserveWidthAuto) column->WidthAuto = TableGetColumnWidthAuto(table, column); + const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; + if (column_is_resizable) + has_resizable = true; if (column->AutoFitQueue != 0x00) - want_auto_fit = true; + has_auto_fit_request = true; } if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) table->IsSortSpecsDirty = true; @@ -726,16 +735,15 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) IM_ASSERT(table->RightMostEnabledColumn >= 0); // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible - // to avoid the column fitting to wait until the first visible frame of the child container (may or not be a good thing). + // to avoid the column fitting having to wait until the first visible frame of the child container (may or not be a good thing). // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time. - if (want_auto_fit && table->OuterWindow != table->InnerWindow) + if (has_auto_fit_request && table->OuterWindow != table->InnerWindow) table->InnerWindow->SkipItems = false; - if (want_auto_fit) + if (has_auto_fit_request) table->IsSettingsDirty = true; // [Part 3] Fix column flags. Count how many fixed/stretch columns we have and sum of weights. int count_fixed = 0; // Number of columns that have fixed sizing policy (not stretched sizing policy) (this is NOT the opposite of count_resizable!) - int count_resizable = 0; // Number of columns the user can resize (this is NOT the opposite of count_fixed!) 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. float max_width_auto = 0.0f; // Largest auto-width (used for SameWidths feature) float stretch_sum_weights = 0.0f; // Sum of all weights for stretch columns. @@ -746,10 +754,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) continue; ImGuiTableColumn* column = &table->Columns[column_n]; - // Count resizable columns - if ((column->Flags & ImGuiTableColumnFlags_NoResize) == 0) - count_resizable++; - if (column->Flags & (ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAuto)) { // Non-resizable columns keep their requested width @@ -775,8 +779,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } else { - IM_ASSERT_PARANOID(column->Flags & ImGuiTableColumnFlags_WidthStretch); - if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f) column->StretchWeight = (column->InitStretchWeightOrWidth > 0.0f) ? column->InitStretchWeightOrWidth : 1.0f; @@ -1004,7 +1006,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (g.IO.MousePos.x >= unused_x1) table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount; } - if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) + if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable)) table->Flags &= ~ImGuiTableFlags_Resizable; // [Part 9] Lock actual OuterRect/WorkRect right-most position. @@ -1318,20 +1320,21 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo if (flags & ImGuiTableColumnFlags_WidthStretch) IM_ASSERT(init_width_or_weight != 0.0f && "Need to provide a valid weight!"); column->InitStretchWeightOrWidth = init_width_or_weight; - if (table->IsInitializing && column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) - { - // Init width or weight - if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) - column->WidthRequest = init_width_or_weight; - if (flags & ImGuiTableColumnFlags_WidthStretch) - column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; - - // Disable auto-fit if an explicit width/weight has been specified - if (init_width_or_weight > 0.0f) - column->AutoFitQueue = 0x00; - } if (table->IsInitializing) { + // Init width or weight + if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) + { + if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) + column->WidthRequest = init_width_or_weight; + if (flags & ImGuiTableColumnFlags_WidthStretch) + column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; + + // Disable auto-fit if an explicit width/weight has been specified + if (init_width_or_weight > 0.0f) + column->AutoFitQueue = 0x00; + } + // Init default visibility/sort state if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) column->IsEnabled = column->IsEnabledNextFrame = false; @@ -1995,7 +1998,7 @@ void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Can reset weight of hidden stretch column + if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Cannot reset weight of hidden stretch column continue; column->CannotSkipItemsQueue = (1 << 0); column->AutoFitQueue = (1 << 1); @@ -2639,6 +2642,10 @@ void ImGui::TableHeadersRow() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + // 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) + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + // Open row const float row_y1 = GetCursorScreenPos().y; const float row_height = TableGetHeaderRowHeight(); @@ -3327,6 +3334,8 @@ void ImGui::DebugNodeTable(ImGuiTable* table) if (!is_active) { PopStyleColor(); } if (IsItemHovered()) GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + if (IsItemVisible() && table->HoveredColumnBody != -1) + GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255)); if (!open) return; bool clear_settings = SmallButton("Clear settings");