From 3b3503e60f1379f88a43e8adf106b82c4180e2b6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 16 Nov 2020 20:25:35 +0100 Subject: [PATCH] Tables: decent support for auto-resize of stretch columns (trickier than it sounds) Four cases: 1. visible columns are all stretch, resize all : "size all to default" reset to default weight 2. visible columns are all stretch, resize one: "size one to fit" set weight, reapply weight (todo: improve weight redistribution in case of >1 siblings) 3. visible columns are mixed, resize all: "size all to fit/default" reset stretchs to default weight, set fixed to auto width 4. visible columns are mixed, resize one: "size one to fit", redistribute weight the same way as a manual resize + TableSetupColumn() more consistently clear AutoFitQueue. + zero-clear RowCellData buffer. --- imgui_demo.cpp | 4 +- imgui_internal.h | 9 ++-- imgui_tables.cpp | 122 ++++++++++++++++++++++++++++++----------------- 3 files changed, 85 insertions(+), 50 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 6acbe62aa..f0b063006 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3514,7 +3514,7 @@ static void ShowDemoWindowTables() { // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" // Each columns maintain a sizing weight, and they will occupy all available width. - static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); @@ -3549,7 +3549,7 @@ static void ShowDemoWindowTables() "Using _Resizable + _SizingPolicyFixedX flags.\n" "Fixed-width columns generally makes more sense if you want to use horizontal scrolling.\n\n" "Double-click a column border to auto-fit the column to its contents."); - static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody; //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); // FIXME-TABLE: Explain or fix the effect of enable Scroll on outer_size if (ImGui::BeginTable("##table1", 3, flags)) { diff --git a/imgui_internal.h b/imgui_internal.h index 0515d7e8e..3167728f0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -519,6 +519,7 @@ struct ImSpan inline void set(T* data, int size) { Data = data; DataEnd = data + size; } inline void set(T* data, T* data_end) { Data = data; DataEnd = data_end; } inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } + inline int size_in_bytes() const { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); } inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } @@ -1941,7 +1942,6 @@ struct ImGuiTableColumn PrevVisibleColumn = NextVisibleColumn = -1; SortOrder = -1; SortDirection = ImGuiSortDirection_None; - AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1; } }; @@ -1969,7 +1969,6 @@ struct ImGuiTable int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; int ColumnsCount; // Number of columns declared in BeginTable() - int ColumnsVisibleCount; // Number of non-hidden columns (<= ColumnsCount) int CurrentRow; int CurrentColumn; ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched. @@ -2018,9 +2017,12 @@ struct ImGuiTable ImVector SortSpecsData; // FIXME-OPT: Fixed-size array / small-vector pattern, optimize for single sort spec ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() ImS8 SortSpecsCount; + ImS8 ColumnsVisibleCount; // Number of non-hidden columns (<= ColumnsCount) + ImS8 ColumnsVisibleFixedCount; // Number of non-hidden columns (<= ColumnsCount) ImS8 DeclColumnsCount; // Count calls to TableSetupColumn() ImS8 HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column! ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). + ImS8 AutoFitSingleStretchColumn; // Index of single stretch column requesting auto-fit. ImS8 ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. ImS8 LastResizedColumn; // Index of column being resized from previous frame. ImS8 HeldHeaderColumn; // Index of column header being held. @@ -2287,7 +2289,8 @@ namespace ImGui IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); IMGUI_API ImGuiID TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no = 0); - IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); IMGUI_API void TableRemove(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e893ef377..6e04874c1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -155,6 +155,7 @@ ImGuiTable::ImGuiTable() ContextPopupColumn = -1; ReorderColumn = -1; ResizedColumn = -1; + AutoFitSingleStretchColumn = -1; HoveredColumnBody = HoveredColumnBorder = -1; } @@ -216,10 +217,13 @@ static void TableBeginInitMemory(ImGuiTable* table, int columns_count) span_allocator.GetSpan(1, &table->DisplayOrderToIndex); span_allocator.GetSpan(2, &table->RowCellData); + memset(table->RowCellData.Data, 0, table->RowCellData.size_in_bytes()); for (int n = 0; n < columns_count; n++) { - table->Columns[n] = ImGuiTableColumn(); - table->Columns[n].DisplayOrder = table->DisplayOrderToIndex[n] = (ImS8)n; + ImGuiTableColumn* column = &table->Columns[n]; + *column = ImGuiTableColumn(); + column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImS8)n; + column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames } } @@ -440,6 +444,15 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->LastResizedColumn = table->ResizedColumn; table->ResizedColumnNextWidth = FLT_MAX; table->ResizedColumn = -1; + + // Process auto-fit for single stretch column, which is a special case + // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings. + if (table->AutoFitSingleStretchColumn != -1) + { + table->Columns[table->AutoFitSingleStretchColumn].AutoFitQueue = 0x00; + TableSetColumnWidth(table->AutoFitSingleStretchColumn, table->Columns[table->AutoFitSingleStretchColumn].WidthAuto); + table->AutoFitSingleStretchColumn = -1; + } } // Handle reordering request @@ -486,7 +499,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->IsSettingsDirty = true; } - // Setup and lock Visible state and order + // Lock Visible state and Order table->ColumnsVisibleCount = 0; table->IsDefaultDisplayOrder = true; ImGuiTableColumn* last_visible_column = NULL; @@ -522,7 +535,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) last_visible_column->NextVisibleColumn = (ImS8)column_n; column->PrevVisibleColumn = (ImS8)table->Columns.index_from_ptr(last_visible_column); } - column->IndexWithinVisibleSet = (ImS8)table->ColumnsVisibleCount; + column->IndexWithinVisibleSet = table->ColumnsVisibleCount; table->ColumnsVisibleCount++; table->VisibleMaskByIndex |= index_mask; table->VisibleMaskByDisplayOrder |= display_order_mask; @@ -658,6 +671,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) { + // Process auto-fit for non-stretched columns // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) if ((column->AutoFitQueue != 0x00) || ((column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) && !column->IsClipped)) column->WidthRequest = width_auto; @@ -676,15 +690,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else { IM_ASSERT(column->Flags & ImGuiTableColumnFlags_WidthStretch); - const int init_size = (column->StretchWeight < 0.0f); - if (init_size) - column->StretchWeight = 1.0f; + const float default_weight = (column->InitStretchWeightOrWidth > 0.0f) ? column->InitStretchWeightOrWidth : 1.0f; + if (column->AutoFitQueue != 0x00) + column->StretchWeight = default_weight; sum_weights_stretched += column->StretchWeight; if (table->LeftMostStretchedColumnDisplayOrder == -1) table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->DisplayOrder; } sum_width_fixed_requests += table->CellPaddingX * 2.0f; } + table->ColumnsVisibleFixedCount = (ImS8)count_fixed; // Layout const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsVisibleCount - 1); @@ -963,10 +978,9 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) bool hovered = false, held = false; bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); - if (pressed && IsMouseDoubleClicked(0) && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + if (pressed && IsMouseDoubleClicked(0)) { - // FIXME-TABLE: Double-clicking on column edge could auto-fit Stretch column? - TableSetColumnAutofit(table, column_n); + TableSetColumnWidthAutoSingle(table, column_n); ClearActiveID(); held = hovered = false; } @@ -1313,21 +1327,28 @@ void ImGui::TableSetColumnWidth(int column_n, float width) } else if (column_0->Flags & ImGuiTableColumnFlags_WidthStretch) { - // [Resize Rule 2] - if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) + // We can also use previous column if there's no next one + if (column_1 == NULL) + column_1 = (column_0->PrevVisibleColumn != -1) ? &table->Columns[column_0->PrevVisibleColumn] : NULL; + if (column_1 == NULL) + return; + + if (column_1->Flags & ImGuiTableColumnFlags_WidthFixed) { + // [Resize Rule 2] float off = (column_0->WidthGiven - column_0_width); float column_1_width = column_1->WidthGiven + off; column_1->WidthRequest = ImMax(min_width, column_1_width); - return; } - - // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) - float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); - column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; - column_1->WidthRequest = column_1_width; - column_0->WidthRequest = column_0_width; - TableUpdateColumnsWeightFromWidth(table); + else + { + // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) + float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + column_1->WidthRequest = column_1_width; + column_0->WidthRequest = column_0_width; + TableUpdateColumnsWeightFromWidth(table); + } } } @@ -1613,25 +1634,19 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, // Initialize defaults if (flags & ImGuiTableColumnFlags_WidthStretch) - { IM_ASSERT(init_width_or_weight != 0.0f && "Need to provide a valid weight!"); - if (init_width_or_weight < 0.0f) - init_width_or_weight = 1.0f; - } 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) - { - // Disable auto-fit if a default fixed width has been specified column->WidthRequest = init_width_or_weight; - column->AutoFitQueue = 0x00; - } if (flags & ImGuiTableColumnFlags_WidthStretch) - column->StretchWeight = init_width_or_weight; - else - column->StretchWeight = 1.0f; + column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : 1.0f; + + // Disable auto-fit if an explicit fixed width has been specified + if (init_width_or_weight > 0.0f) + column->AutoFitQueue = 0x00; } if (table->IsInitializing) { @@ -2041,13 +2056,30 @@ ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int return id; } -void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_n) +// Disable clipping then auto-fit, will take 2 frames +// (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) +void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n) { - // Disable clipping then auto-fit, will take 2 frames - // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) + // Single auto width uses auto-fit ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsVisible) + return; column->CannotSkipItemsQueue = (1 << 0); column->AutoFitQueue = (1 << 1); + if (column->Flags & ImGuiTableColumnFlags_WidthStretch) + table->AutoFitSingleStretchColumn = (ImS8)column_n; +} + +void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table) +{ + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsVisible) + continue; + column->CannotSkipItemsQueue = (1 << 0); + column->AutoFitQueue = (1 << 1); + } } void ImGui::PushTableBackground() @@ -2092,20 +2124,20 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) { if (column != NULL) { - const bool can_resize = !(column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && column->IsVisible; + const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsVisible; if (MenuItem("Size column to fit", NULL, false, can_resize)) - TableSetColumnAutofit(table, column_n); + TableSetColumnWidthAutoSingle(table, column_n); } - if (MenuItem("Size all columns to fit", NULL)) - { - for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) - { - ImGuiTableColumn* other_column = &table->Columns[other_column_n]; - if (other_column->IsVisible) - TableSetColumnAutofit(table, other_column_n); - } - } + const char* size_all_desc; + if (table->ColumnsVisibleFixedCount == table->ColumnsVisibleCount) + size_all_desc = "Size all columns to fit"; // All fixed + else if (table->ColumnsVisibleFixedCount == 0) + size_all_desc = "Size all columns to default"; // All stretch + else + size_all_desc = "Size all columns to fit/default"; // Mixed + if (MenuItem(size_all_desc, NULL)) + TableSetColumnWidthAutoAll(table); want_separator = true; }