From 883c236edaf88ce3e030309c9352d1d21478b1e5 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 28 Dec 2019 19:17:59 +0100 Subject: [PATCH] Tables: Handle columns clipped due to host rect Return false in user functions, set SkipItems in window, redirect to dummy draw channel. --- imgui.cpp | 1 + imgui_demo.cpp | 4 +- imgui_internal.h | 10 +++-- imgui_tables.cpp | 106 ++++++++++++++++++++++++++++++----------------- 4 files changed, 78 insertions(+), 43 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c7be36245..660e2b55f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -11068,6 +11068,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImGuiWindow*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} +void ImGui::DebugNodeTable(ImGuiTable*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 893205932..7c0c4a7fb 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3619,7 +3619,9 @@ static void ShowDemoWindowTables() ImGui::TableNextRow(); for (int column = 0; column < 7; column++) { - ImGui::TableSetColumnIndex(column); + // Both TableNextCell() and TableSetColumnIndex() return false when a column is not visible, which can be used for clipping. + if (!ImGui::TableSetColumnIndex(column)) + continue; if (column == 0) ImGui::Text("Line %d", row); else diff --git a/imgui_internal.h b/imgui_internal.h index 6cddf0c99..f5678f839 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1848,6 +1848,7 @@ struct ImGuiTableColumn ImS16 NameOffset; // Offset into parent ColumnsName[] bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. bool NextIsActive; + bool IsClipped; // Set when not overlapping the host window clipping rectangle. We don't use the opposite "!Visible" name because Clipped can be altered by events. ImS8 IndexDisplayOrder; // Index within DisplayOrder[] (column may be reordered by users) ImS8 IndexWithinActiveSet; // Index within active set (<= IndexOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] @@ -1855,7 +1856,8 @@ struct ImGuiTableColumn ImS8 DrawChannelRowsAfterFreeze; ImS8 PrevActiveColumn; // Index of prev active column within Columns[], -1 if first active column ImS8 NextActiveColumn; // Index of next active column within Columns[], -1 if last active column - ImS8 AutoFitFrames; + ImS8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit + ImS8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem ImS8 SortOrder; // -1: Not sorting on this column ImS8 SortDirection; // enum ImGuiSortDirection_ @@ -1869,7 +1871,7 @@ struct ImGuiTableColumn IndexDisplayOrder = IndexWithinActiveSet = -1; DrawChannelCurrent = DrawChannelRowsBeforeFreeze = DrawChannelRowsAfterFreeze = -1; PrevActiveColumn = NextActiveColumn = -1; - AutoFitFrames = 3; + AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames SortOrder = -1; SortDirection = ImGuiSortDirection_Ascending; } @@ -1884,6 +1886,7 @@ struct ImGuiTable ImVector DisplayOrder; // Store display order of columns (when not reordered, the values are 0...Count-1) ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map + ImU64 VisibleMaskByIndex; // Visible (== Active and not Clipped) ImGuiTableFlags SettingsSaveFlags; // Pre-compute which data we are going to save into the .ini file (e.g. when order is not altered we won't save order) int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; @@ -2179,7 +2182,7 @@ namespace ImGui //IMGUI_API bool SetTableColumnNo(int column_n); //IMGUI_API int GetTableLineNo(); IMGUI_API void TableBeginInitVisibility(ImGuiTable* table); - IMGUI_API void TableBeginInitDrawChannels(ImGuiTable* table); + IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); @@ -2194,6 +2197,7 @@ namespace ImGui IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API ImRect TableGetCellRect(); IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_no); + IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_no); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); IMGUI_API void TableLoadSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d545b45a8..e847d4773 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -65,13 +65,13 @@ // - BeginTable() user begin into a table // - BeginChild() - (if ScrollX/ScrollY is set) // - TableBeginInitVisibility() - lock columns visibility -// - TableBeginInitDrawChannels() - setup ImDrawList channels // - TableSetupColumn() user submit columns details (optional) // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) // - TableSortSpecsClickColumn() // - TableGetSortSpecs() user queries updated sort specs (optional) // - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() // - TableUpdateLayout() - called by the FIRST call to TableNextRow() +// - TableUpdateDrawChannels() - setup ImDrawList channels // - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission // - TableDrawContextMenu() - draw right-click context menu // - [...] user emit contents @@ -338,7 +338,6 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags } TableBeginInitVisibility(table); - TableBeginInitDrawChannels(table); // Grab a copy of window fields we will modify table->BackupSkipItems = inner_window->SkipItems; @@ -378,7 +377,7 @@ void ImGui::TableBeginInitVisibility(ImGuiTable* table) } if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_MultiSortable)) table->IsSortSpecsDirty = true; - if (column->AutoFitFrames > 0) + if (column->AutoFitQueue != 0x00) want_column_auto_fit = true; ImU64 index_mask = (ImU64)1 << column_n; @@ -405,6 +404,7 @@ void ImGui::TableBeginInitVisibility(ImGuiTable* table) } IM_ASSERT(column->IndexWithinActiveSet <= column->IndexDisplayOrder); } + table->VisibleMaskByIndex = table->ActiveMaskByIndex; // Columns will be masked out by TableUpdateLayout() when Clipped table->RightMostActiveColumn = (ImS8)(last_active_column ? table->Columns.index_from_ptr(last_active_column) : -1); // Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid @@ -413,7 +413,7 @@ void ImGui::TableBeginInitVisibility(ImGuiTable* table) table->InnerWindow->SkipItems = false; } -void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) +void ImGui::TableUpdateDrawChannels(ImGuiTable* table) { // Allocate draw channels. // - We allocate them following the storage order instead of the display order so reordering won't needlessly increase overall dormant memory cost @@ -428,7 +428,7 @@ void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClipX) ? 1 : table->ColumnsActiveCount; const int channels_for_background = 1; - const int channels_for_dummy = (table->ColumnsActiveCount < table->ColumnsCount) ? +1 : 0; + const int channels_for_dummy = (table->ColumnsActiveCount < table->ColumnsCount || table->VisibleMaskByIndex != table->ActiveMaskByIndex) ? +1 : 0; const int channels_total = channels_for_background + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total); table->DummyDrawChannel = channels_for_dummy ? (ImS8)(channels_total - 1) : -1; @@ -437,7 +437,7 @@ void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->IsActive) + if (!column->IsClipped) { column->DrawChannelRowsBeforeFreeze = (ImS8)(draw_channel_current); column->DrawChannelRowsAfterFreeze = (ImS8)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row : 0)); @@ -534,7 +534,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { // Latch initial size for fixed columns count_fixed += 1; - const bool init_size = (column->AutoFitFrames > 0) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); + const bool init_size = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); if (init_size) { // Combine width from regular rows + width from headers unless requested not to @@ -546,7 +546,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // FIXME-TABLE: Increase minimum size during init frame so avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. // Otherwise what tends to happen is that TextWrapped would output a very large height (= first frame scrollbar display very off + clipper would skip lots of items) // This is merely making the side-effect less extreme, but doesn't properly fixes it. - if (column->AutoFitFrames > 1 && table->IsFirstFrame) + if (column->AutoFitQueue > 0x01 && table->IsFirstFrame) column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); } width_fixed += column->WidthRequested; @@ -559,10 +559,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (table->LeftMostStretchedColumnDisplayOrder == -1) table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->IndexDisplayOrder; } - - // Don't increment auto-fit until container window got a chance to submit its items - if (column->AutoFitFrames > 0 && table->BackupSkipItems == false) - column->AutoFitFrames--; } // Layout @@ -678,6 +674,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Max.x = offset_x; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); + column->IsClipped = true; continue; } @@ -693,27 +690,44 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->MinX = offset_x; column->MaxX = column->MinX + column->WidthGiven; - const float initial_max_pos_x = column->MinX + table->CellPaddingX1; - column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; - column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; - - // Starting cursor position - column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; - - // Alignment - // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. - // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) - //if (column->Flags & ImGuiTableColumnFlags_AlignRight) - // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]); - //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) - // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]), 0.5f); - //// A one pixel padding on the right side makes clipping more noticeable and contents look less cramped. column->ClipRect.Min.x = column->MinX; column->ClipRect.Min.y = work_rect.Min.y; column->ClipRect.Max.x = column->MaxX;// -1.0f; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); + + column->IsClipped = (column->ClipRect.Max.x <= column->ClipRect.Min.x) && (column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0; + if (column->IsClipped) + { + // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. + table->VisibleMaskByIndex &= ~((ImU64)1 << column_n); + } + else + { + // Starting cursor position + column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; + + // Alignment + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. + // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) + //if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]); + //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) + // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]), 0.5f); + + // Reset content width variables + const float initial_max_pos_x = column->MinX + table->CellPaddingX1; + column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; + column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; + } + + // Don't decrement auto-fit counters until container window got a chance to submit its items + if (table->BackupSkipItems == false) + { + column->AutoFitQueue >>= 1; + column->CannotSkipItemsQueue >>= 1; + } if (active_n < table->FreezeColumnsCount) host_clip_rect.Min.x = ImMax(host_clip_rect.Min.x, column->MaxX + 2.0f); @@ -727,6 +741,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) table->Flags &= ~ImGuiTableFlags_Resizable; + // Allocate draw channels + TableUpdateDrawChannels(table); + // Borders if (table->Flags & ImGuiTableFlags_Resizable) TableUpdateBorders(table); @@ -1067,7 +1084,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // - W1 F2 F3 resize from F2| --> FIXME should resize F2, F3 and not have effect on W1 (Stretch columns are _before_ the Fixed column). // Rules: - // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableSetupLayout(). + // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). // - [Resize Rule 2] Resizing from right-side of a Stretch column before a fixed column froward sizing to left-side of fixed column. // - [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure that our left border won't move. @@ -1188,7 +1205,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to fix... // cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." - // 2019/10/22: (2) Clamping code in TableSetupLayout() seemingly made this not necessary... + // 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary... #if 0 if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) merge_set_all_fit_within_inner_rect = false; @@ -1289,7 +1306,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) { column->WidthRequested = init_width_or_weight; - column->AutoFitFrames = 0; + column->AutoFitQueue = 0x00; } if (flags & ImGuiTableColumnFlags_WidthStretch) { @@ -1502,7 +1519,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) // FIXME-COLUMNS: Setup baseline, preserve across columns (how can we obtain first line baseline tho..) // window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); - window->SkipItems = column->IsActive ? table->BackupSkipItems : true; + window->SkipItems = column->IsClipped ? true : table->BackupSkipItems; if (table->Flags & ImGuiTableFlags_NoClipX) { table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); @@ -1558,8 +1575,8 @@ bool ImGui::TableNextCell() TableNextRow(); } - ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; - return column->IsActive; + int column_n = table->CurrentColumn; + return (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) != 0; } const char* ImGui::TableGetColumnName(int column_n) @@ -1581,7 +1598,7 @@ bool ImGui::TableGetColumnIsVisible(int column_n) return false; if (column_n < 0) column_n = table->CurrentColumn; - return (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) != 0; + return (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) != 0; } int ImGui::TableGetColumnIndex() @@ -1608,7 +1625,7 @@ bool ImGui::TableSetColumnIndex(int column_idx) TableBeginCell(table, column_idx); } - return (table->ActiveMaskByIndex & ((ImU64)1 << column_idx)) != 0; + return (table->VisibleMaskByIndex & ((ImU64)1 << column_idx)) != 0; } ImRect ImGui::TableGetCellRect() @@ -1627,6 +1644,15 @@ const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_no) return &table->ColumnsNames.Buf[column->NameOffset]; } +void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_no) +{ + // 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) + ImGuiTableColumn* column = &table->Columns[column_no]; + column->CannotSkipItemsQueue = (1 << 0); + column->AutoFitQueue = (1 << 1); +} + void ImGui::PushTableBackground() { ImGuiContext& g = *GImGui; @@ -1664,7 +1690,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) { const bool can_resize = !(selected_column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && selected_column->IsActive; if (MenuItem("Size column to fit", NULL, false, can_resize)) - selected_column->AutoFitFrames = 1; + TableSetColumnAutofit(table, selected_column_n); } if (MenuItem("Size all columns to fit", NULL)) @@ -1673,7 +1699,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) { ImGuiTableColumn* column = &table->Columns[column_n]; if (column->IsActive) - column->AutoFitFrames = 1; + TableSetColumnAutofit(table, column_n); } } want_separator = true; @@ -2280,6 +2306,7 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan // - DebugNodeTable() [Internal] //------------------------------------------------------------------------- +#ifndef IMGUI_DISABLE_METRICS_WINDOW void ImGui::DebugNodeTable(ImGuiTable* table) { char buf[256]; @@ -2296,11 +2323,11 @@ void ImGui::DebugNodeTable(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[n]; const char* name = TableGetColumnName(table, n); BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" - "Active: %d, DrawChannels: %d,%d\n" + "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", n, column->IndexDisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, - column->IsActive, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, + column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->WidthGiven, column->WidthRequested, column->ResizeWeight, column->UserID, column->Flags, (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", @@ -2324,6 +2351,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) TreePop(); } } +#endif // #ifndef IMGUI_DISABLE_METRICS_WINDOW //-------------------------------------------------------------------------