From 164caa2db7c64ab5c4c8dd059d5e548e985e64b2 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 6 Jan 2020 11:53:04 +0100 Subject: [PATCH] Tables: Support for multi-line columns name. Renaming of some fields from BackupXXX to HostXXX. Comments. --- imgui.cpp | 2 +- imgui.h | 5 ++-- imgui_demo.cpp | 2 ++ imgui_internal.h | 8 +++--- imgui_tables.cpp | 63 ++++++++++++++++++++++++++++-------------------- 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 90ff9eff..c8023fdb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2142,7 +2142,7 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) static bool GetSkipItemForListClipping() { ImGuiContext& g = *GImGui; - return (g.CurrentTable ? g.CurrentTable->BackupSkipItems : g.CurrentWindow->SkipItems); + return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); } // Helper to calculate coarse clipping of large list of evenly sized items. diff --git a/imgui.h b/imgui.h index 3577fe28..e0f51274 100644 --- a/imgui.h +++ b/imgui.h @@ -693,7 +693,8 @@ namespace ImGui IMGUI_API void TableHeader(const char* label); // submit one header cell manually. // Tables: Sorting // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. - // - Read ->SpecsChanged to tell if the specs have changed since last call. + // - You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. + // - Don't hold on this structure over multiple frames. IMGUI_API const ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). // Tab Bars, Tabs @@ -1856,7 +1857,7 @@ struct ImGuiTableSortSpecs { const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. - bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. + bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. Use this to sort again! ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsChanged = false; ColumnsMask = 0x00; } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 25d035b5..f1c40e0b 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3916,6 +3916,7 @@ static void ShowDemoWindowTables() { MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + MyItem::s_current_sort_specs = NULL; } // Display data @@ -4104,6 +4105,7 @@ static void ShowDemoWindowTables() { MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + MyItem::s_current_sort_specs = NULL; } items_need_sort = false; diff --git a/imgui_internal.h b/imgui_internal.h index f897815c..9b980e0d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1918,9 +1918,11 @@ struct ImGuiTable float ResizedColumnNextWidth; ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect WorkRect; - ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. ImRect InnerClipRect; ImRect BackgroundClipRect; // We use this to cpu-clip cell background color fill + ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. + ImRect HostWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImVec2 HostCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() ImGuiWindow* OuterWindow; // Parent window for the table ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names @@ -1956,9 +1958,7 @@ struct ImGuiTable bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) bool IsResetDisplayOrderRequest; bool IsFreezeRowsPassed; // Set when we got past the frozen row (the first one). - bool BackupSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis - ImRect BackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() - ImVec2 BackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis ImGuiTable() { diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b17c1369..24519c97 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -232,6 +232,13 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG PushID(instance_id); } + // Backup a copy of host window members we will modify + ImGuiWindow* inner_window = table->InnerWindow; + table->HostClipRect = inner_window->ClipRect; + table->HostSkipItems = inner_window->SkipItems; + table->HostWorkRect = inner_window->WorkRect; + table->HostCursorMaxPos = inner_window->DC.CursorMaxPos; + // Borders // - None ........Content..... Pad .....Content........ // - VOuter | Pad ..Content..... Pad .....Content.. Pad | // FIXME-TABLE: Not handled properly @@ -239,7 +246,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // - VOuter+VInner | Pad ..Content.. Pad | Pad ..Content.. Pad | const bool has_cell_padding_x = (flags & ImGuiTableFlags_BordersVOuter) != 0; - ImGuiWindow* inner_window = table->InnerWindow; table->CellPaddingX1 = has_cell_padding_x ? g.Style.CellPadding.x + 1.0f : 0.0f; table->CellPaddingX2 = has_cell_padding_x ? g.Style.CellPadding.x : 0.0f; table->CellPaddingY = g.Style.CellPadding.y; @@ -249,7 +255,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->CurrentRow = -1; table->RowBgColorCounter = 0; table->LastRowFlags = ImGuiTableRowFlags_None; - table->HostClipRect = inner_window->ClipRect; table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWith(table->HostClipRect); @@ -306,11 +311,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsSettingsRequestLoad) TableLoadSettings(table); - // Grab a copy of window fields we will modify - table->BackupSkipItems = inner_window->SkipItems; - table->BackupWorkRect = inner_window->WorkRect; - table->BackupCursorMaxPos = inner_window->DC.CursorMaxPos; - // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were misleading "out of table" output will be confusing to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. @@ -756,7 +756,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Don't decrement auto-fit counters until container window got a chance to submit its items - if (table->BackupSkipItems == false) + if (table->HostSkipItems == false) { column->AutoFitQueue >>= 1; column->CannotSkipItemsQueue >>= 1; @@ -896,8 +896,8 @@ void ImGui::EndTable() TableEndRow(table); // Finalize table height - inner_window->SkipItems = table->BackupSkipItems; - inner_window->DC.CursorMaxPos = table->BackupCursorMaxPos; + inner_window->SkipItems = table->HostSkipItems; + inner_window->DC.CursorMaxPos = table->HostCursorMaxPos; if (inner_window != outer_window) { table->OuterRect.Max.y = ImMax(table->OuterRect.Max.y, inner_window->Pos.y + inner_window->Size.y); @@ -970,8 +970,8 @@ void ImGui::EndTable() } // Layout in outer window - inner_window->WorkRect = table->BackupWorkRect; - inner_window->SkipItems = table->BackupSkipItems; + inner_window->WorkRect = table->HostWorkRect; + inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; outer_window->DC.ColumnsOffset.x = 0.0f; if (inner_window != outer_window) @@ -1594,7 +1594,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->IsClipped ? true : table->BackupSkipItems; + window->SkipItems = column->IsClipped ? true : table->HostSkipItems; if (table->Flags & ImGuiTableFlags_NoClipX) { table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); @@ -1828,15 +1828,23 @@ void ImGui::TableAutoHeaders() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); + const int columns_count = table->ColumnsCount; - TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight() + g.Style.CellPadding.y * 2.0f); + // Calculate row height (for the unlikely case that labels may be are multi-line) + float row_height = GetTextLineHeight(); + for (int column_n = 0; column_n < columns_count; column_n++) + if (TableGetColumnIsVisible(column_n)) + row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); + row_height += g.Style.CellPadding.y * 2.0f; + + // Open row + TableNextRow(ImGuiTableRowFlags_Headers, row_height); if (window->SkipItems) return; // This for loop is constructed to not make use of internal functions, // as this is intended to be a base template to copy and build from. int open_context_popup = INT_MAX; - const int columns_count = table->ColumnsCount; for (int column_n = 0; column_n < columns_count; column_n++) { if (!TableSetColumnIndex(column_n)) @@ -1873,7 +1881,7 @@ void ImGui::TableAutoHeaders() // FIXME-TABLE: This is not user-land code any more... // FIXME-TABLE: Need to explain why this is here! - window->SkipItems = table->BackupSkipItems; + window->SkipItems = table->HostSkipItems; // Allow opening popup from the right-most section after the last column // FIXME-TABLE: This is not user-land code any more... perhaps instead we should expose hovered column. @@ -1918,6 +1926,7 @@ void ImGui::TableAutoHeaders() // Emit a column header (text + optional sort order) // We cpu-clip text here so that all columns headers can be merged into a same draw call. // FIXME-TABLE: Should hold a selection state. +// FIXME-TABLE: Style confusion between CellPadding.y and FramePadding.y void ImGui::TableHeader(const char* label) { ImGuiContext& g = *GImGui; @@ -1931,19 +1940,21 @@ void ImGui::TableHeader(const char* label) const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; - float row_height = GetTextLineHeight(); - ImRect cell_r = TableGetCellRect(); - //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] - ImRect work_r = cell_r; - work_r.Min.x = window->DC.CursorPos.x; - work_r.Max.y = work_r.Min.y + row_height; - // Label if (label == NULL) label = ""; const char* label_end = FindRenderedTextEnd(label); ImVec2 label_size = CalcTextSize(label, label_end, true); ImVec2 label_pos = window->DC.CursorPos; + + // If we already got a row height, there's use that. + ImRect cell_r = TableGetCellRect(); + float label_height = ImMax(label_size.y, cell_r.GetHeight() - g.Style.CellPadding.y * 2.0f); + + //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + ImRect work_r = cell_r; + work_r.Min.x = window->DC.CursorPos.x; + work_r.Max.y = work_r.Min.y + label_height; float ellipsis_max = work_r.Max.x; // Selectable @@ -1954,7 +1965,7 @@ void ImGui::TableHeader(const char* label) // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceNo); - const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, row_height)); + const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, label_height)); const bool held = IsItemActive(); if (held) table->HeldHeaderColumn = (ImS8)column_n; @@ -2016,7 +2027,7 @@ void ImGui::TableHeader(const char* label) // Render clipped label // Clipping here ensure that in the majority of situations, all our header cells will be merged into a single draw call. //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); - RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + row_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + 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); // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. // FIXME-TABLE: Clarify policies of how label width and potential decorations (arrows) fit into auto-resize of the column @@ -2066,7 +2077,7 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click } // Return NULL if no sort specs. -// Return ->WantSort == true when the specs have changed since the last query. +// You can sort your data again when 'SpecsChanged == true'.It will be true with sorting specs have changed since last call, or the first time. const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui;