Tables: Calculating ideal total width, some renaming, comments. Clarify that inner_width is unused with ScrollX=0. Clip many comments to 120 columns.

This commit is contained in:
omar 2020-05-12 17:23:10 +02:00 committed by ocornut
parent b7ff85d9ad
commit 9f43aae226
4 changed files with 168 additions and 133 deletions

View File

@ -10515,7 +10515,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); }
else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; }
else if (rect_type == TRT_ColumnsContentHeadersUsed) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate else if (rect_type == TRT_ColumnsContentHeadersUsed) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate
else if (rect_type == TRT_ColumnsContentHeadersIdeal) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersDesired, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " else if (rect_type == TRT_ColumnsContentHeadersIdeal) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // "
else if (rect_type == TRT_ColumnsContentRowsFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthRowsFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " else if (rect_type == TRT_ColumnsContentRowsFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthRowsFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // "
else if (rect_type == TRT_ColumnsContentRowsUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->MinX + c->ContentWidthRowsUnfrozen, table->InnerClipRect.Max.y); } // " else if (rect_type == TRT_ColumnsContentRowsUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->MinX + c->ContentWidthRowsUnfrozen, table->InnerClipRect.Max.y); } // "
IM_ASSERT(0); IM_ASSERT(0);
@ -10573,7 +10573,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++)
{ {
ImGuiTable* table = g.Tables.GetByIndex(table_n); ImGuiTable* table = g.Tables.GetByIndex(table_n);
if (table->LastFrameActive < g.FrameCount - 1 || table->OuterWindow != g.NavWindow) if (table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow))
continue; continue;
BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name);

View File

@ -3680,8 +3680,8 @@ static void ShowDemoWindowTables()
ImGui::CheckboxFlags("_NoSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSortDescending); ImGui::CheckboxFlags("_NoSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSortDescending);
ImGui::CheckboxFlags("_PreferSortAscending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortAscending); ImGui::CheckboxFlags("_PreferSortAscending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortAscending);
ImGui::CheckboxFlags("_PreferSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortDescending); ImGui::CheckboxFlags("_PreferSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortDescending);
ImGui::CheckboxFlags("_IndentEnable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentEnable); ImGui::CheckboxFlags("_IndentEnable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentEnable); ImGui::SameLine(); HelpMarker("Default for column 0");
ImGui::CheckboxFlags("_IndentDisable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentDisable); ImGui::CheckboxFlags("_IndentDisable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentDisable); ImGui::SameLine(); HelpMarker("Default for column >0");
ImGui::PopID(); ImGui::PopID();
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
} }
@ -4089,7 +4089,6 @@ static void ShowDemoWindowTables()
static int items_count = IM_ARRAYSIZE(template_items_names); static int items_count = IM_ARRAYSIZE(template_items_names);
static ImVec2 outer_size_value = ImVec2(0, 250); static ImVec2 outer_size_value = ImVec2(0, 250);
static float row_min_height = 0.0f; // Auto static float row_min_height = 0.0f; // Auto
static float inner_width_without_scroll = 0.0f; // Fill
static float inner_width_with_scroll = 0.0f; // Auto-extend static float inner_width_with_scroll = 0.0f; // Auto-extend
static bool outer_size_enabled = true; static bool outer_size_enabled = true;
static bool lock_first_column_visibility = false; static bool lock_first_column_visibility = false;
@ -4171,10 +4170,7 @@ static void ShowDemoWindowTables()
// From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling.
// To facilitate experimentation we expose two values and will select the right one depending on active flags. // To facilitate experimentation we expose two values and will select the right one depending on active flags.
if (flags & ImGuiTableFlags_ScrollX) ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX);
ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX);
else
ImGui::DragFloat("inner_width (when ScrollX inactive)", &inner_width_without_scroll, 1.0f, 0.0f, FLT_MAX);
ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX);
ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); ImGui::SameLine(); HelpMarker("Specify height of the Selectable item.");
ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000); ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000);
@ -4213,7 +4209,7 @@ static void ShowDemoWindowTables()
ImVec2 table_scroll_cur, table_scroll_max; // For debug display ImVec2 table_scroll_cur, table_scroll_max; // For debug display
const ImDrawList* table_draw_list = NULL; // " const ImDrawList* table_draw_list = NULL; // "
const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : inner_width_without_scroll; const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : 0.0f;
if (ImGui::BeginTable("##table", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) if (ImGui::BeginTable("##table", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use))
{ {
// Declare columns // Declare columns

View File

@ -1905,11 +1905,11 @@ struct ImGuiTableColumn
float ContentMaxPosRowsFrozen; // Submitted contents absolute maximum position, from which we can infer width. float ContentMaxPosRowsFrozen; // Submitted contents absolute maximum position, from which we can infer width.
float ContentMaxPosRowsUnfrozen; // (kept as float because we need to manipulate those between each cell change) float ContentMaxPosRowsUnfrozen; // (kept as float because we need to manipulate those between each cell change)
float ContentMaxPosHeadersUsed; float ContentMaxPosHeadersUsed;
float ContentMaxPosHeadersDesired; float ContentMaxPosHeadersIdeal;
ImS16 ContentWidthRowsFrozen; // Contents width. Because row freezing is not correlated with headers/not-headers we need all 4 variants (ImDrawCmd merging uses different data than alignment code). ImS16 ContentWidthRowsFrozen; // Contents width. Because row freezing is not correlated with headers/not-headers we need all 4 variants (ImDrawCmd merging uses different data than alignment code).
ImS16 ContentWidthRowsUnfrozen; // (encoded as ImS16 because we actually rarely use those width) ImS16 ContentWidthRowsUnfrozen; // (encoded as ImS16 because we actually rarely use those width)
ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls
ImS16 ContentWidthHeadersDesired; ImS16 ContentWidthHeadersIdeal;
ImS16 NameOffset; // Offset into parent ColumnsNames[] ImS16 NameOffset; // Offset into parent ColumnsNames[]
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 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 IsActiveNextFrame; bool IsActiveNextFrame;
@ -1982,7 +1982,8 @@ struct ImGuiTable
float LastOuterHeight; // Outer height from last frame float LastOuterHeight; // Outer height from last frame
float LastFirstRowHeight; // Height of first row from last frame float LastFirstRowHeight; // Height of first row from last frame
float ColumnsTotalWidth; float ColumnsTotalWidth;
float InnerWidth; float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details.
float IdealTotalWidth; // Sum of ideal column width for nothing to be clipped
float ResizedColumnNextWidth; float ResizedColumnNextWidth;
ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable().
ImRect WorkRect; ImRect WorkRect;
@ -2264,14 +2265,14 @@ namespace ImGui
IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n);
IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API void TableEndCell(ImGuiTable* table);
IMGUI_API ImRect TableGetCellRect(); IMGUI_API ImRect TableGetCellRect();
IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_n); IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n);
IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); 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 TableSetColumnAutofit(ImGuiTable* table, int column_n);
IMGUI_API void PushTableBackground(); IMGUI_API void PushTableBackground();
IMGUI_API void PopTableBackground(); IMGUI_API void PopTableBackground();
IMGUI_API void TableLoadSettings(ImGuiTable* table); IMGUI_API void TableLoadSettings(ImGuiTable* table);
IMGUI_API void TableSaveSettings(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table);
IMGUI_API ImGuiTableSettings* TableFindSettings(ImGuiTable* table); IMGUI_API ImGuiTableSettings* TableFindSettings(const ImGuiTable* table);
IMGUI_API void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); IMGUI_API void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
IMGUI_API void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); IMGUI_API void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
IMGUI_API void TableSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); IMGUI_API void TableSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf);

View File

