diff --git a/imgui.h b/imgui.h index b3a70ae2..ebbfa771 100644 --- a/imgui.h +++ b/imgui.h @@ -157,6 +157,7 @@ typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling +typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect(), AddRectFilled() etc. typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build @@ -684,6 +685,7 @@ namespace ImGui 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 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 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. // Tables: Headers & Columns declaration // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu @@ -1036,7 +1038,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_MultiSortable = 1 << 4, // Allow sorting on multiple columns by holding Shift (sort_specs_count may be > 1). Call TableGetSortSpecs() to obtain sort specs. ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. // Decoration - ImGuiTableFlags_RowBg = 1 << 6, // Use ImGuiCol_TableRowBg and ImGuiCol_TableRowBgAlt colors behind each rows. + ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent to calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. ImGuiTableFlags_BordersOuterH = 1 << 8, // Draw horizontal borders at the top and bottom. ImGuiTableFlags_BordersInnerV = 1 << 9, // Draw vertical borders between columns. @@ -1109,6 +1111,25 @@ enum ImGuiTableRowFlags_ ImGuiTableRowFlags_Headers = 1 << 0 // Identify header row (set default background color + width of its contents accounted different for auto column width) }; +// Enum for ImGui::TableSetBgColor() +// Background colors are rendering in 3 layers: +// - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. +// - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. +// - Layer 2: draw with CellBg color if set. +// The purpose of the two row/columns layers is to let you decide if a background color changes should override or blend with the existing color. +// When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows. +// If you set the color of RowBg0 target, your color will override the existing RowBg0 color. +// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. +enum ImGuiTableBgTarget_ +{ + ImGuiTableBgTarget_None = 0, + ImGuiTableBgTarget_ColumnBg0 = 1, // FIXME-TABLE: Todo. Set column background color 0 (generally used for background + ImGuiTableBgTarget_ColumnBg1 = 2, // FIXME-TABLE: Todo. Set column background color 1 (generally used for selection marking) + ImGuiTableBgTarget_RowBg0 = 3, // Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) + ImGuiTableBgTarget_RowBg1 = 4, // Set row background color 1 (generally used for selection marking) + ImGuiTableBgTarget_CellBg = 5 // Set cell background color (top-most color) +}; + // Flags for ImGui::IsWindowFocused() enum ImGuiFocusedFlags_ { diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f38bf4a8..08005cbd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3902,14 +3902,11 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Row height")) { HelpMarker("You can pass a 'min_row_height' to TableNextRow().\n\nRows are padded with 'style.CellPadding.y' on top and bottom, so effectively the minimum row height will always be >= 'style.CellPadding.y * 2.0f'.\n\nWe cannot honor a _maximum_ row height as that would requires a unique clipping rectangle per row."); - if (ImGui::BeginTable("##2ways", 2, ImGuiTableFlags_Borders)) + if (ImGui::BeginTable("##Table", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerV)) { - float min_row_height = ImGui::GetFontSize() + ImGui::GetStyle().CellPadding.y * 2.0f; - ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); - ImGui::Text("min_row_height = %.2f", min_row_height); for (int row = 0; row < 10; row++) { - min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); + float min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); ImGui::Text("min_row_height = %.2f", min_row_height); } @@ -3918,6 +3915,61 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Background color")) + { + static ImGuiTableFlags table_flags = ImGuiTableFlags_RowBg; + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&table_flags, ImGuiTableFlags_Borders); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&table_flags, ImGuiTableFlags_RowBg); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style."); + + static int row_bg_type = 1; + static int row_bg_target = 1; + static int cell_bg_type = 1; + ImGui::Combo("row bg type", (int*)&row_bg_type, "None\0Red\0Gradient\0"); + ImGui::Combo("row bg target", (int*)&row_bg_target, "RowBg0\0RowBg1\0"); ImGui::SameLine(); HelpMarker("Target RowBg0 to override the alternating odd/even colors,\nTarget RowBg1 to blend with them."); + ImGui::Combo("cell bg type", (int*)&cell_bg_type, "None\0Blue\0"); ImGui::SameLine(); HelpMarker("We are colorizing cells to B1->C2 here."); + IM_ASSERT(row_bg_type >= 0 && row_bg_type <= 2); + IM_ASSERT(row_bg_target >= 0 && row_bg_target <= 1); + IM_ASSERT(cell_bg_type >= 0 && cell_bg_type <= 1); + + if (ImGui::BeginTable("##Table", 5, table_flags)) + { + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + + // Demonstrate setting a row background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBgX, ...)' + // We use a transparent color so we can see the one behind in case our target is RowBg1 and RowBg0 was already targeted by the ImGuiTableFlags_RowBg flag. + if (row_bg_type != 0) + { + ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient? + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color); + } + + // Fill cells + for (int column = 0; column < 5; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%c%c", 'A' + row, '0' + column); + + // Change background of Cells B1->C2 + // Demonstrate setting a cell background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ...)' + // (the CellBg color will be blended over the RowBg and ColumnBg colors) + // We can also pass a column number as a third parameter to TableSetBgColor() and do this outside the column loop. + if (row >= 1 && row <= 2 && column >= 1 && column <= 2 && cell_bg_type == 1) + { + ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 0.65f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Tree view")) diff --git a/imgui_internal.h b/imgui_internal.h index 4dc5f0f2..513b7f24 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1883,11 +1883,11 @@ struct ImGuiTabBar #ifdef IMGUI_HAS_TABLE -#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. #define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. #define IMGUI_TABLE_MAX_DRAW_CHANNELS (2 + 64 * 2) // See TableUpdateDrawChannels() -// [Internal] sizeof() ~ 100 +// [Internal] sizeof() ~ 104 // We use the terminology "Visible" to refer to a column that is not Hidden by user or settings. However it may still be out of view and clipped (see IsClipped). struct ImGuiTableColumn { @@ -1943,6 +1943,14 @@ struct ImGuiTableColumn } }; +// Transient cell data stored per row. +// sizeof() ~ 6 +struct ImGuiTableCellData +{ + ImU32 BgColor; // Actual color + ImS8 Column; // Column number +}; + struct ImGuiTable { ImGuiID ID; @@ -1950,6 +1958,7 @@ struct ImGuiTable ImVector RawData; ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) + ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. ImU64 VisibleMaskByIndex; // Column Index -> IsVisible map (== not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 VisibleMaskByDisplayOrder; // Column DisplayOrder -> IsVisible map ImU64 VisibleUnclippedMaskByIndex;// Visible and not Clipped, aka "actually visible" "not hidden by some scrolling" @@ -1970,7 +1979,7 @@ struct ImGuiTable ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ ImGuiTableRowFlags LastRowFlags : 16; int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper) - ImU32 RowBgColor; // Request for current row background color + ImU32 RowBgColor[2]; // Background color override for current row. ImU32 BorderColorStrong; ImU32 BorderColorLight; float BorderX1; @@ -2018,6 +2027,7 @@ struct ImGuiTable ImS8 FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) ImS8 FreezeColumnsRequest; // Requested frozen columns count ImS8 FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) + ImS8 RowCellDataCount; // Number of RowCellData[] entries in current row bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index cb7a1b34..5e785af3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -307,13 +307,15 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->RawData.Size == 0) { // Allocate single buffer for our arrays - ImSpanAllocator<2> span_allocator; + ImSpanAllocator<3> span_allocator; span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn)); span_allocator.ReserveBytes(1, columns_count * sizeof(ImS8)); + span_allocator.ReserveBytes(2, columns_count * sizeof(ImGuiTableCellData)); table->RawData.resize(span_allocator.GetArenaSizeInBytes()); span_allocator.SetArenaBasePtr(table->RawData.Data); span_allocator.GetSpan(0, &table->Columns); span_allocator.GetSpan(1, &table->DisplayOrderToIndex); + span_allocator.GetSpan(2, &table->RowCellData); for (int n = 0; n < columns_count; n++) { @@ -1609,7 +1611,8 @@ void ImGui::TableBeginRow(ImGuiTable* table) // New row table->CurrentRow++; table->CurrentColumn = -1; - table->RowBgColor = IM_COL32_DISABLE; + table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; + table->RowCellDataCount = 0; table->IsInsideRow = true; // Begin frozen rows @@ -1626,7 +1629,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. if (table->RowFlags & ImGuiTableRowFlags_Headers) { - table->RowBgColor = GetColorU32(ImGuiCol_TableHeaderBg); + TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg)); if (table->CurrentRow == 0) table->IsUsingHeaders = true; } @@ -1655,14 +1658,18 @@ void ImGui::TableEndRow(ImGuiTable* table) if (table->CurrentRow == 0) table->LastFirstRowHeight = bg_y2 - bg_y1; - if (table->CurrentRow >= 0 && bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y) + const bool is_visible = table->CurrentRow >= 0 && bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y; + if (is_visible) { // Decide of background color for the row - ImU32 bg_col = 0; - if (table->RowBgColor != IM_COL32_DISABLE) - bg_col = table->RowBgColor; + ImU32 bg_col0 = 0; + ImU32 bg_col1 = 0; + if (table->RowBgColor[0] != IM_COL32_DISABLE) + bg_col0 = table->RowBgColor[0]; else if (table->Flags & ImGuiTableFlags_RowBg) - bg_col = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + if (table->RowBgColor[1] != IM_COL32_DISABLE) + bg_col1 = table->RowBgColor[1]; // Decide of top border color ImU32 border_col = 0; @@ -1684,8 +1691,9 @@ void ImGui::TableEndRow(ImGuiTable* table) } } - const bool draw_stong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); - if (bg_col != 0 || border_col != 0 || draw_stong_bottom_border) + const bool draw_cell_bg_color = table->RowCellDataCount > 0; + const bool draw_strong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); + if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) { // In theory we could call SetWindowClipRectBeforeChannelChange() but since we know TableEndRow() is // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. @@ -1693,14 +1701,29 @@ void ImGui::TableEndRow(ImGuiTable* table) table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); } - // Draw background + // Draw row background // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle - if (bg_col) + if (bg_col0 || bg_col1) { - ImRect bg_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); - bg_rect.ClipWith(table->BackgroundClipRect); - if (bg_rect.Min.y < bg_rect.Max.y) - window->DrawList->AddRectFilledMultiColor(bg_rect.Min, bg_rect.Max, bg_col, bg_col, bg_col, bg_col); + ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); + row_rect.ClipWith(table->BackgroundClipRect); + if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0); + if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1); + } + + // Draw cell background color + if (draw_cell_bg_color) + { + ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCount - 1]; + for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) + { + ImGuiTableColumn* column = &table->Columns[cell_data->Column]; + ImRect cell_rect(column->MinX - table->CellSpacingX, bg_y1, column->MaxX, bg_y2); // FIXME-TABLE: Padding currently wrong until we finish the padding refactor + cell_rect.ClipWith(table->BackgroundClipRect); + window->DrawList->AddRectFilled(cell_rect.Min, cell_rect.Max, cell_data->BgColor); + } } // Draw top border @@ -1708,7 +1731,7 @@ void ImGui::TableEndRow(ImGuiTable* table) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size); // Draw bottom border at the row unfreezing mark (always strong) - if (draw_stong_bottom_border) + if (draw_strong_bottom_border) if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); } @@ -2305,6 +2328,46 @@ int ImGui::TableGetHoveredColumn() return (int)table->HoveredColumnBody; } +void ImGui::TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(bg_target != ImGuiTableBgTarget_None); + + if (color == IM_COL32_DISABLE) + color = 0; + + // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. + switch (bg_target) + { + case ImGuiTableBgTarget_CellBg: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + if (column_n == -1) + column_n = table->CurrentColumn; + if ((table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) == 0) + return; + ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCount++]; + cell_data->BgColor = color; + cell_data->Column = (ImS8)column_n; + break; + } + case ImGuiTableBgTarget_RowBg0: + case ImGuiTableBgTarget_RowBg1: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + IM_ASSERT(column_n == -1); + int bg_idx = (bg_target == ImGuiTableBgTarget_RowBg1) ? 1 : 0; + table->RowBgColor[bg_idx] = color; + break; + } + default: + IM_ASSERT(0); + } +} + void ImGui::TableSortSpecsSanitize(ImGuiTable* table) { IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable);