From 6182973bdeb7acb38c71cb64a58afc3b750a539a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 1 Oct 2020 14:03:25 +0200 Subject: [PATCH] Tables: (Breaking) Rename TableNextCell() to TableNextColumn(), made TableNextRow() NOT enter into first column. --- imgui.h | 30 ++++++++++++++-------- imgui_demo.cpp | 65 ++++++++++++++++++++++++++---------------------- imgui_tables.cpp | 26 +++++++++++-------- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/imgui.h b/imgui.h index beb4182f..c7516b01 100644 --- a/imgui.h +++ b/imgui.h @@ -653,7 +653,7 @@ namespace ImGui IMGUI_API bool IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0); // return true if the popup is open. // Tables - // [ALPHA API] API will evolve! (see: FIXME-TABLE) + // [ALPHA API] API may evolve! // - Full-featured replacement for old Columns API // - See Demo->Tables for details. // - See ImGuiTableFlags_ and ImGuiTableColumnsFlags_ enums for a description of available flags. @@ -662,28 +662,36 @@ namespace ImGui // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows // - 4. Optionally call TableHeadersRow() to submit a header row (names will be pulled from data submitted to TableSetupColumns) - // - 4. Populate contents - // - In most situations you can use TableNextRow() + TableSetColumnIndex() to start appending into a column. - // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, - // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). - // - Submit your content with regular ImGui function. + // - 5. Populate contents + // - In most situations you can use TableNextRow() + TableSetColumnIndex(xx) to start appending into a column. + // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, + // you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex(). + // TableNextColumn() will automatically wrap-around into the next row if needed. + // - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column! + // - Summary of possible call flow: + // ---------------------------------------------------------------------------------------------------------- + // TableNextRow() -> TableSetColumnIndex(0) -> Text("Hello 0") -> TableSetColumnIndex(1) -> Text("Hello 1") // OK + // TableNextRow() -> TableNextColumn() Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK + // TableNextColumn() Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK: TableNextColumn() automatically gets to next row! + // TableNextRow() Text("Hello 0") // Not OK! Missing TableSetColumnIndex() or TableNextColumn()! Text will not appear! + // ---------------------------------------------------------------------------------------------------------- // - 5. Call EndTable() #define IMGUI_HAS_TABLE 1 IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. - IMGUI_API bool TableNextCell(); // append into the next column (next column, or next row if currently in last column). Return true if column is visible. + IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true if column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. IMGUI_API int TableGetColumnIndex(); // return current column index. // Tables: Headers & Columns declaration - // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableHeadersRow() and context-menus. // - Use TableHeadersRow() to create a row and automatically submit a TableHeader() for each column. // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). - IMGUI_API void TableSetupScrollFreeze(int columns, int rows); + // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); + IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) // Tables: Miscellaneous functions @@ -694,13 +702,13 @@ namespace ImGui // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. - IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextColumn() and TableSetColumnIndex(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. - // Columns (Legacy API, prefer using Tables) + // Legacy Columns API (2020: prefer using Tables!) // - You can also use SameLine(pos_x) to mimic simplified columns. IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f95bbffe..bcca3c74 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3348,31 +3348,30 @@ static void ShowDemoWindowTables() // This essentially the same as above, except instead of using a for loop we call TableSetColumnIndex() manually. // Sometimes this makes more sense. - HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, manually."); + HelpMarker("Using TableNextRow() + calling TableNextColumn() _before_ each cell, manually."); if (ImGui::BeginTable("##table2", 3)) { for (int row = 0; row < 4; row++) { ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); + ImGui::TableNextColumn(); ImGui::Text("Row %d", row); - ImGui::TableSetColumnIndex(1); + ImGui::TableNextColumn(); ImGui::Text("Some contents"); - ImGui::TableSetColumnIndex(2); + ImGui::TableNextColumn(); ImGui::Text("123.456"); } ImGui::EndTable(); } - // Another subtle variant, we call TableNextCell() _before_ each cell. At the end of a row, TableNextCell() will create a new row. - // Note that we don't call TableNextRow() here! - // If we want to call TableNextRow(), then we don't need to call TableNextCell() for the first cell. - HelpMarker("Only using TableNextCell(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); + // Another subtle variant, we call TableNextColumn() _before_ each cell. At the end of a row, TableNextColumn() will create a new row. + // Note that we never TableNextRow() here! + HelpMarker("Only using TableNextColumn(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); if (ImGui::BeginTable("##table4", 3)) { for (int item = 0; item < 14; item++) { - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("Item %d", item); } ImGui::EndTable(); @@ -3690,7 +3689,7 @@ static void ShowDemoWindowTables() ImGui::TableNextRow(); for (int column = 0; column < 7; column++) { - // Both TableNextCell() and TableSetColumnIndex() return false when a column is not visible, which can be used for clipping. + // Both TableNextColumn() and TableSetColumnIndex() return false when a column is not visible, which can be used for clipping. if (!ImGui::TableSetColumnIndex(column)) continue; if (column == 0) @@ -3718,7 +3717,7 @@ static void ShowDemoWindowTables() for (int column = 0; column < column_count; column++) { // Make the UI compact because there are so many fields - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); @@ -3780,7 +3779,8 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("A1"); ImGui::TableHeadersRow(); - ImGui::TableNextRow(); ImGui::Text("A0 Cell 0"); + ImGui::TableNextColumn(); + ImGui::Text("A0 Cell 0"); { float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeightV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) @@ -3790,20 +3790,22 @@ static void ShowDemoWindowTables() ImGui::TableHeadersRow(); ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); ImGui::Text("B0 Cell 0"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("B0 Cell 1"); ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); ImGui::Text("B1 Cell 0"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("B1 Cell 1"); ImGui::EndTable(); } } - ImGui::TableNextCell(); ImGui::Text("A0 Cell 1"); - ImGui::TableNextRow(); ImGui::Text("A1 Cell 0"); - ImGui::TableNextCell(); ImGui::Text("A1 Cell 1"); + ImGui::TableNextColumn(); ImGui::Text("A0 Cell 1"); + ImGui::TableNextColumn(); ImGui::Text("A1 Cell 0"); + ImGui::TableNextColumn(); ImGui::Text("A1 Cell 1"); ImGui::EndTable(); } ImGui::TreePop(); @@ -3915,6 +3917,7 @@ static void ShowDemoWindowTables() { float min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); + ImGui::TableNextColumn(); ImGui::Text("min_row_height = %.2f", min_row_height); } ImGui::EndTable(); @@ -4003,13 +4006,14 @@ static void ShowDemoWindowTables() static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes) { ImGui::TableNextRow(); + ImGui::TableNextColumn(); const bool is_folder = (node->ChildCount > 0); if (is_folder) { bool open = ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextDisabled("--"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(node->Type); if (open) { @@ -4021,9 +4025,9 @@ static void ShowDemoWindowTables() else { ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("%d", node->Size); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(node->Type); } } @@ -4228,13 +4232,13 @@ static void ShowDemoWindowTables() MyItem* item = &items[row_n]; ImGui::PushID(item->ID); ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); + ImGui::TableNextColumn(); ImGui::Text("%04d", item->ID); - ImGui::TableSetColumnIndex(1); + ImGui::TableNextColumn(); ImGui::TextUnformatted(item->Name); - ImGui::TableSetColumnIndex(2); + ImGui::TableNextColumn(); ImGui::SmallButton("None"); - ImGui::TableSetColumnIndex(3); + ImGui::TableNextColumn(); ImGui::Text("%d", item->Quantity); ImGui::PopID(); } @@ -4431,6 +4435,7 @@ static void ShowDemoWindowTables() const bool item_is_selected = selection.contains(item->ID); ImGui::PushID(item->ID); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); + ImGui::TableNextColumn(); // For the demo purpose we can select among different type of items submitted in the first column char label[32]; @@ -4462,14 +4467,14 @@ static void ShowDemoWindowTables() } } - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(item->Name); // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity, // and we are currently sorting on the column showing the Quantity. // To avoid triggering a sort while holding the button, we only trigger it when the button has been released. // You will probably need a more advanced system in your code if you want to automatically sort when a specific entry changes. - if (ImGui::TableNextCell()) + if (ImGui::TableNextColumn()) { if (ImGui::SmallButton("Chop")) { item->Quantity += 1; } if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } @@ -4478,16 +4483,16 @@ static void ShowDemoWindowTables() if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } } - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("%d", item->Quantity); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); if (show_wrapped_text) ImGui::TextWrapped("Lorem ipsum dolor sit amet"); else ImGui::Text("Lorem ipsum dolor sit amet"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("1234"); ImGui::PopID(); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d8681d4b..df7d9dda 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -79,7 +79,7 @@ // | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction // | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) -// - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableHeadersRow() +// - TableNextRow() / TableNextColumn() user begin into the first row, also automatically called by TableHeadersRow() // | TableEndCell() - close existing cell if not the first time // | TableBeginCell() - enter into current cell // - [...] user emit contents @@ -345,7 +345,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } table->RefScale = new_ref_scale_unit; - // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. + // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. inner_window->SkipItems = true; @@ -988,7 +988,7 @@ void ImGui::EndTable() // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); - // If the user never got to call TableNextRow() or TableNextCell(), we call layout ourselves to ensure all our + // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. if (!table->IsLayoutLocked) TableUpdateLayout(table); @@ -1610,7 +1610,8 @@ void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) table->RowPosY2 += table->CellPaddingY * 2.0f; table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); - TableBeginCell(table, 0); + // Disable output until user calls TableNextColumn() + table->InnerWindow->SkipItems = true; } // [Internal] @@ -1654,7 +1655,8 @@ void ImGui::TableEndRow(ImGuiTable* table) IM_ASSERT(window == table->InnerWindow); IM_ASSERT(table->IsInsideRow); - TableEndCell(table); + if (table->CurrentColumn != -1) + TableEndCell(table); // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. @@ -1783,7 +1785,7 @@ void ImGui::TableEndRow(ImGuiTable* table) table->IsInsideRow = false; } -// [Internal] Called by TableNextCell()! +// [Internal] Called by TableNextColumn()! // This is called very frequently, so we need to be mindful of unnecessary overhead. // FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. void ImGui::TableBeginCell(ImGuiTable* table, int column_n) @@ -1824,7 +1826,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) } } -// [Internal] Called by TableNextRow()/TableNextCell()! +// [Internal] Called by TableNextRow()/TableNextColumn()! void ImGui::TableEndCell(ImGuiTable* table) { ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; @@ -1846,28 +1848,30 @@ void ImGui::TableEndCell(ImGuiTable* table) // Append into the next cell // FIXME-TABLE: Wrapping to next row should be optional? -bool ImGui::TableNextCell() +bool ImGui::TableNextColumn() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; if (!table) return false; - if (table->CurrentColumn != -1 && table->CurrentColumn + 1 < table->ColumnsCount) + if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) { - TableEndCell(table); + if (table->CurrentColumn != -1) + TableEndCell(table); TableBeginCell(table, table->CurrentColumn + 1); } else { TableNextRow(); + TableBeginCell(table, 0); } - int column_n = table->CurrentColumn; // FIXME-TABLE: Need to clarify if we want to allow IsItemHovered() here //g.CurrentWindow->DC.LastItemStatusFlags = (column_n == table->HoveredColumn) ? ImGuiItemStatusFlags_HoveredRect : ImGuiItemStatusFlags_None; // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. + int column_n = table->CurrentColumn; return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; }