@ -109,8 +109,8 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags)
if (flags & ImGuiTableFlags_Resizable) if (flags & ImGuiTableFlags_Resizable)
flags |= ImGuiTableFlags_BordersVInner; flags |= ImGuiTableFlags_BordersVInner;
// Adjust flags: disable top rows freezing if there's no scrolling // Adjust flags: disable top rows freezing if there's no scrolling.
// In theory we could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. // We could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos.
if ((flags & ImGuiTableFlags_ScrollX) == 0) if ((flags & ImGuiTableFlags_ScrollX) == 0)
flags &= ~ImGuiTableFlags_ScrollFreezeColumnsMask_; flags &= ~ImGuiTableFlags_ScrollFreezeColumnsMask_;
if ((flags & ImGuiTableFlags_ScrollY) == 0) if ((flags & ImGuiTableFlags_ScrollY) == 0)
@ -120,7 +120,8 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags)
if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0)
flags &= ~ImGuiTableFlags_NoHostExtendY; flags &= ~ImGuiTableFlags_NoHostExtendY;
// Adjust flags: we don't support NoClipX with (FreezeColumns > 0), we could with some work but it doesn't appear to be worth the effort // Adjust flags: we don't support NoClipX with (FreezeColumns > 0)
// We could with some work but it doesn't appear to be worth the effort.
if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_)
flags &= ~ImGuiTableFlags_NoClipX; flags &= ~ImGuiTableFlags_NoClipX;
@ -133,28 +134,32 @@ ImGuiTable* ImGui::FindTableByID(ImGuiID id)
return g.Tables.GetByKey(id); return g.Tables.GetByKey(id);
} }
// About 'outer_size': // (Read carefully because this is subtle but it does make sense!)
// The meaning of outer_size needs to differ slightly depending of if we are using ScrollX/ScrollY flags. // About 'outer_size', its meaning needs to differ slightly depending of if we are using ScrollX/ScrollY flags:
// With ScrollX/ScrollY: using a child window for scrolling: // X:
// - outer_size.x < 0.0f -> right align from window/work-rect maximum x edge.
// - outer_size.x = 0.0f -> auto enlarge, use all available space.
// - outer_size.x > 0.0f -> fixed width
// Y with ScrollX/ScrollY: using a child window for scrolling:
// - outer_size.y < 0.0f -> bottom align // - outer_size.y < 0.0f -> bottom align
// - outer_size.y = 0.0f -> bottom align: consistent with BeginChild(), best to preserve (0,0) default arg // - outer_size.y = 0.0f -> bottom align, consistent with BeginChild(). not recommended unless table is last item in parent window.
// - outer_size.y > 0.0f -> fixed child height // - outer_size.y > 0.0f -> fixed child height. recommended when using Scrolling on any axis.
// Without scrolling, we output table directly in parent window: // Y without scrolling, we output table directly in parent window:
// - outer_size.y < 0.0f -> bottom align (will auto extend, unless NoHostExtendV is set) // - outer_size.y < 0.0f -> bottom align (will auto extend, unless NoHostExtendV is set)
// - outer_size.y = 0.0f -> zero minimum height (will auto extend, unless NoHostExtendV is set) // - outer_size.y = 0.0f -> zero minimum height (will auto extend, unless NoHostExtendV is set)
// - outer_size.y > 0.0f -> minimum height (will auto extend, unless NoHostExtendV is set) // - outer_size.y > 0.0f -> minimum height (will auto extend, unless NoHostExtendV is set)
// About: 'inner_width': // About 'inner_width':
// With ScrollX: // With ScrollX:
// - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird // - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird
// - inner_width = 0.0f -> auto enlarge: *only* fixed size columns, which will take space they need (proportional columns becomes fixed columns) <-- desired default :( // - inner_width = 0.0f -> fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes Fixed columns.
// - inner_width > 0.0f -> fit in known width: fixed column take space they need if possible (otherwise shrink down), proportional columns share remaining space. // - inner_width > 0.0f -> override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise shrink down), Stretch columns share remaining space!
// Without ScrollX: // Without ScrollX:
// - inner_width < 0.0f -> fit in known width (right align from outer_size.x) <-- desired default // - inner_width -> *ignored*
// - inner_width = 0.0f -> auto enlarge: will emit contents size in parent window // Details:
// - inner_width > 0.0f -> fit in known width (bypass outer_size.x, permitted but not useful, should instead alter outer_width) // - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept
// FIXME-TABLE: This is currently not very useful. // of "available space" doesn't make sense.
// FIXME-TABLE: Replace enlarge vs fixed width by a flag. // - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding
// Even if not really useful, we allow 'inner_scroll_width < outer_size.x' for consistency and to facilitate understanding of what the value does. // of what the value does.
bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width)
{ {
ImGuiID id = GetID(str_id); ImGuiID id = GetID(str_id);
@ -213,15 +218,17 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
if (use_child_window) if (use_child_window)
{ {
// Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent
// (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing)
ImVec2 override_content_size(FLT_MAX, FLT_MAX); ImVec2 override_content_size(FLT_MAX, FLT_MAX);
if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY))
override_content_size.y = FLT_MIN; override_content_size.y = FLT_MIN;
// Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and never lead to any scrolling) // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and
// We don't handle inner_width < 0.0f, we could potentially use it to right-align based on the right side of the child window work rect, // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align
// which would require knowing ahead if we are going to have decoration taking horizontal spaces (typically a vertical scrollbar). // based on the right side of the child window work rect, which would require knowing ahead if we are going to
if (inner_width != 0.0f) // have decoration taking horizontal spaces (typically a vertical scrollbar).
if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f)
override_content_size.x = inner_width; override_content_size.x = inner_width;
if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX)
@ -322,7 +329,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
TableLoadSettings(table); TableLoadSettings(table);
// Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. // 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. // 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. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option.
inner_window->SkipItems = true; inner_window->SkipItems = true;
@ -373,7 +380,8 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table)
table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImS8)reorder_dir; table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImS8)reorder_dir;
IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir);
// Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], rebuild the later from the former. // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[],
// rebuild the later from the former.
for (int column_n = 0; column_n < table->ColumnsCount; column_n++) for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n;
table->ReorderColumnDir = 0; table->ReorderColumnDir = 0;
@ -452,10 +460,13 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table)
void ImGui::TableUpdateDrawChannels(ImGuiTable* table) void ImGui::TableUpdateDrawChannels(ImGuiTable* table)
{ {
// Allocate draw channels. // Allocate draw channels.
// - We allocate them following the storage order instead of the display order so reordering columns won't needlessly increase overall dormant memory cost. // - We allocate them following storage order instead of display order so reordering columns won't needlessly
// - We isolate headers draw commands in their own channels instead of just altering clip rects. This is in order to facilitate merging of draw commands. // increase overall dormant memory cost.
// - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of draw channels. // - We isolate headers draw commands in their own channels instead of just altering clip rects.
// - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. // This is in order to facilitate merging of draw commands.
// - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels.
// - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other
// channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged.
// Draw channel allocation (before merging): // Draw channel allocation (before merging):
// - NoClip --> 1+1 channels: background + foreground (same clip rect == 1 draw call) // - NoClip --> 1+1 channels: background + foreground (same clip rect == 1 draw call)
// - Clip --> 1+N channels // - Clip --> 1+N channels
@ -536,21 +547,24 @@ static float TableGetMinColumnWidth()
// Layout columns for the frame // Layout columns for the frame
// Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first.
// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAlwaysAutoResize columns, // FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAlwaysAutoResize
// increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution for WidthAlwaysAutoResize columns? // columns, increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution
// for WidthAlwaysAutoResize columns?
void ImGui::TableUpdateLayout(ImGuiTable* table) void ImGui::TableUpdateLayout(ImGuiTable* table)
{ {
IM_ASSERT(table->IsLayoutLocked == false); IM_ASSERT(table->IsLayoutLocked == false);
// Compute offset, clip rect for the frame // Compute offset, clip rect for the frame
// (can't make auto padding larger than what WorkRect knows about so right-alignment matches)
const ImRect work_rect = table->WorkRect; const ImRect work_rect = table->WorkRect;
const float padding_auto_x = table->CellPaddingX2; // Can't make auto padding larger than what WorkRect knows about so right-alignment matches. const float padding_auto_x = table->CellPaddingX2;
const float min_column_width = TableGetMinColumnWidth(); const float min_column_width = TableGetMinColumnWidth();
int count_fixed = 0; int count_fixed = 0;
float width_fixed = 0.0f; float width_fixed = 0.0f;
float total_weights = 0.0f; float total_weights = 0.0f;
table->LeftMostStretchedColumnDisplayOrder = -1; table->LeftMostStretchedColumnDisplayOrder = -1;
table->IdealTotalWidth = 0.0f;
for (int order_n = 0; order_n < table->ColumnsCount; order_n++) for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
{ {
if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n)))
@ -569,6 +583,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
if (table->Flags & ImGuiTableFlags_Sortable) if (table->Flags & ImGuiTableFlags_Sortable)
TableFixColumnSortDirection(column); TableFixColumnSortDirection(column);
// Calculate "ideal" column width for nothing to be clipped.
// Combine width from regular rows + width from headers unless requested not to.
const float column_content_width_rows = (float)ImMax(column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen);
const float column_content_width_headers = (float)column->ContentWidthHeadersIdeal;
float column_width_ideal = column_content_width_rows;
if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth))
column_width_ideal = ImMax(column_width_ideal, column_content_width_headers);
column_width_ideal = ImMax(column_width_ideal + padding_auto_x, min_column_width);
table->IdealTotalWidth += column_width_ideal;
if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed))
{ {
// Latch initial size for fixed columns // Latch initial size for fixed columns
@ -576,14 +600,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
const bool init_size = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); const bool init_size = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize);
if (init_size) if (init_size)
{ {
// Combine width from regular rows + width from headers unless requested not to column->WidthRequested = column_width_ideal;
float width_request = (float)ImMax(column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen);
if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth))
width_request = ImMax(width_request, (float)column->ContentWidthHeadersDesired);
column->WidthRequested = ImMax(width_request + padding_auto_x, min_column_width);
// FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets
// 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) // (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. // This is merely making the side-effect less extreme, but doesn't properly fixes it.
if (column->AutoFitQueue > 0x01 && table->IsInitializing) if (column->AutoFitQueue > 0x01 && table->IsInitializing)
column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f);
@ -606,7 +627,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible
const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1); const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1);
float width_avail; float width_avail;
if ((table->Flags & ImGuiTableFlags_ScrollX) && (table->InnerWidth == 0.0f)) if ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f)
width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f;
else else
width_avail = work_rect.GetWidth() - width_spacings - 1.0f; width_avail = work_rect.GetWidth() - width_spacings - 1.0f;
@ -630,15 +651,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
column->WidthRequested = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); column->WidthRequested = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f);
width_remaining_for_stretched_columns -= column->WidthRequested; width_remaining_for_stretched_columns -= column->WidthRequested;
// [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing to left-side of fixed column // [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing
// We also need to copy the NoResize flag.. // to left-side of fixed column. We also need to copy the NoResize flag..
if (column->NextActiveColumn != -1) if (column->NextActiveColumn != -1)
if (ImGuiTableColumn* next_column = &table->Columns[column->NextActiveColumn]) if (ImGuiTableColumn* next_column = &table->Columns[column->NextActiveColumn])
if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed) if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed)
column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_); column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_);
} }
// [Resize Rule 1] The right-most active column is not resizable if there is at least one Stretch column (see comments in TableResizeColumn().) // [Resize Rule 1] The right-most active column is not resizable if there is at least one Stretch column
// (see comments in TableResizeColumn().)
if (column->NextActiveColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1) if (column->NextActiveColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1)
column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; column->Flags |= ImGuiTableColumnFlags_NoDirectResize_;
@ -675,8 +697,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
else else
#endif #endif
// Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column) // Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column).
// Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending where the mouse cursor is and/or relative weights. // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending where
// the mouse cursor is and/or relative weights.
// FIXME-TABLE: May be simpler to store floating width and floor final positions only // FIXME-TABLE: May be simpler to store floating width and floor final positions only
// FIXME-TABLE: Make it optional? User might prefer to preserve pixel perfect same size? // FIXME-TABLE: Make it optional? User might prefer to preserve pixel perfect same size?
if (width_remaining_for_stretched_columns >= 1.0f) if (width_remaining_for_stretched_columns >= 1.0f)
@ -707,7 +730,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n)))
{ {
// Hidden column: clear a few fields and we are done with it for the remainder of the function. // Hidden column: clear a few fields and we are done with it for the remainder of the function.
// We set a zero-width clip rect however we pay attention to set Min.y/Max.y properly to not interfere with the clipper. // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper.
column->MinX = column->MaxX = offset_x; column->MinX = column->MaxX = offset_x;
column->StartXRows = column->StartXHeaders = offset_x; column->StartXRows = column->StartXHeaders = offset_x;
column->WidthGiven = 0.0f; column->WidthGiven = 0.0f;
@ -729,8 +752,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
} }
else else
{ {
// If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make
// Because of this we also know that all of the columns will always fit in table->WorkRect and therefore in table->InnerRect (because ScrollX is off) // sure they are all visible. Because of this we also know that all of the columns will always fit in
// table->WorkRect and therefore in table->InnerRect (because ScrollX is off)
if (!(table->Flags & ImGuiTableFlags_NoKeepColumnsVisible)) if (!(table->Flags & ImGuiTableFlags_NoKeepColumnsVisible))
max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width;
} }
@ -760,8 +784,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1;
// Alignment // Alignment
// FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in
// (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.) // 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) //if (column->Flags & ImGuiTableColumnFlags_AlignRight)
// column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen); // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen);
//else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter)
@ -770,7 +795,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// Reset content width variables // Reset content width variables
const float initial_max_pos_x = column->MinX + table->CellPaddingX1; const float initial_max_pos_x = column->MinX + table->CellPaddingX1;
column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x;
column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersIdeal = initial_max_pos_x;
} }
// Don't decrement auto-fit counters until container window got a chance to submit its items // Don't decrement auto-fit counters until container window got a chance to submit its items
@ -787,7 +812,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
active_n++; active_n++;
} }
// Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either because of using _WidthAlwaysAutoResize/_WidthStretch) // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag,
// either because of using _WidthAlwaysAutoResize/_WidthStretch).
// This will hide the resizing option from the context menu. // This will hide the resizing option from the context menu.
if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable))
table->Flags &= ~ImGuiTableFlags_Resizable; table->Flags &= ~ImGuiTableFlags_Resizable;
@ -828,15 +854,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// Process interaction on resizing borders. Actual size change will be applied in EndTable() // Process interaction on resizing borders. Actual size change will be applied in EndTable()
// - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise // - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise
// - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets overlapping the same area. // - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets
// overlapping the same area.
void ImGui::TableUpdateBorders(ImGuiTable* table) void ImGui::TableUpdateBorders(ImGuiTable* table)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable);
// At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and use // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and
// the final height from last frame. Because this is only affecting _interaction_ with columns, it is not really problematic. // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not
// (whereas the actual visual will be displayed in EndTable() and using the current frame height) // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height).
// Actual columns highlight/render will be performed in EndTable() and not be affected. // Actual columns highlight/render will be performed in EndTable() and not be affected.
const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersVFullHeight); const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersVFullHeight);
const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS;
@ -895,8 +922,8 @@ void ImGui::EndTable()
ImGuiTable* table = g.CurrentTable; ImGuiTable* table = g.CurrentTable;
IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!");
// This assert would be very useful to catch a common error... unfortunately it would probably trigger in some cases, // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some
// and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) // 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?"); //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 TableNextCell(), we call layout ourselves to ensure all our
@ -942,7 +969,7 @@ void ImGui::EndTable()
column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows);
column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows);
column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers);
column->ContentWidthHeadersDesired = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersDesired - ref_x_headers); column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers);
if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) if (table->ActiveMaskByIndex & ((ImU64)1 << column_n))
max_pos_x = ImMax(max_pos_x, column->MaxX); max_pos_x = ImMax(max_pos_x, column->MaxX);
@ -1084,9 +1111,9 @@ void ImGui::TableDrawBorders(ImGuiTable* table)
if (table->Flags & ImGuiTableFlags_BordersOuter) if (table->Flags & ImGuiTableFlags_BordersOuter)
{ {
// Display outer border offset by 1 which is a simple way to display it without adding an extra draw call // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call
// (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their parent. // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their
// In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part of it in inner window, // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part
// and the part that's over scrollbars in the outer window..) // of it in inner window, and the part that's over scrollbars in the outer window..)
// Either solution currently won't allow us to use a larger border size: the border would clipped. // Either solution currently won't allow us to use a larger border size: the border would clipped.
ImRect outer_border = table->OuterRect; ImRect outer_border = table->OuterRect;
const ImU32 outer_col = table->BorderColorStrong; const ImU32 outer_col = table->BorderColorStrong;
@ -1193,8 +1220,8 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f
if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed)
{ {
// [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure
// ensure that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others. // that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others.
if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed))
if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->DisplayOrder) if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->DisplayOrder)
{ {
@ -1240,12 +1267,15 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f
// Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled).
// When the contents of a column didn't stray off its limit, we move its channels into the corresponding group // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group
// based on its position (within frozen rows/columns groups or not). // based on its position (within frozen rows/columns groups or not).
// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be merged by the DrawSplitter.Merge() call. // At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be
// merged by the DrawSplitter.Merge() call.
// //
// Column channels will not be merged into one of the 1-4 groups in the following cases: // Column channels will not be merged into one of the 1-4 groups in the following cases:
// - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value).
// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds matches, by e.g. calling SetCursorScreenPos(). // Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds
// - The channel uses more than one draw command itself. We drop all our merging stuff here.. we could do better but it's going to be rare. // matches, by e.g. calling SetCursorScreenPos().
// - The channel uses more than one draw command itself. We drop all our merging stuff here.. we could do better
// but it's going to be rare.
// //
// This function is particularly tricky to understand.. take a breath. // This function is particularly tricky to understand.. take a breath.
void ImGui::TableDrawMergeChannels(ImGuiTable* table) void ImGui::TableDrawMergeChannels(ImGuiTable* table)
@ -1308,13 +1338,13 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table)
merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect);
merge_group_mask |= (1 << merge_group_dst_n); merge_group_mask |= (1 << merge_group_dst_n);
// If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command with // If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command
// the existing outer window command. But we can only do so if our columns all fit within the expected clip rect, // with the existing outer window command. But we can only do so if our columns all fit within the expected
// otherwise clipping will be incorrect when ScrollX is disabled. // clip rect, otherwise clipping will be incorrect when ScrollX is disabled.
// FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column don't fit within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column don't fit within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect
// 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to fix... // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to
// cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." // fix... cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV."
// 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary... // 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary...
#if 0 #if 0
if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x)
@ -1322,7 +1352,8 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table)
#endif #endif
} }
// Invalidate current draw channel (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging) // Invalidate current draw channel
// (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging)
column->DrawChannelCurrent = -1; column->DrawChannelCurrent = -1;
} }
@ -1402,8 +1433,9 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags,
ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount];
table->DeclColumnsCount++; table->DeclColumnsCount++;
// When passing a width automatically enforce WidthFixed policy (vs TableFixColumnFlags would default to WidthAlwaysAutoResize) // When passing a width automatically enforce WidthFixed policy
// (we write down to FlagsIn which is a little misleading, another solution would be to pass init_width_or_weight to TableFixColumnFlags) // (vs TableFixColumnFlags would default to WidthAlwaysAutoResize)
// (we write to FlagsIn which is a little misleading, another solution would be to pass init_width_or_weight to TableFixColumnFlags)
if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0)
if ((table->Flags & ImGuiTableFlags_SizingPolicyFixedX) && (init_width_or_weight > 0.0f)) if ((table->Flags & ImGuiTableFlags_SizingPolicyFixedX) && (init_width_or_weight > 0.0f))
flags |= ImGuiTableColumnFlags_WidthFixed; flags |= ImGuiTableColumnFlags_WidthFixed;
@ -1521,8 +1553,8 @@ void ImGui::TableEndRow(ImGuiTable* table)
TableEndCell(table); TableEndCell(table);
// Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is
// However it is likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding.
window->DC.CursorPos.y = table->RowPosY2; window->DC.CursorPos.y = table->RowPosY2;
// Row background fill // Row background fill
@ -1588,7 +1620,8 @@ void ImGui::TableEndRow(ImGuiTable* table)
window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong); window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong);
// End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle)
// We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and get the new cursor position. // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and
// get the new cursor position.
if (unfreeze_rows) if (unfreeze_rows)
{ {
IM_ASSERT(table->IsFreezeRowsPassed == false); IM_ASSERT(table->IsFreezeRowsPassed == false);
@ -1767,16 +1800,16 @@ ImRect ImGui::TableGetCellRect()
return ImRect(column->MinX, table->RowPosY1, column->MaxX, table->RowPosY2); return ImRect(column->MinX, table->RowPosY1, column->MaxX, table->RowPosY2);
} }
const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_n) const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n)
{ {
ImGuiTableColumn* column = &table->Columns[column_n]; const ImGuiTableColumn* column = &table->Columns[column_n];
if (column->NameOffset == -1) if (column->NameOffset == -1)
return NULL; return NULL;
return &table->ColumnsNames.Buf[column->NameOffset]; return &table->ColumnsNames.Buf[column->NameOffset];
} }
// Return the resizing ID for the right-side of the given column. // Return the resizing ID for the right-side of the given column.
ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no) ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no)
{ {
IM_ASSERT(column_n < table->ColumnsCount); IM_ASSERT(column_n < table->ColumnsCount);
ImGuiID id = table->ID + (instance_no * table->ColumnsCount) + column_n; ImGuiID id = table->ID + (instance_no * table->ColumnsCount) + column_n;
@ -1880,8 +1913,8 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n)
} }
// This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). // This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn().
// The intent is that advanced users willing to create customized headers would not need to use this helper and may create their own. // The intent is that advanced users willing to create customized headers would not need to use this helper and may
// However presently this function uses too many internal structures/calls. // create their own. However presently this function uses too many internal structures/calls.
void ImGui::TableAutoHeaders() void ImGui::TableAutoHeaders()
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
@ -1964,7 +1997,7 @@ void ImGui::TableAutoHeaders()
window->DC.CursorPos.y -= g.Style.ItemSpacing.y; window->DC.CursorPos.y -= g.Style.ItemSpacing.y;
window->DC.CursorMaxPos = backup_cursor_max_pos; // Don't feed back into the width of the Header row window->DC.CursorMaxPos = backup_cursor_max_pos; // Don't feed back into the width of the Header row
// We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden.
if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
open_context_popup = -1; open_context_popup = -1;
} }
@ -2024,7 +2057,8 @@ void ImGui::TableHeader(const char* label)
// FIXME-TABLE: Fix when padding are disabled. // FIXME-TABLE: Fix when padding are disabled.
//window->DC.CursorPos.x = column->MinX + table->CellPadding.x; //window->DC.CursorPos.x = column->MinX + table->CellPadding.x;
// 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...) // 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->InstanceCurrent); const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent);
const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld | ImGuiSelectableFlags_DontClosePopups, ImVec2(0.0f, label_height)); const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld | ImGuiSelectableFlags_DontClosePopups, ImVec2(0.0f, label_height));
const bool held = IsItemActive(); const bool held = IsItemActive();
@ -2091,16 +2125,17 @@ void ImGui::TableHeader(const char* label)
TableSortSpecsClickColumn(table, column, g.IO.KeyShift); TableSortSpecsClickColumn(table, column, g.IO.KeyShift);
} }
// Render clipped label // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will
// Clipping here ensure that in the majority of situations, all our header cells will be merged into a single draw call. // be merged into a single draw call.
//window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE);
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); 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. // 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 // FIXME-TABLE: Clarify policies of how label width and potential decorations (arrows) fit into auto-resize of the column
float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow;
column->ContentMaxPosHeadersUsed = ImMax(column->ContentMaxPosHeadersUsed, work_r.Max.x);// ImMin(max_pos_x, work_r.Max.x)); column->ContentMaxPosHeadersUsed = ImMax(column->ContentMaxPosHeadersUsed, work_r.Max.x);// ImMin(max_pos_x, work_r.Max.x));
column->ContentMaxPosHeadersDesired = ImMax(column->ContentMaxPosHeadersDesired, max_pos_x); column->ContentMaxPosHeadersIdeal = ImMax(column->ContentMaxPosHeadersIdeal, max_pos_x);
PopID(); PopID();
} }
@ -2144,7 +2179,8 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click
} }
// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) // Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set)
// 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. // 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.
// Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()!
const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs()
{ {
@ -2292,7 +2328,7 @@ static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id)
return NULL; return NULL;
} }
ImGuiTableSettings* ImGui::TableFindSettings(ImGuiTable* table) ImGuiTableSettings* ImGui::TableFindSettings(const ImGuiTable* table)
{ {
if (table->SettingsOffset == -1) if (table->SettingsOffset == -1)
return NULL; return NULL;
@ -2438,7 +2474,8 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan
if (settings->ID == 0) // Skip ditched settings if (settings->ID == 0) // Skip ditched settings
continue; continue;
// TableSaveSettings() may clear some of those flags when we establish that the data can be stripped (e.g. Order was unchanged) // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped
// (e.g. Order was unchanged)
const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0;
const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0;
const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0;
@ -2473,6 +2510,7 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
#ifndef IMGUI_DISABLE_METRICS_WINDOW #ifndef IMGUI_DISABLE_METRICS_WINDOW
void ImGui::DebugNodeTable(ImGuiTable* table) void ImGui::DebugNodeTable(ImGuiTable* table)
{ {
char buf[256]; char buf[256];
@ -2482,33 +2520,33 @@ void ImGui::DebugNodeTable(ImGuiTable* table)
bool open = TreeNode(table, "%s", buf); bool open = TreeNode(table, "%s", buf);
if (IsItemHovered()) if (IsItemHovered())
GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255));
if (open) if (!open)
return;
BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s, IdealWidth: %.1f", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "", table->IdealTotalWidth);
for (int n = 0; n < table->ColumnsCount; n++)
{ {
for (int n = 0; n < table->ColumnsCount; n++) ImGuiTableColumn* column = &table->Columns[n];
{ const char* name = TableGetColumnName(table, n);
ImGuiTableColumn* column = &table->Columns[n]; BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n"
const char* name = TableGetColumnName(table, n); "Active: %d, Clipped: %d, DrawChannels: %d,%d\n"
BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n"
"Active: %d, Clipped: %d, DrawChannels: %d,%d\n" "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Ideal %d/%d\n"
"WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" "SortOrder: %d, SortDir: %s\n"
"ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Desired %d/%d\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..",
"SortOrder: %d, SortDir: %s\n" n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x,
"UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze,
n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, column->WidthGiven, column->WidthRequested, column->ResizeWeight,
column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersIdeal,
column->WidthGiven, column->WidthRequested, column->ResizeWeight, column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None",
column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersDesired, column->UserID, column->Flags,
column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "",
column->UserID, column->Flags, (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "",
(column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "",
(column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : "");
(column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "",
(column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : "");
}
if (ImGuiTableSettings* settings = TableFindSettings(table))
DebugNodeTableSettings(settings);
TreePop();
} }
if (ImGuiTableSettings* settings = TableFindSettings(table))
DebugNodeTableSettings(settings);
TreePop();
} }
void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings)