--- Dear ImGui WIP -- (Tables and Columns Code) -- 1:1 port of imgui_tables.cpp (docking branch, 1.93 WIP) -- -- Port notes: -- - Column indices / display orders / draw channel indices are 0-based VALUES like upstream. -- Per-column arrays (table.Columns, table.DisplayOrderToIndex, table.RowCellData) are Lua tables with 0-based keys. -- table.ColumnsSize mirrors upstream `table->Columns.size()`. -- - ImBitArray masks are Lua tables { [n] = true }. -- - table.ColumnsNames is a Lua array of strings, column.NameOffset is a 0-based index into it (-1 = none). -- - g.SettingsTables is a plain vector of ImGuiTableSettings; table.SettingsOffset is a 1-based index into it (-1 = none). -- Each settings entry owns `Columns` (0-based Lua table of ImGuiTableColumnSettings). -- - ImGuiTableSortSpecs.Specs is a 1-based Lua array of ImGuiTableColumnSortSpecs. --- @type ImGuiContext? local GImGui local MT = ImGui.GetMetatables() -- Sets local `GImGui` in this file(imgui_tables.lua). -- This is currently only used in main code `ImGui.SetCurrentContext()` --- @param ctx ImGuiContext? function ImGui._SetCurrentContext_Tables(ctx) GImGui = ctx end local bit32 = bit32 local band, bor, bnot, lshift, rshift = bit32.band, bit32.bor, bit32.bnot, bit32.lshift, bit32.rshift IM_COL32_DISABLE = IM_COL32(0, 0, 0, 1) IMGUI_TABLE_MAX_COLUMNS = 512 local function IMGUI_DEBUG_LOG_TABLE(fmt, ...) local g = GImGui if band(g.DebugLogFlags, ImGuiDebugLogFlags.EventTable) ~= 0 then ImGui.DebugLog(fmt, ...) end end ---------------------------------------------------------------- -- [SECTION] Structs ---------------------------------------------------------------- local function BitArrayClearAll(arr) table.clear(arr) end local function BitArraySet(arr, n) arr[n] = true end local function BitTest(arr, n) return arr[n] == true end --- @class ImGuiTableColumnSortSpecs function ImGuiTableColumnSortSpecs() return { ColumnUserID = 0, ColumnIndex = 0, SortOrder = 0, SortDirection = ImGuiSortDirection.None } end --- @class ImGuiTableSortSpecs function ImGuiTableSortSpecs() return { Specs = nil, SpecsCount = 0, SpecsDirty = false } end --- @class ImGuiTableColumn function ImGuiTableColumn() return { Flags = 0, WidthGiven = 0.0, MinX = 0.0, MaxX = 0.0, WidthRequest = -1.0, WidthAuto = 0.0, WidthMax = 0.0, StretchWeight = -1.0, InitStretchWeightOrWidth = 0.0, ClipRect = ImRect(), ID = 0, UserData = 0, WorkMinX = 0.0, WorkMaxX = 0.0, ItemWidth = 0.0, ContentMaxXFrozen = 0.0, ContentMaxXUnfrozen = 0.0, ContentMaxXHeadersUsed = 0.0, ContentMaxXHeadersIdeal = 0.0, NameOffset = -1, DisplayOrder = -1, IndexWithinEnabledSet = -1, PrevEnabledColumn = -1, NextEnabledColumn = -1, SortOrder = -1, DrawChannelCurrent = -1, DrawChannelFrozen = -1, DrawChannelUnfrozen = -1, IsEnabled = false, IsUserEnabled = false, IsUserEnabledNextFrame = false, IsVisibleX = false, IsVisibleY = false, IsRequestOutput = false, IsSkipItems = false, IsPreserveWidthAuto = false, IsJustCreated = true, IsLoadedSettings = false, IsNeedReconcileSrc = false, IsNeedReconcileDst = false, NavLayerCurrent = 0, AutoFitQueue = 0, CannotSkipItemsQueue = 0, SortDirection = ImGuiSortDirection.None, SortDirectionsAvailCount = 0, SortDirectionsAvailMask = 0, SortDirectionsAvailList = 0, } end --- `*dst = *src` local function TableColumnCopy(dst, src) for k, v in pairs(src) do if k ~= "ClipRect" then dst[k] = v end end ImRect_Copy(dst.ClipRect, src.ClipRect) return dst end local function TableColumnClone(src) return TableColumnCopy(ImGuiTableColumn(), src) end --- @class ImGuiTableInstanceData function ImGuiTableInstanceData() return { TableInstanceID = 0, LastOuterHeight = 0.0, LastTopHeadersRowHeight = 0.0, LastFrozenHeight = 0.0, HoveredRowLast = -1, HoveredRowNext = -1 } end --- @class ImGuiTableTempData function ImGuiTableTempData() return { WindowID = 0, TableIndex = 0, LastTimeActive = -1.0, AngledHeadersExtraWidth = 0.0, AngledHeadersRequests = ImVector(), ReconcileColumnsRequests = ImVector(), OldColumnsRawData = nil, OldColumnsData = nil, OldColumnsDataSize = 0, UserOuterSize = ImVec2(), DrawSplitter = ImDrawListSplitter(), HostBackupWorkRect = ImRect(), HostBackupParentWorkRect = ImRect(), HostBackupPrevLineSize = ImVec2(), HostBackupCurrLineSize = ImVec2(), HostBackupCursorMaxPos = ImVec2(), HostBackupColumnsOffset = 0.0, HostBackupItemWidth = 0.0, HostBackupItemWidthStackSize = 0, } end --- @class ImGuiTable function ImGuiTable() return { ID = 0, Flags = 0, RawData = nil, TempData = nil, Columns = {}, ColumnsSize = 0, DisplayOrderToIndex = {}, RowCellData = {}, EnabledMaskByDisplayOrder = {}, EnabledMaskByIndex = {}, VisibleMaskByIndex = {}, SettingsLoadedFlags = 0, SettingsOffset = 0, LastFrameActive = -1, ColumnsCount = 0, CurrentRow = 0, CurrentColumn = 0, InstanceCurrent = 0, InstanceInteracted = 0, RowPosY1 = 0.0, RowPosY2 = 0.0, RowMinHeight = 0.0, RowCellPaddingY = 0.0, RowTextBaseline = 0.0, RowIndentOffsetX = 0.0, RowFlags = 0, LastRowFlags = 0, RowBgColorCounter = 0, RowBgColor = { [0] = 0, [1] = 0 }, BorderColorStrong = 0, BorderColorLight = 0, BorderX1 = 0.0, BorderX2 = 0.0, HostIndentX = 0.0, MinColumnWidth = 0.0, OuterPaddingX = 0.0, CellPaddingX = 0.0, CellSpacingX1 = 0.0, CellSpacingX2 = 0.0, InnerWidth = 0.0, ColumnsGivenWidth = 0.0, ColumnsAutoFitWidth = 0.0, ColumnsStretchSumWeights = 0.0, ResizedColumnNextWidth = 0.0, ResizeLockMinContentsX2 = 0.0, RefScale = 0.0, AngledHeadersHeight = 0.0, AngledHeadersSlope = 0.0, OuterRect = ImRect(), InnerRect = ImRect(), WorkRect = ImRect(), InnerClipRect = ImRect(), BgClipRect = ImRect(), Bg0ClipRectForDrawCmd = ImRect(), Bg2ClipRectForDrawCmd = ImRect(), HostClipRect = ImRect(), HostBackupInnerClipRect = ImRect(), OuterWindow = nil, InnerWindow = nil, ColumnsNames = {}, DrawSplitter = nil, InstanceDataFirst = ImGuiTableInstanceData(), InstanceDataExtra = ImVector(), SortSpecs = ImGuiTableSortSpecs(), SortSpecsMulti = {}, SortSpecsSingle = ImGuiTableColumnSortSpecs(), SortSpecsCount = 0, ColumnsEnabledCount = 0, ColumnsEnabledFixedCount = 0, DeclColumnsCount = 0, AngledHeadersCount = 0, HoveredColumnBody = 0, HoveredColumnBorder = 0, HighlightColumnHeader = 0, AutoFitSingleColumn = 0, ResizedColumn = 0, LastResizedColumn = 0, HeldHeaderColumn = 0, LastHeldHeaderColumn = 0, ReorderColumn = 0, ReorderColumnDstOrder = 0, LeftMostEnabledColumn = 0, RightMostEnabledColumn = 0, LeftMostStretchedColumn = 0, RightMostStretchedColumn = 0, ContextPopupColumn = 0, FreezeRowsRequest = 0, FreezeRowsCount = 0, FreezeColumnsRequest = 0, FreezeColumnsCount = 0, RowCellDataCurrent = 0, DummyDrawChannel = 0, Bg2DrawChannelCurrent = 0, Bg2DrawChannelUnfrozen = 0, NavLayer = 0, IsLayoutLocked = false, IsInsideRow = false, IsNewTable = false, IsInitializing = false, IsReconcileMode = false, IsSortSpecsDirty = false, IsUsingHeaders = false, IsContextPopupOpen = false, DisableDefaultContextMenu = false, IsSettingsRequestLoad = false, IsSettingsDirty = false, IsDefaultDisplayOrder = false, IsDefaultVisibility = false, IsResetAllRequest = false, IsResetDisplayOrderRequest = false, IsResetVisibilityRequest = false, IsUnfrozenRows = false, IsDefaultSizingPolicy = false, IsActiveIdAliveBeforeTable = false, IsActiveIdInTable = false, HasScrollbarYCurr = false, HasScrollbarYPrev = false, MemoryCompacted = false, HostSkipItems = false, } end --- @class ImGuiTableColumnSettings function ImGuiTableColumnSettings() return { WidthOrWeight = 0.0, ID = 0, Index = -1, DisplayOrder = -1, SortOrder = -1, SortDirection = ImGuiSortDirection.None, IsEnabled = -1, IsStretch = 0, IsLoaded = false } end --- @class ImGuiTableSettings function ImGuiTableSettings() return { ID = 0, SaveFlags = 0, RefScale = 0.0, ColumnsCount = 0, ColumnsCountMax = 0, LastUsedDate = 0, WantApply = false, Columns = {} } end --- @class ImGuiOldColumnData function ImGuiOldColumnData() return { OffsetNorm = 0.0, OffsetNormBeforeResize = 0.0, Flags = 0, ClipRect = ImRect() } end --- @class ImGuiOldColumns function ImGuiOldColumns() return { ID = 0, Flags = 0, IsFirstFrame = false, IsBeingResized = false, Current = 0, Count = 0, OffMinX = 0.0, OffMaxX = 0.0, LineMinY = 0.0, LineMaxY = 0.0, HostCursorPosY = 0.0, HostCursorMaxPosX = 0.0, HostInitialClipRect = ImRect(), HostBackupClipRect = ImRect(), HostBackupParentWorkRect = ImRect(), Columns = ImVector(), Splitter = ImDrawListSplitter() } end ImGuiOldColumnFlags = ImGuiOldColumnFlags or { None = 0, NoBorder = lshift(1, 0), NoResize = lshift(1, 1), NoPreserveWidths = lshift(1, 2), NoForceWithinWindow = lshift(1, 3), GrowParentContentsSize = lshift(1, 4), } ImGuiTableColumnFlags.NoDirectResize_ = ImGuiTableColumnFlags.NoDirectResize_ or lshift(1, 30) local function Rect(r) return ImRect(r.Min.x, r.Min.y, r.Max.x, r.Max.y) end local function V2(v) return ImVec2(v.x, v.y) end ---------------------------------------------------------------- -- [SECTION] Tables: Main code ---------------------------------------------------------------- -- Configuration local TABLE_DRAW_CHANNEL_BG0 = 0 local TABLE_DRAW_CHANNEL_BG2_FROZEN = 1 local TABLE_DRAW_CHANNEL_NOCLIP = 2 -- When using ImGuiTableFlags_NoClip (this becomes the last visible channel) local TABLE_BORDER_SIZE = 1.0 -- FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering. local TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0 -- Extend outside inner borders. local TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06 -- Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. -- Helper local function TableFixFlags(flags, outer_window) -- Adjust flags: set default sizing policy if band(flags, ImGuiTableFlags.SizingMask_) == 0 then flags = bor(flags, (band(flags, ImGuiTableFlags.ScrollX) ~= 0 or band(outer_window.Flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0) and ImGuiTableFlags.SizingFixedFit or ImGuiTableFlags.SizingStretchSame) end -- Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame if band(flags, ImGuiTableFlags.SizingMask_) == ImGuiTableFlags.SizingFixedSame then flags = bor(flags, ImGuiTableFlags.NoKeepColumnsVisible) end -- Adjust flags: enforce borders when resizable if band(flags, ImGuiTableFlags.Resizable) ~= 0 then flags = bor(flags, ImGuiTableFlags.BordersInnerV) end -- Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on if band(flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) ~= 0 then flags = band(flags, bnot(bor(ImGuiTableFlags.NoHostExtendX, ImGuiTableFlags.NoHostExtendY))) end -- Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody if band(flags, ImGuiTableFlags.NoBordersInBodyUntilResize) ~= 0 then flags = band(flags, bnot(ImGuiTableFlags.NoBordersInBody)) end -- Adjust flags: disable saved settings if there's nothing to save if band(flags, bor(ImGuiTableFlags.Resizable, ImGuiTableFlags.Hideable, ImGuiTableFlags.Reorderable, ImGuiTableFlags.Sortable)) == 0 then flags = bor(flags, ImGuiTableFlags.NoSavedSettings) end -- Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) if band(outer_window.RootWindow.Flags, ImGuiWindowFlags.NoSavedSettings) ~= 0 then flags = bor(flags, ImGuiTableFlags.NoSavedSettings) end return flags end function ImGui.TableFindByID(id) local g = GImGui return g.Tables:GetByKey(id) end --- @param table_ ImGuiTable --- @param instance_no int function ImGui.TableGetInstanceData(table_, instance_no) if instance_no == 0 then return table_.InstanceDataFirst end return table_.InstanceDataExtra.Data[instance_no] end function ImGui.TableGetInstanceID(table_, instance_no) return ImGui.TableGetInstanceData(table_, instance_no).TableInstanceID end -- Read about "TABLE SIZING" at the top of this file. --- @param str_id string --- @param columns_count int --- @param flags? ImGuiTableFlags --- @param outer_size? ImVec2 --- @param inner_width? float function ImGui.BeginTable(str_id, columns_count, flags, outer_size, inner_width) local id = ImGui.GetID(str_id) return ImGui.BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width) end function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_width) if flags == nil then flags = 0 end if outer_size == nil then outer_size = ImVec2(0.0, 0.0) end if inner_width == nil then inner_width = 0.0 end local g = GImGui local outer_window = ImGui.GetCurrentWindow() if outer_window.SkipItems then -- Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible. return false end -- Sanity checks IM_ASSERT(columns_count > 0 and columns_count < IMGUI_TABLE_MAX_COLUMNS) if band(flags, ImGuiTableFlags.ScrollX) ~= 0 then IM_ASSERT(inner_width >= 0.0) end -- If an outer size is specified ahead we will be able to early out when not visible. Exact clipping criteria may evolve. local use_child_window = band(flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) ~= 0 local avail_size = ImGui.GetContentRegionAvail() local actual_outer_size = ImTrunc(ImGui.CalcItemSize(outer_size, ImMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window and ImMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) or 0.0)) local outer_rect = ImRect(outer_window.DC.CursorPos, outer_window.DC.CursorPos + actual_outer_size) local outer_window_is_measuring_size = (outer_window.AutoFitFramesX > 0) or (outer_window.AutoFitFramesY > 0) -- Doesn't apply to AlwaysAutoResize windows! if use_child_window and ImGui.IsClippedEx(outer_rect, 0) and not outer_window_is_measuring_size then ImGui.ItemSize(outer_rect) ImGui.ItemAdd(outer_rect, id) g.NextWindowData:ClearFlags() return false end -- Acquire storage for the table local table_ = g.Tables:GetOrAddByKey(id) if table_.Columns == nil then -- pool created a plain table for k, v in pairs(ImGuiTable()) do table_[k] = v end end -- Acquire temporary buffers local table_idx = g.Tables:GetIndex(table_) g.TablesTempDataStacked = g.TablesTempDataStacked + 1 while g.TablesTempDataStacked > g.TablesTempData.Size do g.TablesTempData:push_back(ImGuiTableTempData()) end local temp_data = g.TablesTempData.Data[g.TablesTempDataStacked] table_.TempData = temp_data temp_data.TableIndex = table_idx temp_data.ReconcileColumnsRequests:resize(0) table_.DrawSplitter = table_.TempData.DrawSplitter table_.DrawSplitter:Clear() -- Fix flags table_.IsDefaultSizingPolicy = band(flags, ImGuiTableFlags.SizingMask_) == 0 flags = TableFixFlags(flags, outer_window) -- Initialize local previous_frame_active = table_.LastFrameActive local instance_no = (previous_frame_active ~= g.FrameCount) and 0 or (table_.InstanceCurrent + 1) local previous_flags = table_.Flags table_.ID = id table_.Flags = flags table_.LastFrameActive = g.FrameCount table_.OuterWindow = outer_window table_.InnerWindow = outer_window table_.ColumnsCount = columns_count table_.IsLayoutLocked = false table_.InnerWidth = inner_width table_.NavLayer = outer_window.DC.NavLayerCurrent table_.IsNewTable = (previous_frame_active == -1) ImVec2_Copy(temp_data.UserOuterSize, outer_size) -- Instance data (for instance 0, TableID == TableInstanceID) local instance_id table_.InstanceCurrent = instance_no if instance_no > 0 then IM_ASSERT(table_.ColumnsCount == columns_count, "BeginTable(): Cannot change columns count mid-frame while preserving same ID") if table_.InstanceDataExtra.Size < instance_no then table_.InstanceDataExtra:push_back(ImGuiTableInstanceData()) end instance_id = ImGui.GetIDWithSeed(instance_no, ImGui.GetIDWithSeed("##Instances", nil, id)) -- Push "##Instances" followed by (int)instance_no in ID stack. else instance_id = id end local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) table_instance.TableInstanceID = instance_id -- When not using a child window, WorkRect.Max will grow as we append contents. if use_child_window then -- Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent local override_content_size = ImVec2(FLT_MAX, FLT_MAX) if band(flags, ImGuiTableFlags.ScrollX) ~= 0 and band(flags, ImGuiTableFlags.ScrollY) == 0 then override_content_size.y = FLT_MIN end -- Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and never lead to any scrolling). if band(flags, ImGuiTableFlags.ScrollX) ~= 0 and inner_width > 0.0 then override_content_size.x = inner_width end if override_content_size.x ~= FLT_MAX or override_content_size.y ~= FLT_MAX then ImGui.SetNextWindowContentSize(ImVec2((override_content_size.x ~= FLT_MAX) and override_content_size.x or 0.0, (override_content_size.y ~= FLT_MAX) and override_content_size.y or 0.0)) end -- Reset scroll if we are reactivating it if band(previous_flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) == 0 then if band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasScroll) == 0 then ImGui.SetNextWindowScroll(ImVec2(0.0, 0.0)) end end -- Create scrolling region (without border and zero window padding) local child_window_flags = (band(flags, ImGuiTableFlags.ScrollX) ~= 0) and ImGuiWindowFlags.HorizontalScrollbar or ImGuiWindowFlags.None ImGui.BeginChildEx(name, instance_id, outer_rect:GetSize(), ImGuiChildFlags.None, child_window_flags) table_.InnerWindow = g.CurrentWindow ImRect_Copy(table_.WorkRect, table_.InnerWindow.WorkRect) ImRect_Copy(table_.OuterRect, table_.InnerWindow:Rect()) ImRect_Copy(table_.InnerRect, table_.InnerWindow.InnerRect) IM_ASSERT(table_.InnerWindow.WindowPadding.x == 0.0 and table_.InnerWindow.WindowPadding.y == 0.0 and table_.InnerWindow.WindowBorderSize == 0.0) -- Allow submitting when host is measuring if table_.InnerWindow.SkipItems and outer_window_is_measuring_size then table_.InnerWindow.SkipItems = false end -- When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned if instance_no == 0 then table_.HasScrollbarYPrev = table_.HasScrollbarYCurr table_.HasScrollbarYCurr = false end table_.HasScrollbarYCurr = table_.HasScrollbarYCurr or table_.InnerWindow.ScrollbarY else -- For non-scrolling tables, WorkRect == OuterRect == InnerRect. ImRect_Copy(table_.WorkRect, outer_rect) ImRect_Copy(table_.OuterRect, outer_rect) ImRect_Copy(table_.InnerRect, outer_rect) table_.HasScrollbarYPrev = false table_.HasScrollbarYCurr = false table_.InnerWindow.DC.TreeDepth = table_.InnerWindow.DC.TreeDepth + 1 -- This is designed to always linking ImGuiTreeNodeFlags_DrawLines linking across a table end -- Push a standardized ID for both child-using and not-child-using tables ImGui.PushOverrideID(id) if instance_no > 0 then ImGui.PushOverrideID(instance_id) end -- Backup a copy of host window members we will modify local inner_window = table_.InnerWindow table_.HostIndentX = inner_window.DC.Indent.x ImRect_Copy(table_.HostClipRect, inner_window.ClipRect) table_.HostSkipItems = inner_window.SkipItems temp_data.WindowID = inner_window.ID ImRect_Copy(temp_data.HostBackupWorkRect, inner_window.WorkRect) ImRect_Copy(temp_data.HostBackupParentWorkRect, inner_window.ParentWorkRect) temp_data.HostBackupColumnsOffset = outer_window.DC.ColumnsOffset.x ImVec2_Copy(temp_data.HostBackupPrevLineSize, inner_window.DC.PrevLineSize) ImVec2_Copy(temp_data.HostBackupCurrLineSize, inner_window.DC.CurrLineSize) ImVec2_Copy(temp_data.HostBackupCursorMaxPos, inner_window.DC.CursorMaxPos) temp_data.HostBackupItemWidth = outer_window.DC.ItemWidth temp_data.HostBackupItemWidthStackSize = outer_window.DC.ItemWidthStack.Size inner_window.DC.PrevLineSize.x = 0.0; inner_window.DC.PrevLineSize.y = 0.0 inner_window.DC.CurrLineSize.x = 0.0; inner_window.DC.CurrLineSize.y = 0.0 -- Make borders not overlap our contents by offsetting HostClipRect (#6765, #7428, #3752) if inner_window ~= outer_window then local border_size = TABLE_BORDER_SIZE if band(flags, ImGuiTableFlags.BordersOuterV) ~= 0 then table_.HostClipRect.Min.x = ImMin(table_.HostClipRect.Min.x + border_size, table_.HostClipRect.Max.x) if inner_window.DecoOuterSizeX2 == 0.0 then table_.HostClipRect.Max.x = ImMax(table_.HostClipRect.Max.x - border_size, table_.HostClipRect.Min.x) end end if band(flags, ImGuiTableFlags.BordersOuterH) ~= 0 then table_.HostClipRect.Min.y = ImMin(table_.HostClipRect.Min.y + border_size, table_.HostClipRect.Max.y) if inner_window.DecoOuterSizeY2 == 0.0 then table_.HostClipRect.Max.y = ImMax(table_.HostClipRect.Max.y - border_size, table_.HostClipRect.Min.y) end end end -- Padding and Spacing local pad_outer_x if band(flags, ImGuiTableFlags.NoPadOuterX) ~= 0 then pad_outer_x = false elseif band(flags, ImGuiTableFlags.PadOuterX) ~= 0 then pad_outer_x = true else pad_outer_x = band(flags, ImGuiTableFlags.BordersOuterV) ~= 0 end local pad_inner_x = band(flags, ImGuiTableFlags.NoPadInnerX) == 0 local inner_spacing_for_border = (band(flags, ImGuiTableFlags.BordersInnerV) ~= 0) and TABLE_BORDER_SIZE or 0.0 local inner_spacing_explicit = (pad_inner_x and band(flags, ImGuiTableFlags.BordersInnerV) == 0) and g.Style.CellPadding.x or 0.0 local inner_padding_explicit = (pad_inner_x and band(flags, ImGuiTableFlags.BordersInnerV) ~= 0) and g.Style.CellPadding.x or 0.0 table_.CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border table_.CellSpacingX2 = inner_spacing_explicit table_.CellPaddingX = inner_padding_explicit local outer_padding_for_border = (band(flags, ImGuiTableFlags.BordersOuterV) ~= 0) and TABLE_BORDER_SIZE or 0.0 local outer_padding_explicit = pad_outer_x and g.Style.CellPadding.x or 0.0 table_.OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table_.CellPaddingX table_.CurrentColumn = -1 table_.CurrentRow = -1 table_.RowBgColorCounter = 0 table_.LastRowFlags = ImGuiTableRowFlags.None ImRect_Copy(table_.InnerClipRect, (inner_window == outer_window) and table_.WorkRect or inner_window.ClipRect) table_.InnerClipRect:ClipWith(table_.WorkRect) -- We need this to honor inner_width table_.InnerClipRect:ClipWithFull(table_.HostClipRect) table_.InnerClipRect.Max.y = (band(flags, ImGuiTableFlags.NoHostExtendY) ~= 0) and ImMin(table_.InnerClipRect.Max.y, inner_window.WorkRect.Max.y) or table_.HostClipRect.Max.y table_.RowPosY1 = table_.WorkRect.Min.y -- This is needed somehow table_.RowPosY2 = table_.WorkRect.Min.y table_.RowTextBaseline = 0.0 -- This will be cleared again by TableBeginRow() table_.RowCellPaddingY = 0.0 table_.FreezeRowsRequest = 0; table_.FreezeRowsCount = 0 -- This will be setup by TableSetupScrollFreeze(), if any table_.FreezeColumnsRequest = 0; table_.FreezeColumnsCount = 0 table_.IsUnfrozenRows = true table_.DeclColumnsCount = 0; table_.AngledHeadersCount = 0 if previous_frame_active + 1 < g.FrameCount then table_.IsActiveIdInTable = false end table_.AngledHeadersHeight = 0.0 temp_data.AngledHeadersExtraWidth = 0.0 -- Using opaque colors facilitate overlapping lines of the grid, otherwise we'd need to improve TableDrawBorders() table_.BorderColorStrong = ImGui.GetColorU32(ImGuiCol.TableBorderStrong) table_.BorderColorLight = ImGui.GetColorU32(ImGuiCol.TableBorderLight) -- Make table current g.CurrentTable = table_ inner_window.DC.NavIsScrollPushableX = false -- Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); outer_window.DC.CurrentTableIdx = table_idx if inner_window ~= outer_window then -- So EndChild() within the inner window can restore the table properly. inner_window.DC.CurrentTableIdx = table_idx end if band(previous_flags, ImGuiTableFlags.Reorderable) ~= 0 and band(flags, ImGuiTableFlags.Reorderable) == 0 then table_.IsResetDisplayOrderRequest = true end -- Mark as used to avoid GC while table_idx >= g.TablesLastTimeActive.Size do g.TablesLastTimeActive:push_back(-1.0) end g.TablesLastTimeActive.Data[table_idx + 1] = g.Time temp_data.LastTimeActive = g.Time table_.MemoryCompacted = false -- Setup memory buffer (clear data if columns count changed) local old_columns_count = table_.ColumnsSize if old_columns_count ~= 0 and old_columns_count ~= columns_count then -- Attempt to preserve width and other settings on column count/specs change (#4046, #9108) IMGUI_DEBUG_LOG_TABLE("[table] Table 0x%08X column count %d -> %d, recreating storage.\n", table_.ID, old_columns_count, columns_count) IM_ASSERT(temp_data.OldColumnsRawData == nil) temp_data.OldColumnsRawData = table_.RawData -- Freed during layout temp_data.OldColumnsData = table_.Columns temp_data.OldColumnsDataSize = table_.ColumnsSize for n = 0, temp_data.OldColumnsDataSize - 1 do temp_data.OldColumnsData[n].IsNeedReconcileSrc = true end table_.RawData = nil end if table_.RawData == nil then ImGui.TableBeginInitMemory(table_, columns_count) table_.IsInitializing = true end if table_.IsResetAllRequest then ImGui.TableResetSettings(table_) end if table_.IsInitializing then -- Initialize if table_.IsNewTable then table_.SettingsOffset = -1 table_.IsSettingsRequestLoad = true end table_.IsSortSpecsDirty = true table_.IsSettingsDirty = true -- Records itself into .ini file even when in default state (#7934) table_.IsReconcileMode = false table_.InstanceInteracted = -1 table_.ContextPopupColumn = -1 table_.ReorderColumn = -1; table_.ReorderColumnDstOrder = -1; table_.ResizedColumn = -1; table_.LastResizedColumn = -1 table_.AutoFitSingleColumn = -1 table_.HoveredColumnBody = -1; table_.HoveredColumnBorder = -1 for n = 0, columns_count - 1 do local column = table_.Columns[n] if temp_data.OldColumnsData and n < temp_data.OldColumnsDataSize then TableColumnCopy(column, temp_data.OldColumnsData[n]) else local width_auto = column.WidthAuto TableColumnCopy(column, ImGuiTableColumn()) column.WidthAuto = width_auto column.IsPreserveWidthAuto = true -- Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker column.IsEnabled = true; column.IsUserEnabled = true; column.IsUserEnabledNextFrame = true column.DisplayOrder = n end table_.DisplayOrderToIndex[n] = column.DisplayOrder end end -- Load settings if table_.IsSettingsRequestLoad then ImGui.TableLoadSettings(table_) end -- Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. inner_window.SkipItems = true -- Clear names if #table_.ColumnsNames > 0 then table.clear(table_.ColumnsNames) end return true end function ImGui.TableBeginInitMemory(table_, columns_count) table_.RawData = true table_.Columns = {} table_.DisplayOrderToIndex = {} table_.RowCellData = {} for n = 0, columns_count - 1 do table_.Columns[n] = ImGuiTableColumn() table_.Columns[n].WidthAuto = 0.0 table_.DisplayOrderToIndex[n] = 0 table_.RowCellData[n] = { BgColor = 0, Column = 0 } end table_.ColumnsSize = columns_count table_.EnabledMaskByDisplayOrder = {} table_.EnabledMaskByIndex = {} table_.VisibleMaskByIndex = {} end -- Apply queued resizing/reordering/hiding requests function ImGui.TableApplyQueuedRequests(table_) -- Handle resizing request if table_.InstanceCurrent == 0 then if table_.ResizedColumn ~= -1 and table_.ResizedColumnNextWidth ~= FLT_MAX then ImGui.TableSetColumnWidth(table_.ResizedColumn, table_.ResizedColumnNextWidth) end table_.LastResizedColumn = table_.ResizedColumn table_.ResizedColumnNextWidth = FLT_MAX table_.ResizedColumn = -1 -- Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy. if table_.AutoFitSingleColumn ~= -1 then ImGui.TableSetColumnWidth(table_.AutoFitSingleColumn, table_.Columns[table_.AutoFitSingleColumn].WidthAuto) table_.AutoFitSingleColumn = -1 end end -- Handle reordering request if table_.InstanceCurrent == 0 then table_.LastHeldHeaderColumn = table_.HeldHeaderColumn table_.HeldHeaderColumn = -1 if table_.ReorderColumn ~= -1 and table_.ReorderColumnDstOrder ~= -1 then ImGui.TableSetColumnDisplayOrder(table_, table_.ReorderColumn, table_.ReorderColumnDstOrder) table_.ReorderColumnDstOrder = -1 end -- Release local g = GImGui if g.ActiveId == 0 then -- FIXME: Need to revisit. See 38f5e5a. table_.ReorderColumn = -1 end end -- Handle display order / visibility reset requests if table_.IsResetDisplayOrderRequest then for n = 0, table_.ColumnsCount - 1 do table_.Columns[n].DisplayOrder = n table_.DisplayOrderToIndex[n] = n end table_.IsResetDisplayOrderRequest = false table_.IsSettingsDirty = true end if table_.IsResetVisibilityRequest then for n = 0, table_.ColumnsSize - 1 do local column = table_.Columns[n] local v = band(column.Flags, ImGuiTableColumnFlags.DefaultHide) == 0 column.IsUserEnabled = v; column.IsUserEnabledNextFrame = v end table_.IsResetVisibilityRequest = false table_.IsSettingsDirty = true end end -- Apply immediately. See TableQueueSetColumnDisplayOrder() for additional checks/constraints. function ImGui.TableSetColumnDisplayOrder(table_, column_n, dst_order) IM_ASSERT(column_n >= 0 and column_n < table_.ColumnsCount) IM_ASSERT(dst_order >= 0 and dst_order < table_.ColumnsCount) local src_column = table_.Columns[column_n] local src_order = src_column.DisplayOrder if src_order == dst_order then return end local reorder_dir = (dst_order < src_order) and -1 or 1 src_column.DisplayOrder = dst_order local order_n = src_order + reorder_dir while order_n ~= dst_order + reorder_dir do local c = table_.Columns[table_.DisplayOrderToIndex[order_n]] c.DisplayOrder = c.DisplayOrder - reorder_dir order_n = order_n + reorder_dir end -- Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former. for n = 0, table_.ColumnsCount - 1 do table_.DisplayOrderToIndex[table_.Columns[n].DisplayOrder] = n end table_.IsSettingsDirty = true end local function TableGetMaxDisplayOrderAllowed(table_, src_order, dst_order) dst_order = ImClamp(dst_order, 0, table_.ColumnsCount - 1) if src_order == dst_order then return dst_order end -- Cannot cross over the frozen column limit when interactively reordering. if table_.FreezeColumnsRequest > 0 then dst_order = (src_order < table_.FreezeColumnsRequest) and ImMin(dst_order, table_.FreezeColumnsRequest - 1) or ImMax(dst_order, table_.FreezeColumnsRequest) end -- Cannot cross over a column with the ImGuiTableColumnFlags_NoReorder flag. local reorder_dir = (src_order < dst_order) and 1 or -1 local order_n = src_order while (src_order < dst_order and order_n <= dst_order) or (dst_order < src_order and order_n >= dst_order) do if band(table_.Columns[table_.DisplayOrderToIndex[order_n]].Flags, ImGuiTableColumnFlags.NoReorder) ~= 0 then dst_order = (order_n == src_order) and src_order or (order_n - reorder_dir) break end order_n = order_n + reorder_dir end return dst_order end -- Reorder requested by user interaction. function ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, dst_order) local src_order = table_.Columns[column_n].DisplayOrder table_.ReorderColumn = column_n table_.ReorderColumnDstOrder = -1 dst_order = TableGetMaxDisplayOrderAllowed(table_, src_order, dst_order) if table_.IsLayoutLocked and dst_order == src_order then -- We allow calling the function before layout w/ reconcile so don't early out. return end table_.ReorderColumnDstOrder = dst_order end -- Adjust flags: default width mode + stretch columns are not allowed when auto extending local function TableSetupColumnFlags(table_, column, column_n, flags_in) local flags = flags_in -- Sizing Policy if band(flags, ImGuiTableColumnFlags.WidthMask_) == 0 then local table_sizing_policy = band(table_.Flags, ImGuiTableFlags.SizingMask_) if table_sizing_policy == ImGuiTableFlags.SizingFixedFit or table_sizing_policy == ImGuiTableFlags.SizingFixedSame then flags = bor(flags, ImGuiTableColumnFlags.WidthFixed) else flags = bor(flags, ImGuiTableColumnFlags.WidthStretch) end else IM_ASSERT(ImIsPowerOfTwo(band(flags, ImGuiTableColumnFlags.WidthMask_))) -- Check that only 1 of each set is used. end -- Resize if band(table_.Flags, ImGuiTableFlags.Resizable) == 0 then flags = bor(flags, ImGuiTableColumnFlags.NoResize) end -- Sorting if band(flags, ImGuiTableColumnFlags.NoSortAscending) ~= 0 and band(flags, ImGuiTableColumnFlags.NoSortDescending) ~= 0 then flags = bor(flags, ImGuiTableColumnFlags.NoSort) end -- Indentation if band(flags, ImGuiTableColumnFlags.IndentMask_) == 0 then flags = bor(flags, (column_n == 0) and ImGuiTableColumnFlags.IndentEnable or ImGuiTableColumnFlags.IndentDisable) end -- Preserve status flags column.Flags = bor(flags, band(column.Flags, ImGuiTableColumnFlags.StatusMask_)) -- Build an ordered list of available sort directions column.SortDirectionsAvailCount = 0; column.SortDirectionsAvailMask = 0; column.SortDirectionsAvailList = 0 if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 then local count, mask, list = 0, 0, 0 local Asc, Desc = ImGuiSortDirection.Ascending, ImGuiSortDirection.Descending if band(flags, ImGuiTableColumnFlags.PreferSortAscending) ~= 0 and band(flags, ImGuiTableColumnFlags.NoSortAscending) == 0 then mask = bor(mask, lshift(1, Asc)); list = bor(list, lshift(Asc, lshift(count, 1))); count = count + 1 end if band(flags, ImGuiTableColumnFlags.PreferSortDescending) ~= 0 and band(flags, ImGuiTableColumnFlags.NoSortDescending) == 0 then mask = bor(mask, lshift(1, Desc)); list = bor(list, lshift(Desc, lshift(count, 1))); count = count + 1 end if band(flags, ImGuiTableColumnFlags.PreferSortAscending) == 0 and band(flags, ImGuiTableColumnFlags.NoSortAscending) == 0 then mask = bor(mask, lshift(1, Asc)); list = bor(list, lshift(Asc, lshift(count, 1))); count = count + 1 end if band(flags, ImGuiTableColumnFlags.PreferSortDescending) == 0 and band(flags, ImGuiTableColumnFlags.NoSortDescending) == 0 then mask = bor(mask, lshift(1, Desc)); list = bor(list, lshift(Desc, lshift(count, 1))); count = count + 1 end if band(table_.Flags, ImGuiTableFlags.SortTristate) ~= 0 or count == 0 then mask = bor(mask, lshift(1, ImGuiSortDirection.None)); count = count + 1 end column.SortDirectionsAvailList = band(list, 0xFF) column.SortDirectionsAvailMask = band(mask, 0xFF) column.SortDirectionsAvailCount = count ImGui.TableFixColumnSortDirection(table_, column) end end -- Layout columns for the frame. This is in essence the followup to BeginTable() and this is our largest function. -- Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() and other TableSetupXXXXX() functions to be called first. function ImGui.TableUpdateLayout(table_) local g = GImGui local temp_data = table_.TempData IM_ASSERT(table_.IsLayoutLocked == false) local columns_count = table_.ColumnsCount -- Reconcile moved columns if temp_data.ReconcileColumnsRequests.Size > 0 then ImGui.TableReconcileColumns(table_) end if temp_data.OldColumnsRawData then temp_data.OldColumnsRawData = nil temp_data.OldColumnsData = nil temp_data.OldColumnsDataSize = 0 end -- Apply columns settings if table_.IsSettingsRequestLoad then ImGui.TableLoadSettingsForColumns(table_) end if table_.IsInitializing or table_.IsSettingsRequestLoad then for n = 0, table_.ColumnsSize - 1 do local column = table_.Columns[n] local init_flags if table_.IsSettingsRequestLoad then init_flags = column.IsLoadedSettings and bnot(table_.SettingsLoadedFlags) or bnot(0) else init_flags = column.IsJustCreated and bnot(0) or 0 end ImGui.TableInitColumnDefaults(table_, column, n, init_flags) end ImGui.TableFixDisplayOrder(table_) -- Call even for non _Reorderable table as we loaded .ini data. table_.IsSettingsRequestLoad = false end -- Apply queued resizing/reordering/hiding requests ImGui.TableApplyQueuedRequests(table_) -- Handle DPI/font resize local new_ref_scale_unit = g.FontSize if table_.RefScale ~= 0.0 and table_.RefScale ~= new_ref_scale_unit then local scale_factor = new_ref_scale_unit / table_.RefScale for n = 0, columns_count - 1 do table_.Columns[n].WidthRequest = table_.Columns[n].WidthRequest * scale_factor end end table_.RefScale = new_ref_scale_unit local table_sizing_policy = band(table_.Flags, ImGuiTableFlags.SizingMask_) table_.IsDefaultDisplayOrder = true; table_.IsDefaultVisibility = true table_.ColumnsEnabledCount = 0 BitArrayClearAll(table_.EnabledMaskByIndex) BitArrayClearAll(table_.EnabledMaskByDisplayOrder) table_.LeftMostEnabledColumn = -1 table_.MinColumnWidth = ImMax(1.0, g.Style.FramePadding.x * 1.0) -- g.Style.ColumnsMinSpacing; // FIXME-TABLE -- [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. local count_fixed = 0 local count_stretch = 0 local prev_visible_column_idx = -1 local has_auto_fit_request = false local has_resizable = false local stretch_sum_width_auto = 0.0 local fixed_max_width_auto = 0.0 for order_n = 0, columns_count - 1 do local column_n = table_.DisplayOrderToIndex[order_n] local column = table_.Columns[column_n] -- Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame. if table_.DeclColumnsCount <= column_n then TableSetupColumnFlags(table_, column, column_n, ImGuiTableColumnFlags.None) column.NameOffset = -1 column.ID = 0; column.UserData = 0 column.InitStretchWeightOrWidth = -1.0 end -- Update Enabled state, mark settings and sort specs dirty if band(table_.Flags, ImGuiTableFlags.Hideable) == 0 or band(column.Flags, ImGuiTableColumnFlags.NoHide) ~= 0 then column.IsUserEnabledNextFrame = true end if column.IsUserEnabled ~= column.IsUserEnabledNextFrame then column.IsUserEnabled = column.IsUserEnabledNextFrame table_.IsSettingsDirty = true end column.IsEnabled = column.IsUserEnabled and band(column.Flags, ImGuiTableColumnFlags.Disabled) == 0 column.IsJustCreated = false if column.IsEnabled ~= (band(column.Flags, ImGuiTableColumnFlags.DefaultHide) == 0) then table_.IsDefaultVisibility = false end if column_n ~= order_n then table_.IsDefaultDisplayOrder = false end if column.SortOrder ~= -1 and not column.IsEnabled then table_.IsSortSpecsDirty = true end if column.SortOrder > 0 and band(table_.Flags, ImGuiTableFlags.SortMulti) == 0 then table_.IsSortSpecsDirty = true end -- Auto-fit unsized columns local start_auto_fit if band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 then start_auto_fit = column.WidthRequest < 0.0 else start_auto_fit = column.StretchWeight < 0.0 end if start_auto_fit then column.AutoFitQueue = lshift(1, 3) - 1 -- Fit for three frames column.CannotSkipItemsQueue = lshift(1, 3) - 1 end if not column.IsEnabled then column.IndexWithinEnabledSet = -1 else -- Mark as enabled and link to previous/next enabled column column.PrevEnabledColumn = prev_visible_column_idx column.NextEnabledColumn = -1 if prev_visible_column_idx ~= -1 then table_.Columns[prev_visible_column_idx].NextEnabledColumn = column_n else table_.LeftMostEnabledColumn = column_n end column.IndexWithinEnabledSet = table_.ColumnsEnabledCount table_.ColumnsEnabledCount = table_.ColumnsEnabledCount + 1 BitArraySet(table_.EnabledMaskByIndex, column_n) BitArraySet(table_.EnabledMaskByDisplayOrder, column.DisplayOrder) prev_visible_column_idx = column_n IM_ASSERT(column.IndexWithinEnabledSet <= column.DisplayOrder) -- Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping) if not column.IsPreserveWidthAuto and table_.InstanceCurrent == 0 then column.WidthAuto = ImGui.TableGetColumnWidthAuto(table_, column) end -- Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto) local column_is_resizable = band(column.Flags, ImGuiTableColumnFlags.NoResize) == 0 if column_is_resizable then has_resizable = true end if band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 and column.InitStretchWeightOrWidth > 0.0 and not column_is_resizable then column.WidthAuto = column.InitStretchWeightOrWidth end if column.AutoFitQueue ~= 0x00 then has_auto_fit_request = true end if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then stretch_sum_width_auto = stretch_sum_width_auto + column.WidthAuto count_stretch = count_stretch + 1 else fixed_max_width_auto = ImMax(fixed_max_width_auto, column.WidthAuto) count_fixed = count_fixed + 1 end end end if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and table_.SortSpecsCount == 0 and band(table_.Flags, ImGuiTableFlags.SortTristate) == 0 then table_.IsSortSpecsDirty = true end table_.RightMostEnabledColumn = prev_visible_column_idx IM_ASSERT(table_.LeftMostEnabledColumn >= 0 and table_.RightMostEnabledColumn >= 0) -- [Part 2] Disable child window clipping while fitting columns. if has_auto_fit_request and table_.OuterWindow ~= table_.InnerWindow then table_.InnerWindow.SkipItems = false end if has_auto_fit_request then table_.IsSettingsDirty = true end -- [Part 3] Fix column flags and record a few extra information. local sum_width_requests = 0.0 local stretch_sum_weights = 0.0 table_.LeftMostStretchedColumn = -1; table_.RightMostStretchedColumn = -1 for column_n = 0, columns_count - 1 do if BitTest(table_.EnabledMaskByIndex, column_n) then local column = table_.Columns[column_n] local column_is_resizable = band(column.Flags, ImGuiTableColumnFlags.NoResize) == 0 if band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 then -- Apply same widths policy local width_auto = column.WidthAuto if table_sizing_policy == ImGuiTableFlags.SizingFixedSame and (column.AutoFitQueue ~= 0x00 or not column_is_resizable) then width_auto = fixed_max_width_auto end -- Apply automatic width if column.AutoFitQueue ~= 0x00 then column.WidthRequest = width_auto elseif band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 and not column_is_resizable and column.IsRequestOutput then column.WidthRequest = width_auto end -- FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets if column.AutoFitQueue > 0x01 and table_.IsInitializing and not column.IsPreserveWidthAuto then column.WidthRequest = ImMax(column.WidthRequest, table_.MinColumnWidth * 4.0) -- FIXME-TABLE: Another constant/scale? end sum_width_requests = sum_width_requests + column.WidthRequest else -- Initialize stretch weight if column.AutoFitQueue ~= 0x00 or column.StretchWeight < 0.0 or not column_is_resizable then if column.InitStretchWeightOrWidth > 0.0 then column.StretchWeight = column.InitStretchWeightOrWidth elseif table_sizing_policy == ImGuiTableFlags.SizingStretchProp then column.StretchWeight = (column.WidthAuto / stretch_sum_width_auto) * count_stretch else column.StretchWeight = 1.0 end end stretch_sum_weights = stretch_sum_weights + column.StretchWeight if table_.LeftMostStretchedColumn == -1 or table_.Columns[table_.LeftMostStretchedColumn].DisplayOrder > column.DisplayOrder then table_.LeftMostStretchedColumn = column_n end if table_.RightMostStretchedColumn == -1 or table_.Columns[table_.RightMostStretchedColumn].DisplayOrder < column.DisplayOrder then table_.RightMostStretchedColumn = column_n end end column.IsPreserveWidthAuto = false sum_width_requests = sum_width_requests + table_.CellPaddingX * 2.0 end end table_.ColumnsEnabledFixedCount = count_fixed table_.ColumnsStretchSumWeights = stretch_sum_weights -- [Part 4] Apply final widths based on requested widths local work_rect = Rect(table_.WorkRect) local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1) local width_removed = (table_.HasScrollbarYPrev and not table_.InnerWindow.ScrollbarY) and g.Style.ScrollbarSize or 0.0 -- To synchronize decoration width of synced tables with mismatching scrollbar state (#5920) local width_avail = ImMax(1.0, ((band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 and table_.InnerWidth == 0.0) and table_.InnerClipRect:GetWidth() or work_rect:GetWidth()) - width_removed) local width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests local width_remaining_for_stretched_columns = width_avail_for_stretched_columns table_.ColumnsGivenWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount for column_n = 0, columns_count - 1 do if BitTest(table_.EnabledMaskByIndex, column_n) then local column = table_.Columns[column_n] -- Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest) if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then local weight_ratio = column.StretchWeight / stretch_sum_weights column.WidthRequest = IM_TRUNC(ImMax(width_avail_for_stretched_columns * weight_ratio, table_.MinColumnWidth) + 0.01) width_remaining_for_stretched_columns = width_remaining_for_stretched_columns - column.WidthRequest end -- [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column if column.NextEnabledColumn == -1 and table_.LeftMostStretchedColumn ~= -1 then column.Flags = bor(column.Flags, ImGuiTableColumnFlags.NoDirectResize_) end -- Assign final width, record width in case we will need to shrink column.WidthGiven = ImTrunc(ImMax(column.WidthRequest, table_.MinColumnWidth)) table_.ColumnsGivenWidth = table_.ColumnsGivenWidth + column.WidthGiven end end -- [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). if width_remaining_for_stretched_columns >= 1.0 and band(table_.Flags, ImGuiTableFlags.PreciseWidths) == 0 then local order_n = columns_count - 1 while stretch_sum_weights > 0.0 and width_remaining_for_stretched_columns >= 1.0 and order_n >= 0 do if BitTest(table_.EnabledMaskByDisplayOrder, order_n) then local column = table_.Columns[table_.DisplayOrderToIndex[order_n]] if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then column.WidthRequest = column.WidthRequest + 1.0 column.WidthGiven = column.WidthGiven + 1.0 width_remaining_for_stretched_columns = width_remaining_for_stretched_columns - 1.0 end end order_n = order_n - 1 end end -- Determine if table is hovered which will be used to flag columns as hovered. local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) table_instance.HoveredRowLast = table_instance.HoveredRowNext table_instance.HoveredRowNext = -1 table_.HoveredColumnBody = -1; table_.HoveredColumnBorder = -1 local mouse_hit_rect = ImRect(table_.OuterRect.Min.x, table_.OuterRect.Min.y, table_.OuterRect.Max.x, ImMax(table_.OuterRect.Max.y, table_.OuterRect.Min.y + table_instance.LastOuterHeight)) local backup_active_id = g.ActiveId g.ActiveId = 0 local is_hovering_table = ImGui.ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags.None) g.ActiveId = backup_active_id -- Determine skewed MousePos.x to support angled headers. local mouse_skewed_x = g.IO.MousePos.x if table_.AngledHeadersHeight > 0.0 then if g.IO.MousePos.y >= table_.OuterRect.Min.y and g.IO.MousePos.y <= table_.OuterRect.Min.y + table_.AngledHeadersHeight then mouse_skewed_x = mouse_skewed_x + ImTrunc((table_.OuterRect.Min.y + table_.AngledHeadersHeight - g.IO.MousePos.y) * table_.AngledHeadersSlope) end end -- [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column local has_at_least_one_column_requesting_output = false local offset_x_frozen = (table_.FreezeColumnsCount > 0) local offset_x = ((table_.FreezeColumnsCount > 0) and table_.OuterRect.Min.x or work_rect.Min.x) + table_.OuterPaddingX - table_.CellSpacingX1 local host_clip_rect = Rect(table_.InnerClipRect) BitArrayClearAll(table_.VisibleMaskByIndex) for order_n = 0, columns_count - 1 do local column_n = table_.DisplayOrderToIndex[order_n] local column = table_.Columns[column_n] -- Initial nav layer: using FreezeRowsCount, NOT FreezeRowsRequest, so Header line changes layer when frozen column.NavLayerCurrent = (table_.FreezeRowsCount > 0) and ImGuiNavLayer.Menu or table_.NavLayer if offset_x_frozen and table_.FreezeColumnsCount == order_n then offset_x = offset_x + work_rect.Min.x - table_.OuterRect.Min.x offset_x_frozen = false end -- Clear status flags column.Flags = band(column.Flags, bnot(ImGuiTableColumnFlags.StatusMask_)) if not BitTest(table_.EnabledMaskByDisplayOrder, order_n) then -- Hidden column: clear a few fields and we are done with it for the remainder of the function. column.MinX = offset_x; column.MaxX = offset_x; column.WorkMinX = offset_x column.ClipRect.Min.x = offset_x; column.ClipRect.Max.x = offset_x column.WidthGiven = 0.0 column.ClipRect.Min.y = work_rect.Min.y column.ClipRect.Max.y = FLT_MAX column.ClipRect:ClipWithFull(host_clip_rect) column.IsVisibleX = false; column.IsVisibleY = false; column.IsRequestOutput = false column.IsSkipItems = true column.ItemWidth = 1.0 else -- Lock start position column.MinX = offset_x -- Lock width based on start position and minimum/maximum width for this position column.WidthMax = ImGui.TableCalcMaxColumnWidth(table_, column_n) column.WidthGiven = ImMin(column.WidthGiven, column.WidthMax) column.WidthGiven = ImMax(column.WidthGiven, ImMin(column.WidthRequest, table_.MinColumnWidth)) column.MaxX = offset_x + column.WidthGiven + table_.CellSpacingX1 + table_.CellSpacingX2 + table_.CellPaddingX * 2.0 -- Lock other positions local previous_instance_work_min_x = column.WorkMinX column.WorkMinX = column.MinX + table_.CellPaddingX + table_.CellSpacingX1 column.WorkMaxX = column.MaxX - table_.CellPaddingX - table_.CellSpacingX2 -- Expected max column.ItemWidth = ImTrunc(column.WidthGiven * 0.65) column.ClipRect.Min.x = column.MinX column.ClipRect.Min.y = work_rect.Min.y column.ClipRect.Max.x = column.MaxX --column->WorkMaxX; column.ClipRect.Max.y = FLT_MAX column.ClipRect:ClipWithFull(host_clip_rect) -- Mark column as Clipped (not in sight) column.IsVisibleX = (column.ClipRect.Max.x > column.ClipRect.Min.x) column.IsVisibleY = true -- (column->ClipRect.Max.y > column->ClipRect.Min.y); local is_visible = column.IsVisibleX --&& column->IsVisibleY; if is_visible then BitArraySet(table_.VisibleMaskByIndex, column_n) end -- Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. column.IsRequestOutput = is_visible or column.AutoFitQueue ~= 0 or column.CannotSkipItemsQueue ~= 0 -- Mark column as SkipItems (ignoring all items/layout) column.IsSkipItems = not column.IsEnabled or table_.HostSkipItems if column.IsSkipItems then IM_ASSERT(not is_visible) end if column.IsRequestOutput and not column.IsSkipItems then has_at_least_one_column_requesting_output = true end -- Update status flags column.Flags = bor(column.Flags, ImGuiTableColumnFlags.IsEnabled) if is_visible then column.Flags = bor(column.Flags, ImGuiTableColumnFlags.IsVisible) end if column.SortOrder ~= -1 then column.Flags = bor(column.Flags, ImGuiTableColumnFlags.IsSorted) end -- Detect hovered column if is_hovering_table and mouse_skewed_x >= column.ClipRect.Min.x and mouse_skewed_x < column.ClipRect.Max.x then column.Flags = bor(column.Flags, ImGuiTableColumnFlags.IsHovered) table_.HoveredColumnBody = column_n end -- Reset content width variables if table_.InstanceCurrent == 0 then column.ContentMaxXFrozen = column.WorkMinX column.ContentMaxXUnfrozen = column.WorkMinX column.ContentMaxXHeadersUsed = column.WorkMinX column.ContentMaxXHeadersIdeal = column.WorkMinX else -- As we store an absolute value to make per-cell updates faster, we need to offset values used for width computation. local offset_from_previous_instance = column.WorkMinX - previous_instance_work_min_x column.ContentMaxXFrozen = column.ContentMaxXFrozen + offset_from_previous_instance column.ContentMaxXUnfrozen = column.ContentMaxXUnfrozen + offset_from_previous_instance column.ContentMaxXHeadersUsed = column.ContentMaxXHeadersUsed + offset_from_previous_instance column.ContentMaxXHeadersIdeal = column.ContentMaxXHeadersIdeal + offset_from_previous_instance end -- Don't decrement auto-fit counters until container window got a chance to submit its items if table_.HostSkipItems == false and table_.InstanceCurrent == 0 then column.AutoFitQueue = rshift(column.AutoFitQueue, 1) column.CannotSkipItemsQueue = rshift(column.CannotSkipItemsQueue, 1) end if order_n < table_.FreezeColumnsCount then host_clip_rect.Min.x = ImClamp(column.MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x) end offset_x = offset_x + column.WidthGiven + table_.CellSpacingX1 + table_.CellSpacingX2 + table_.CellPaddingX * 2.0 end end -- In case the table is visible (e.g. decorations) but all columns clipped, we keep a column visible. if has_at_least_one_column_requesting_output == false then table_.Columns[table_.LeftMostEnabledColumn].IsRequestOutput = true table_.Columns[table_.LeftMostEnabledColumn].IsSkipItems = false end -- [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) local unused_x1 = ImMax(table_.WorkRect.Min.x, table_.Columns[table_.RightMostEnabledColumn].ClipRect.Max.x) if is_hovering_table and table_.HoveredColumnBody == -1 then if mouse_skewed_x >= unused_x1 then table_.HoveredColumnBody = columns_count end end if has_resizable == false and band(table_.Flags, ImGuiTableFlags.Resizable) ~= 0 then table_.Flags = band(table_.Flags, bnot(ImGuiTableFlags.Resizable)) end table_.IsActiveIdAliveBeforeTable = (g.ActiveIdIsAlive ~= 0) -- [Part 8] Lock actual OuterRect/WorkRect right-most position. if table_.RightMostStretchedColumn ~= -1 then table_.Flags = band(table_.Flags, bnot(ImGuiTableFlags.NoHostExtendX)) end if band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then table_.OuterRect.Max.x = unused_x1; table_.WorkRect.Max.x = unused_x1 table_.InnerClipRect.Max.x = ImMin(table_.InnerClipRect.Max.x, unused_x1) end ImRect_Copy(table_.InnerWindow.ParentWorkRect, table_.WorkRect) table_.BorderX1 = table_.InnerClipRect.Min.x table_.BorderX2 = table_.InnerClipRect.Max.x -- Setup window's WorkRect.Max.y for GetContentRegionAvail(). Other values will be updated in each TableBeginCell() call. local window_content_max_y if band(table_.Flags, ImGuiTableFlags.NoHostExtendY) ~= 0 then window_content_max_y = table_.OuterRect.Max.y else window_content_max_y = ImMax(table_.InnerWindow.ContentRegionRect.Max.y, (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and 0.0 or table_.OuterRect.Max.y) end table_.InnerWindow.WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table_.InnerWindow.WorkRect.Min.y, table_.InnerWindow.WorkRect.Max.y) -- [Part 9] Allocate draw channels and setup background cliprect ImGui.TableSetupDrawChannels(table_) -- [Part 10] Hit testing on borders if band(table_.Flags, ImGuiTableFlags.Resizable) ~= 0 then ImGui.TableUpdateBorders(table_) end table_instance.LastTopHeadersRowHeight = 0.0 table_.IsLayoutLocked = true table_.IsUsingHeaders = false -- Highlight header table_.HighlightColumnHeader = -1 if table_.IsContextPopupOpen and table_.ContextPopupColumn ~= -1 and table_.InstanceInteracted == table_.InstanceCurrent then table_.HighlightColumnHeader = table_.ContextPopupColumn elseif band(table_.Flags, ImGuiTableFlags.HighlightHoveredColumn) ~= 0 and table_.HoveredColumnBody ~= -1 and table_.HoveredColumnBody ~= columns_count and table_.HoveredColumnBorder == -1 then if g.ActiveId == 0 or (table_.IsActiveIdInTable or g.DragDropActive) then table_.HighlightColumnHeader = table_.HoveredColumnBody end end -- [Part 11] Default context menu if table_.DisableDefaultContextMenu == false and ImGui.TableBeginContextMenuPopup(table_) then ImGui.TableDrawDefaultContextMenu(table_, table_.Flags) ImGui.EndPopup() end -- [Part 12] Sanitize and build sort specs before we have a chance to use them for display. if table_.IsSortSpecsDirty and band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 then ImGui.TableSortSpecsBuild(table_) end -- [Part 13] Setup inner window decoration size (for scrolling / nav tracking to properly take account of frozen rows/columns) if table_.FreezeColumnsRequest > 0 then table_.InnerWindow.DecoInnerSizeX1 = table_.Columns[table_.DisplayOrderToIndex[table_.FreezeColumnsRequest - 1]].MaxX - table_.OuterRect.Min.x -- FIXME-FROZEN end if table_.FreezeRowsRequest > 0 then table_.InnerWindow.DecoInnerSizeY1 = table_instance.LastFrozenHeight end table_instance.LastFrozenHeight = 0.0 local inner_window = table_.InnerWindow local bs = g.BoxSelectState if bs and bs.Window == inner_window and bs.UnclipMode then ImGui.TableApplyExternalUnclipRect(table_, bs.UnclipRect) end -- Initial state if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then table_.DrawSplitter:SetCurrentChannel(inner_window.DrawList, TABLE_DRAW_CHANNEL_NOCLIP) else inner_window.DrawList:PushClipRect(inner_window.InnerClipRect.Min, inner_window.InnerClipRect.Max, false) -- FIXME: use table->InnerClipRect? end end -- When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields. function ImGui.TableApplyExternalUnclipRect(table_, rect) if rect:IsInverted() then return end for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if not column.IsRequestOutput then if rect:Overlaps(ImRect(column.MinX, table_.WorkRect.Min.y, column.MaxX, FLT_MAX)) then column.IsRequestOutput = true end end end end -- Process hit-testing on resizing borders. Actual size change will be applied in EndTable() function ImGui.TableUpdateBorders(table_) local g = GImGui IM_ASSERT(band(table_.Flags, ImGuiTableFlags.Resizable) ~= 0) local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local hit_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale) local hit_y1 = ((table_.FreezeRowsCount >= 1) and table_.OuterRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight local hit_y2_body = ImMax(table_.OuterRect.Max.y, hit_y1 + table_instance.LastOuterHeight - table_.AngledHeadersHeight) local hit_y2_head = hit_y1 + table_instance.LastTopHeadersRowHeight for order_n = 0, table_.ColumnsCount - 1 do repeat if not BitTest(table_.EnabledMaskByDisplayOrder, order_n) then break end local column_n = table_.DisplayOrderToIndex[order_n] local column = table_.Columns[column_n] if band(column.Flags, bor(ImGuiTableColumnFlags.NoResize, ImGuiTableColumnFlags.NoDirectResize_)) ~= 0 then break end -- ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() local border_y2_hit = (band(table_.Flags, ImGuiTableFlags.NoBordersInBody) ~= 0) and hit_y2_head or hit_y2_body if band(table_.Flags, ImGuiTableFlags.NoBordersInBody) ~= 0 and table_.IsUsingHeaders == false then break end if not column.IsVisibleX and table_.LastResizedColumn ~= column_n then break end local column_id = ImGui.TableGetColumnResizeID(table_, column_n, table_.InstanceCurrent) local hit_rect = ImRect(column.MaxX - hit_half_width, hit_y1, column.MaxX + hit_half_width, border_y2_hit) ImGui.ItemAdd(hit_rect, column_id, nil, ImGuiItemFlags.NoNav) local pressed, hovered, held = ImGui.ButtonBehavior(hit_rect, column_id, bor(ImGuiButtonFlags.FlattenChildren, ImGuiButtonFlags.PressedOnClick, ImGuiButtonFlags.PressedOnDoubleClick, ImGuiButtonFlags.NoNavFocus)) if pressed and ImGui.IsMouseDoubleClicked(0) then ImGui.TableSetColumnWidthAutoSingle(table_, column_n) ImGui.ClearActiveID() held = false end if held then if table_.LastResizedColumn == -1 then table_.ResizeLockMinContentsX2 = (table_.RightMostEnabledColumn ~= -1) and table_.Columns[table_.RightMostEnabledColumn].MaxX or -FLT_MAX end table_.ResizedColumn = column_n table_.InstanceInteracted = table_.InstanceCurrent end if (hovered and g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) or held then table_.HoveredColumnBorder = column_n ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeEW) end until true end end function ImGui.EndTable() local g = GImGui local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "EndTable() call should only be done while in BeginTable() scope!") -- If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves. if not table_.IsLayoutLocked then ImGui.TableUpdateLayout(table_) end local flags = table_.Flags local inner_window = table_.InnerWindow local outer_window = table_.OuterWindow local temp_data = table_.TempData IM_ASSERT(inner_window == g.CurrentWindow and inner_window.ID == temp_data.WindowID) IM_ASSERT(outer_window == inner_window or outer_window == inner_window.ParentWindow) if table_.IsInsideRow then ImGui.TableEndRow(table_) end -- Context menu in columns body if band(flags, ImGuiTableFlags.ContextMenuInBody) ~= 0 then if table_.HoveredColumnBody ~= -1 and not ImGui.IsAnyItemHovered() and ImGui.IsMouseReleased(ImGuiMouseButton.Right) then ImGui.TableOpenContextMenu(table_.HoveredColumnBody) end end -- Finalize table height local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) ImVec2_Copy(inner_window.DC.PrevLineSize, temp_data.HostBackupPrevLineSize) ImVec2_Copy(inner_window.DC.CurrLineSize, temp_data.HostBackupCurrLineSize) ImVec2_Copy(inner_window.DC.CursorMaxPos, temp_data.HostBackupCursorMaxPos) local inner_content_max_y = ImCeil(table_.RowPosY2) -- Rounding final position is important as we currently don't round row height IM_ASSERT(table_.RowPosY2 == inner_window.DC.CursorPos.y) if inner_window ~= outer_window then inner_window.DC.CursorMaxPos.y = inner_content_max_y elseif band(flags, ImGuiTableFlags.NoHostExtendY) == 0 then local v = ImMax(table_.OuterRect.Max.y, inner_content_max_y) -- Patch OuterRect/InnerRect height table_.OuterRect.Max.y = v; table_.InnerRect.Max.y = v end table_.WorkRect.Max.y = ImMax(table_.WorkRect.Max.y, table_.OuterRect.Max.y) table_instance.LastOuterHeight = table_.OuterRect:GetHeight() -- Setup inner scrolling range if band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 then local outer_padding_for_border = (band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0) and TABLE_BORDER_SIZE or 0.0 local max_pos_x = inner_window.DC.CursorMaxPos.x if table_.RightMostEnabledColumn ~= -1 then max_pos_x = ImMax(max_pos_x, table_.Columns[table_.RightMostEnabledColumn].WorkMaxX + table_.CellPaddingX + table_.OuterPaddingX - outer_padding_for_border) end if table_.ResizedColumn ~= -1 then max_pos_x = ImMax(max_pos_x, table_.ResizeLockMinContentsX2) end inner_window.DC.CursorMaxPos.x = max_pos_x + table_.TempData.AngledHeadersExtraWidth end -- Pop clipping rect if band(flags, ImGuiTableFlags.NoClip) == 0 then inner_window.DrawList:PopClipRect() end ImRect_CopyFromV4(inner_window.ClipRect, inner_window.DrawList._ClipRectStack:back()) -- Draw borders if band(flags, ImGuiTableFlags.Borders) ~= 0 then ImGui.TableDrawBorders(table_) end -- Flatten channels and merge draw calls local splitter = table_.DrawSplitter splitter:SetCurrentChannel(inner_window.DrawList, 0) if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then ImGui.TableMergeDrawChannels(table_) end splitter:Merge(inner_window.DrawList) -- Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable() local auto_fit_width_for_fixed = 0.0 local auto_fit_width_for_stretched = 0.0 local auto_fit_width_for_stretched_min = 0.0 for column_n = 0, table_.ColumnsCount - 1 do if BitTest(table_.EnabledMaskByIndex, column_n) then local column = table_.Columns[column_n] local column_width_request if band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoResize) == 0 then column_width_request = column.WidthRequest else column_width_request = ImGui.TableGetColumnWidthAuto(table_, column) end if band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 then auto_fit_width_for_fixed = auto_fit_width_for_fixed + column_width_request else auto_fit_width_for_stretched = auto_fit_width_for_stretched + column_width_request end if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoResize) ~= 0 then auto_fit_width_for_stretched_min = ImMax(auto_fit_width_for_stretched_min, column_width_request / (column.StretchWeight / table_.ColumnsStretchSumWeights)) end end end local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1) table_.ColumnsAutoFitWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount + auto_fit_width_for_fixed + ImMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min) -- Update scroll if band(table_.Flags, ImGuiTableFlags.ScrollX) == 0 and inner_window ~= outer_window then inner_window.Scroll.x = 0.0 elseif table_.LastResizedColumn ~= -1 and table_.ResizedColumn == -1 and inner_window.ScrollbarX and table_.InstanceInteracted == table_.InstanceCurrent then -- When releasing a column being resized, scroll to keep the resulting column in sight local neighbor_width_to_keep_visible = table_.MinColumnWidth + table_.CellPaddingX * 2.0 local column = table_.Columns[table_.LastResizedColumn] if column.MaxX < table_.InnerClipRect.Min.x then ImGui.SetScrollFromPosX(inner_window, column.MaxX - inner_window.Pos.x - neighbor_width_to_keep_visible, 1.0) elseif column.MaxX > table_.InnerClipRect.Max.x then ImGui.SetScrollFromPosX(inner_window, column.MaxX - inner_window.Pos.x + neighbor_width_to_keep_visible, 1.0) end end -- Apply resizing/dragging at the end of the frame if table_.ResizedColumn ~= -1 and table_.InstanceCurrent == table_.InstanceInteracted then local column = table_.Columns[table_.ResizedColumn] local new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale)) local new_width = ImTrunc(new_x2 - column.MinX - table_.CellSpacingX1 - table_.CellPaddingX * 2.0) table_.ResizedColumnNextWidth = new_width end table_.IsActiveIdInTable = (g.ActiveIdIsAlive ~= 0 and table_.IsActiveIdAliveBeforeTable == false) -- Pop from id stack IM_ASSERT_USER_ERROR(inner_window.IDStack:back() == table_instance.TableInstanceID, "Mismatching PushID/PopID!") IM_ASSERT_USER_ERROR(outer_window.DC.ItemWidthStack.Size >= temp_data.HostBackupItemWidthStackSize, "Too many PopItemWidth!") if table_.InstanceCurrent > 0 then ImGui.PopID() end ImGui.PopID() -- Restore window data that we modified local backup_outer_max_pos = V2(outer_window.DC.CursorMaxPos) ImRect_Copy(inner_window.WorkRect, temp_data.HostBackupWorkRect) ImRect_Copy(inner_window.ParentWorkRect, temp_data.HostBackupParentWorkRect) inner_window.SkipItems = table_.HostSkipItems ImVec2_Copy(outer_window.DC.CursorPos, table_.OuterRect.Min) outer_window.DC.ItemWidth = temp_data.HostBackupItemWidth outer_window.DC.ItemWidthStack:resize(temp_data.HostBackupItemWidthStackSize) outer_window.DC.ColumnsOffset.x = temp_data.HostBackupColumnsOffset -- Layout in outer window if inner_window ~= outer_window then local backup_nav_layers_active_mask = inner_window.DC.NavLayersActiveMask inner_window.DC.NavLayersActiveMask = bor(inner_window.DC.NavLayersActiveMask, lshift(1, table_.NavLayer)) -- So empty table don't appear to navigate differently. g.CurrentTable = nil -- To avoid error recovery recursing ImGui.EndChild() g.CurrentTable = table_ inner_window.DC.NavLayersActiveMask = backup_nav_layers_active_mask else inner_window.DC.TreeDepth = inner_window.DC.TreeDepth - 1 ImGui.ItemSize(table_.OuterRect:GetSize()) ImGui.ItemAdd(table_.OuterRect, 0) end -- Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar if band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then IM_ASSERT(band(table_.Flags, ImGuiTableFlags.ScrollX) == 0) outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth) elseif temp_data.UserOuterSize.x <= 0.0 then local outer_content_max_x = table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth local decoration_size = table_.TempData.AngledHeadersExtraWidth + ((inner_window ~= outer_window) and inner_window.ScrollbarSizes.x or 0.0) outer_window.DC.IdealMaxPos.x = ImMax(outer_window.DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data.UserOuterSize.x) outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table_.OuterRect.Max.x, outer_content_max_x + decoration_size)) else outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table_.OuterRect.Max.x) end if temp_data.UserOuterSize.y <= 0.0 then local outer_content_size_y = (inner_window == outer_window) and (inner_content_max_y - table_.InnerRect.Min.y) or (inner_content_max_y - inner_window.DC.CursorStartPos.y) local outer_content_max_y = table_.OuterRect.Min.y + outer_content_size_y local decoration_size = (inner_window ~= outer_window) and inner_window.ScrollbarSizes.y or 0.0 outer_window.DC.IdealMaxPos.y = ImMax(outer_window.DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data.UserOuterSize.y) outer_window.DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table_.OuterRect.Max.y, outer_content_max_y + decoration_size)) else -- OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set) outer_window.DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table_.OuterRect.Max.y) end -- Save settings if table_.IsSettingsDirty then ImGui.TableSaveSettings(table_) end table_.IsInitializing = false -- Clear or restore current table, if any IM_ASSERT(g.CurrentWindow == outer_window and g.CurrentTable == table_) IM_ASSERT(g.TablesTempDataStacked > 0) g.TablesTempDataStacked = g.TablesTempDataStacked - 1 temp_data = (g.TablesTempDataStacked > 0) and g.TablesTempData.Data[g.TablesTempDataStacked] or nil g.CurrentTable = (temp_data and (temp_data.WindowID == outer_window.ID)) and g.Tables:GetByIndex(temp_data.TableIndex) or nil if g.CurrentTable then g.CurrentTable.TempData = temp_data g.CurrentTable.DrawSplitter = temp_data.DrawSplitter end outer_window.DC.CurrentTableIdx = g.CurrentTable and g.Tables:GetIndex(g.CurrentTable) or -1 ImGui.NavUpdateCurrentWindowIsScrollPushableX() end -- Called in TableUpdateLayout() when initializing/after loading settings. -- 'init_mask' specify which fields to initialize. --- @param column_n int # index of `column` (C++: Columns.index_from_ptr(column)) function ImGui.TableInitColumnDefaults(table_, column, column_n, init_mask) local flags = column.Flags if band(init_mask, ImGuiTableFlags.Resizable) ~= 0 then local init_width_or_weight = column.InitStretchWeightOrWidth column.WidthRequest = (band(flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 and init_width_or_weight > 0.0) and init_width_or_weight or -1.0 column.StretchWeight = (init_width_or_weight > 0.0 and band(flags, ImGuiTableColumnFlags.WidthStretch) ~= 0) and init_width_or_weight or -1.0 if init_width_or_weight > 0.0 then -- Disable auto-fit if an explicit width/weight has been specified column.AutoFitQueue = 0x00 end end if band(init_mask, ImGuiTableFlags.Reorderable) ~= 0 then column.DisplayOrder = (band(table_.Flags, ImGuiTableFlags.Reorderable) ~= 0) and -1 or column_n end if band(init_mask, ImGuiTableFlags.Hideable) ~= 0 then local v = band(flags, ImGuiTableColumnFlags.DefaultHide) == 0 column.IsUserEnabled = v; column.IsUserEnabledNextFrame = v end if band(init_mask, ImGuiTableFlags.Sortable) ~= 0 then -- Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. column.SortOrder = (band(flags, ImGuiTableColumnFlags.DefaultSort) ~= 0) and 0 or -1 if band(flags, ImGuiTableColumnFlags.DefaultSort) ~= 0 then column.SortDirection = (band(flags, ImGuiTableColumnFlags.PreferSortDescending) ~= 0) and ImGuiSortDirection.Descending or ImGuiSortDirection.Ascending else column.SortDirection = ImGuiSortDirection.None end end end -- See "COLUMNS SIZING POLICIES" comments at the top of this file -- If (init_width_or_weight <= 0.0f) it is ignored local function TableSetupColumnApply(table_, idx, id, name_offset, flags, init_width_or_weight, user_data) local column = table_.Columns[idx] -- Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa. if table_.IsDefaultSizingPolicy and band(flags, ImGuiTableColumnFlags.WidthMask_) == 0 and band(flags, ImGuiTableFlags.ScrollX) == 0 then IM_ASSERT_USER_ERROR_RET(init_width_or_weight <= 0.0, "TableSetupColumn(): can only specify width/weight if sizing policy is set explicitly in either Table or Column.") end -- When passing a width automatically enforce WidthFixed policy if band(flags, ImGuiTableColumnFlags.WidthMask_) == 0 and init_width_or_weight > 0.0 then if band(table_.Flags, ImGuiTableFlags.SizingMask_) == ImGuiTableFlags.SizingFixedFit or band(table_.Flags, ImGuiTableFlags.SizingMask_) == ImGuiTableFlags.SizingFixedSame then flags = bor(flags, ImGuiTableColumnFlags.WidthFixed) end end if band(flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then flags = bor(flags, ImGuiTableColumnFlags.NoHeaderLabel) table_.AngledHeadersCount = table_.AngledHeadersCount + 1 end TableSetupColumnFlags(table_, column, idx, flags) column.ID = id column.UserData = user_data column.NameOffset = name_offset column.InitStretchWeightOrWidth = init_width_or_weight end --- @param label string? --- @param flags? ImGuiTableColumnFlags --- @param init_width_or_weight? float --- @param user_data? ImGuiID function ImGui.TableSetupColumn(label, flags, init_width_or_weight, user_data) if flags == nil then flags = 0 end if init_width_or_weight == nil then init_width_or_weight = 0.0 end if user_data == nil then user_data = 0 end local g = GImGui local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") IM_ASSERT_USER_ERROR_RET(table_.DeclColumnsCount < table_.ColumnsCount, "TableSetupColumn(): called too many times!") IM_ASSERT_USER_ERROR_RET(table_.IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!") IM_ASSERT(band(flags, ImGuiTableColumnFlags.StatusMask_) == 0, "Illegal to pass StatusMask values to TableSetupColumn()") -- Store name local name_offset = -1 if label ~= nil and label ~= "" then name_offset = #table_.ColumnsNames table_.ColumnsNames[name_offset + 1] = label end local column_id = (label ~= nil and label ~= "") and ImHashStr(label) or 0 -- When ID changed or a column moved: defer the request until layout where we will process full reconcile. local column_idx = table_.DeclColumnsCount table_.DeclColumnsCount = table_.DeclColumnsCount + 1 local column = table_.Columns[column_idx] -- If topology change goes into reconcile mode if table_.IsReconcileMode == false and column.ID ~= column_id and not table_.IsNewTable then table_.IsReconcileMode = true end -- Fast/common path if table_.IsReconcileMode == false then TableSetupColumnApply(table_, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data) column.IsNeedReconcileSrc = false; column.IsNeedReconcileDst = false local old = table_.TempData.OldColumnsData if old and column_idx < table_.TempData.OldColumnsDataSize then old[column_idx].IsNeedReconcileSrc = false end return end -- Reconcile path: defer applying data to TableUpdateLayout() -> TableReconcileMovedColumns() -> TableSetupColumnApply(). local reconcile_data = { ID = column_id, NameOffset = name_offset, Flags = flags, InitWidthOrWeight = init_width_or_weight, UserData = user_data, ColumnNewIdx = column_idx, ColumnOldIdx = -1, ColumnOldData = ImGuiTableColumn(), } table_.TempData.ReconcileColumnsRequests:push_back(reconcile_data) column.NameOffset = name_offset -- Allow TableGetColumnName() to work before layout column.IsNeedReconcileSrc = true; column.IsNeedReconcileDst = true end -- NB: This was written to be similar to the logic in TableLoadSettingsForColumns(). function ImGui.TableReconcileColumns(table_) local temp_data = table_.TempData IMGUI_DEBUG_LOG_TABLE("[table] Reconcile columns for table 0x%08X\n", table_.ID) local dst_columns = table_.Columns local src_columns, src_count if temp_data.OldColumnsData == nil then src_columns, src_count = table_.Columns, table_.ColumnsSize else src_columns, src_count = temp_data.OldColumnsData, temp_data.OldColumnsDataSize end -- Find matches for named columns. local matches = 0 local reconcile_requests = temp_data.ReconcileColumnsRequests for _, reconcile_data in reconcile_requests:iter() do if reconcile_data.ID ~= 0 then for src_n = 0, src_count - 1 do local src_column = src_columns[src_n] if src_column.ID == reconcile_data.ID and src_column.IsNeedReconcileSrc then local dst_column = dst_columns[reconcile_data.ColumnNewIdx] IM_ASSERT(src_column.IsNeedReconcileSrc and dst_column.IsNeedReconcileDst) src_column.IsNeedReconcileSrc = false; dst_column.IsNeedReconcileDst = false reconcile_data.ColumnOldIdx = src_n reconcile_data.ColumnOldData = TableColumnClone(src_column) matches = matches + 1 break end end end end -- Remaining entries are matched sequentially. local dst_idx = 1 -- index in reconcile array (1-based) if matches ~= reconcile_requests.Size then for src_n = 0, src_count - 1 do local src_column = src_columns[src_n] if src_column.IsNeedReconcileSrc then while dst_idx <= reconcile_requests.Size and reconcile_requests.Data[dst_idx].ColumnOldIdx ~= -1 do dst_idx = dst_idx + 1 end if dst_idx > reconcile_requests.Size then break end local reconcile_data = reconcile_requests.Data[dst_idx] local dst_column = dst_columns[reconcile_data.ColumnNewIdx] IM_ASSERT(src_column.IsNeedReconcileSrc and dst_column.IsNeedReconcileDst) src_column.IsNeedReconcileSrc = false; dst_column.IsNeedReconcileDst = false reconcile_data.ColumnOldIdx = src_n reconcile_data.ColumnOldData = TableColumnClone(src_column) end end end -- Apply in the final pass. Because it is possible that src_columns == table->Columns we went through a temporary copy. for _, reconcile_data in reconcile_requests:iter() do TableColumnCopy(table_.Columns[reconcile_data.ColumnNewIdx], reconcile_data.ColumnOldData) TableSetupColumnApply(table_, reconcile_data.ColumnNewIdx, reconcile_data.ID, reconcile_data.NameOffset, reconcile_data.Flags, reconcile_data.InitWidthOrWeight, reconcile_data.UserData) end ImGui.TableFixDisplayOrder(table_) table_.IsSettingsDirty = true -- FIXME-RECONCILE: Necessary? table_.IsReconcileMode = false reconcile_requests:resize(0) -- GC-ed once in NewFrame() end -- [Public] function ImGui.TableSetupScrollFreeze(columns, rows) local g = GImGui local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") IM_ASSERT(table_.IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!") IM_ASSERT(columns >= 0 and columns < IMGUI_TABLE_MAX_COLUMNS) IM_ASSERT(rows >= 0 and rows < 128) -- Arbitrary limit table_.FreezeColumnsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0) and ImMin(columns, table_.ColumnsCount) or 0 table_.FreezeColumnsCount = (table_.InnerWindow.Scroll.x ~= 0.0) and table_.FreezeColumnsRequest or 0 table_.FreezeRowsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and rows or 0 table_.FreezeRowsCount = (table_.InnerWindow.Scroll.y ~= 0.0) and table_.FreezeRowsRequest or 0 table_.IsUnfrozenRows = (table_.FreezeRowsCount == 0) -- Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it. end ---------------------------------------------------------------- -- [SECTION] Tables: Simple accessors ---------------------------------------------------------------- function ImGui.SetWindowClipRectBeforeSetChannel(window, clip_rect) local clip_rect_vec4 = clip_rect:ToVec4() ImRect_Copy(window.ClipRect, clip_rect) ImVec4_Copy(window.DrawList._CmdHeader.ClipRect, clip_rect_vec4) window.DrawList._ClipRectStack.Data[window.DrawList._ClipRectStack.Size] = clip_rect_vec4 end function ImGui.TableGetColumnCount() local g = GImGui local table_ = g.CurrentTable return table_ and table_.ColumnsCount or 0 end -- Overloads: TableGetColumnName(column_n?) / TableGetColumnName(table, column_n) function ImGui.TableGetColumnName(a, b) local table_, column_n if type(a) == "table" then table_, column_n = a, b else local g = GImGui table_ = g.CurrentTable if not table_ then return nil end column_n = a or -1 if column_n < 0 then column_n = table_.CurrentColumn end end if table_.IsLayoutLocked == false and column_n >= table_.DeclColumnsCount then return "" -- NameOffset is invalid at this point end local column = table_.Columns[column_n] if column.NameOffset == -1 then return "" end return table_.ColumnsNames[column.NameOffset + 1] end -- Change user accessible enabled/disabled state of a column (often perceived as "showing/hiding" from users point of view) function ImGui.TableSetColumnEnabled(column_n, enabled) local g = GImGui local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") IM_ASSERT(band(table_.Flags, ImGuiTableFlags.Hideable) ~= 0) -- See comments above if column_n < 0 then column_n = table_.CurrentColumn end IM_ASSERT(column_n >= 0 and column_n < table_.ColumnsCount) local column = table_.Columns[column_n] column.IsUserEnabledNextFrame = enabled end -- We allow querying for an extra column in order to poll the IsHovered state of the right-most section function ImGui.TableGetColumnFlags(column_n) if column_n == nil then column_n = -1 end local g = GImGui local table_ = g.CurrentTable if not table_ then return ImGuiTableColumnFlags.None end if column_n < 0 then column_n = table_.CurrentColumn end if column_n == table_.ColumnsCount then return (table_.HoveredColumnBody == column_n) and ImGuiTableColumnFlags.IsHovered or ImGuiTableColumnFlags.None end return table_.Columns[column_n].Flags end -- Return the cell rectangle based on currently known height. function ImGui.TableGetCellBgRect(table_, column_n) local column = table_.Columns[column_n] local x1 = column.MinX local x2 = column.MaxX x1 = ImMax(x1, table_.WorkRect.Min.x) x2 = ImMin(x2, table_.WorkRect.Max.x) return ImRect(x1, table_.RowPosY1, x2, table_.RowPosY2) end -- Return the resizing ID for the right-side of the given column. function ImGui.TableGetColumnResizeID(table_, column_n, instance_no) if instance_no == nil then instance_no = 0 end IM_ASSERT(column_n >= 0 and column_n < table_.ColumnsCount) local instance_id = ImGui.TableGetInstanceID(table_, instance_no) return band(instance_id + 1 + column_n, 0xFFFFFFFF) -- FIXME: #6140: still not ideal end -- Return -1 when table is not hovered. return columns_count if hovering the unused space at the right of the right-most visible column. function ImGui.TableGetHoveredColumn() local g = GImGui local table_ = g.CurrentTable if not table_ then return -1 end return table_.HoveredColumnBody end -- Return -1 when table is not hovered. Return maxrow+1 if in table but below last submitted row. function ImGui.TableGetHoveredRow() local g = GImGui local table_ = g.CurrentTable if not table_ then return -1 end local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) return table_instance.HoveredRowLast end function ImGui.TableSetBgColor(target, color, column_n) if column_n == nil then column_n = -1 end local g = GImGui local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") IM_ASSERT(target ~= ImGuiTableBgTarget.None) if color == IM_COL32_DISABLE then color = 0 end -- We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. if target == ImGuiTableBgTarget.CellBg then if table_.RowPosY1 > table_.InnerClipRect.Max.y then -- Discard return end if column_n == -1 then column_n = table_.CurrentColumn end if not BitTest(table_.VisibleMaskByIndex, column_n) then return end if table_.RowCellDataCurrent < 0 or table_.RowCellData[table_.RowCellDataCurrent].Column ~= column_n then table_.RowCellDataCurrent = table_.RowCellDataCurrent + 1 end local cell_data = table_.RowCellData[table_.RowCellDataCurrent] cell_data.BgColor = color cell_data.Column = column_n elseif target == ImGuiTableBgTarget.RowBg0 or target == ImGuiTableBgTarget.RowBg1 then if table_.RowPosY1 > table_.InnerClipRect.Max.y then -- Discard return end IM_ASSERT(column_n == -1) local bg_idx = (target == ImGuiTableBgTarget.RowBg1) and 1 or 0 table_.RowBgColor[bg_idx] = color else IM_ASSERT(false) end end ---------------------------------------------------------------- -- [SECTION] Tables: Row changes ---------------------------------------------------------------- -- [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows function ImGui.TableGetRowIndex() local g = GImGui local table_ = g.CurrentTable if not table_ then return 0 end return table_.CurrentRow end -- [Public] Starts into the first cell of a new row --- @param row_flags? ImGuiTableRowFlags --- @param row_min_height? float function ImGui.TableNextRow(row_flags, row_min_height) if row_flags == nil then row_flags = 0 end if row_min_height == nil then row_min_height = 0.0 end local g = GImGui local table_ = g.CurrentTable if not table_.IsLayoutLocked then ImGui.TableUpdateLayout(table_) end if table_.IsInsideRow then ImGui.TableEndRow(table_) end table_.LastRowFlags = table_.RowFlags table_.RowFlags = row_flags table_.RowCellPaddingY = g.Style.CellPadding.y table_.RowMinHeight = row_min_height ImGui.TableBeginRow(table_) -- We honor min_row_height requested by user, but cannot guarantee per-row maximum height, -- because that would essentially require a unique clipping rectangle per-cell. table_.RowPosY2 = table_.RowPosY2 + table_.RowCellPaddingY * 2.0 table_.RowPosY2 = ImMax(table_.RowPosY2, table_.RowPosY1 + row_min_height) -- Disable output until user calls TableNextColumn() table_.InnerWindow.SkipItems = true end -- [Internal] Only called by TableNextRow() function ImGui.TableBeginRow(table_) local window = table_.InnerWindow IM_ASSERT(not table_.IsInsideRow) -- New row table_.CurrentRow = table_.CurrentRow + 1 table_.CurrentColumn = -1 table_.RowBgColor[0] = IM_COL32_DISABLE; table_.RowBgColor[1] = IM_COL32_DISABLE table_.RowCellDataCurrent = -1 table_.IsInsideRow = true -- Begin frozen rows local next_y1 = table_.RowPosY2 if table_.CurrentRow == 0 and table_.FreezeRowsCount > 0 then next_y1 = table_.OuterRect.Min.y window.DC.CursorPos.y = next_y1 end table_.RowPosY1 = next_y1; table_.RowPosY2 = next_y1 table_.RowTextBaseline = 0.0 table_.RowIndentOffsetX = window.DC.Indent.x - table_.HostIndentX -- Lock indent window.DC.PrevLineTextBaseOffset = 0.0 window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y + table_.RowCellPaddingY -- This allows users to call SameLine() to share LineSize between columns. window.DC.PrevLineSize.x = 0.0; window.DC.PrevLineSize.y = 0.0 window.DC.CurrLineSize.x = 0.0; window.DC.CurrLineSize.y = 0.0 window.DC.IsSameLine = false; window.DC.IsSetPos = false window.DC.CursorMaxPos.y = next_y1 -- Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(ImGuiCol.TableHeaderBg)) if table_.CurrentRow == 0 then table_.IsUsingHeaders = true end end end -- [Internal] Called by TableNextRow() function ImGui.TableEndRow(table_) local g = GImGui local window = g.CurrentWindow IM_ASSERT(window == table_.InnerWindow) IM_ASSERT(table_.IsInsideRow) if table_.CurrentColumn ~= -1 then ImGui.TableEndCell(table_) table_.CurrentColumn = -1 end -- Logging if g.LogEnabled then ImGui.LogRenderedText(nil, "|") end -- Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. window.DC.CursorPos.y = table_.RowPosY2 -- Row background fill local bg_y1 = table_.RowPosY1 local bg_y2 = table_.RowPosY2 local unfreeze_rows_actual = (table_.CurrentRow + 1 == table_.FreezeRowsCount) local unfreeze_rows_request = (table_.CurrentRow + 1 == table_.FreezeRowsRequest) local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 and (table_.CurrentRow == 0 or band(table_.LastRowFlags, ImGuiTableRowFlags.Headers) ~= 0) then table_instance.LastTopHeadersRowHeight = table_instance.LastTopHeadersRowHeight + bg_y2 - bg_y1 end local is_visible = (bg_y2 >= table_.InnerClipRect.Min.y and bg_y1 <= table_.InnerClipRect.Max.y) if is_visible then -- Update data for TableGetHoveredRow() if table_.HoveredColumnBody ~= -1 and g.IO.MousePos.y >= bg_y1 and g.IO.MousePos.y < bg_y2 and table_instance.HoveredRowNext < 0 then table_instance.HoveredRowNext = table_.CurrentRow end -- Decide of background color for the row local bg_col0 = 0 local bg_col1 = 0 if table_.RowBgColor[0] ~= IM_COL32_DISABLE then bg_col0 = table_.RowBgColor[0] elseif band(table_.Flags, ImGuiTableFlags.RowBg) ~= 0 then bg_col0 = ImGui.GetColorU32((band(table_.RowBgColorCounter, 1) ~= 0) and ImGuiCol.TableRowBgAlt or ImGuiCol.TableRowBg) end if table_.RowBgColor[1] ~= IM_COL32_DISABLE then bg_col1 = table_.RowBgColor[1] end -- Decide of top border color local top_border_col = 0 local border_size = TABLE_BORDER_SIZE if table_.CurrentRow > 0 and band(table_.Flags, ImGuiTableFlags.BordersInnerH) ~= 0 then top_border_col = (band(table_.LastRowFlags, ImGuiTableRowFlags.Headers) ~= 0) and table_.BorderColorStrong or table_.BorderColorLight end local draw_cell_bg_color = table_.RowCellDataCurrent >= 0 local draw_strong_bottom_border = unfreeze_rows_actual if bor(bg_col0, bg_col1, top_border_col) ~= 0 or draw_strong_bottom_border or draw_cell_bg_color then -- In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is -- always followed by a change of clipping rectangle we perform the smallest overwrite possible here. if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then ImVec4_Copy(window.DrawList._CmdHeader.ClipRect, table_.Bg0ClipRectForDrawCmd:ToVec4()) end table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_BG0) end -- Draw row background -- We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle if bg_col0 ~= 0 or bg_col1 ~= 0 then local row_rect = ImRect(table_.WorkRect.Min.x, bg_y1, table_.WorkRect.Max.x, bg_y2) row_rect:ClipWith(table_.BgClipRect) if bg_col0 ~= 0 and row_rect.Min.y < row_rect.Max.y then window.DrawList:AddRectFilled(row_rect.Min, row_rect.Max, bg_col0) end if bg_col1 ~= 0 and row_rect.Min.y < row_rect.Max.y then window.DrawList:AddRectFilled(row_rect.Min, row_rect.Max, bg_col1) end end -- Draw cell background color if draw_cell_bg_color then for cell_n = 0, table_.RowCellDataCurrent do local cell_data = table_.RowCellData[cell_n] -- As we render the BG here we need to clip things (for layout we would not) local column = table_.Columns[cell_data.Column] local cell_bg_rect = ImGui.TableGetCellBgRect(table_, cell_data.Column) cell_bg_rect:ClipWith(table_.BgClipRect) cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column.ClipRect.Min.x) -- So that first column after frozen one gets clipped when scrolling cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column.MaxX) if cell_bg_rect.Min.y < cell_bg_rect.Max.y then window.DrawList:AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data.BgColor) end end end -- Draw top border if top_border_col ~= 0 and bg_y1 >= table_.BgClipRect.Min.y and bg_y1 < table_.BgClipRect.Max.y then window.DrawList:AddLineH(table_.BorderX1, table_.BorderX2, bg_y1, top_border_col, border_size) end -- Draw bottom border at the row unfreezing mark (always strong) if draw_strong_bottom_border and bg_y2 >= table_.BgClipRect.Min.y and bg_y2 < table_.BgClipRect.Max.y then window.DrawList:AddLineH(table_.BorderX1, table_.BorderX2, bg_y2, table_.BorderColorStrong, border_size) end end -- End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) if unfreeze_rows_request then IM_ASSERT(table_.FreezeRowsRequest > 0) for column_n = 0, table_.ColumnsCount - 1 do table_.Columns[column_n].NavLayerCurrent = table_.NavLayer end local y0 = ImMax(table_.RowPosY2 + 1, table_.InnerClipRect.Min.y) table_instance.LastFrozenHeight = y0 - table_.OuterRect.Min.y if unfreeze_rows_actual then IM_ASSERT(table_.IsUnfrozenRows == false) table_.IsUnfrozenRows = true -- BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect local min_y = ImMin(y0, table_.InnerClipRect.Max.y) table_.BgClipRect.Min.y = min_y; table_.Bg2ClipRectForDrawCmd.Min.y = min_y table_.BgClipRect.Max.y = table_.InnerClipRect.Max.y; table_.Bg2ClipRectForDrawCmd.Max.y = table_.InnerClipRect.Max.y table_.Bg2DrawChannelCurrent = table_.Bg2DrawChannelUnfrozen IM_ASSERT(table_.Bg2ClipRectForDrawCmd.Min.y <= table_.Bg2ClipRectForDrawCmd.Max.y) local row_height = table_.RowPosY2 - table_.RowPosY1 table_.RowPosY2 = table_.WorkRect.Min.y + table_.RowPosY2 - table_.OuterRect.Min.y window.DC.CursorPos.y = table_.RowPosY2 table_.RowPosY1 = table_.RowPosY2 - row_height for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] column.DrawChannelCurrent = column.DrawChannelUnfrozen column.ClipRect.Min.y = table_.Bg2ClipRectForDrawCmd.Min.y end -- Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y ImGui.SetWindowClipRectBeforeSetChannel(window, table_.Columns[0].ClipRect) table_.DrawSplitter:SetCurrentChannel(window.DrawList, table_.Columns[0].DrawChannelCurrent) end end if band(table_.RowFlags, ImGuiTableRowFlags.Headers) == 0 then table_.RowBgColorCounter = table_.RowBgColorCounter + 1 end table_.IsInsideRow = false end ---------------------------------------------------------------- -- [SECTION] Tables: Columns changes ---------------------------------------------------------------- function ImGui.TableGetColumnIndex() local g = GImGui local table_ = g.CurrentTable if not table_ then return 0 end return table_.CurrentColumn end -- [Public] Append into a specific column function ImGui.TableSetColumnIndex(column_n) local g = GImGui local table_ = g.CurrentTable if not table_ then return false end if table_.CurrentColumn ~= column_n then if table_.CurrentColumn ~= -1 then ImGui.TableEndCell(table_) end if not (column_n >= 0 and column_n < table_.ColumnsCount) then IM_ASSERT_USER_ERROR(false, "TableSetColumnIndex() invalid column index!") return false end ImGui.TableBeginCell(table_, column_n) end -- Return whether the column is visible. User may choose to skip submitting items based on this return value, -- however they shouldn't skip submitting for columns that may have the tallest contribution to row height. return table_.Columns[column_n].IsRequestOutput end -- [Public] Append into the next column, wrap and create a new row when already on last column function ImGui.TableNextColumn() local g = GImGui local table_ = g.CurrentTable if not table_ then return false end if table_.IsInsideRow and table_.CurrentColumn + 1 < table_.ColumnsCount then if table_.CurrentColumn ~= -1 then ImGui.TableEndCell(table_) end ImGui.TableBeginCell(table_, table_.CurrentColumn + 1) else ImGui.TableNextRow() ImGui.TableBeginCell(table_, 0) end -- Return whether the column is visible. User may choose to skip submitting items based on this return value, -- however they shouldn't skip submitting for columns that may have the tallest contribution to row height. return table_.Columns[table_.CurrentColumn].IsRequestOutput end -- [Internal] Called by TableSetColumnIndex()/TableNextColumn() -- This is called very frequently, so we need to be mindful of unnecessary overhead. function ImGui.TableBeginCell(table_, column_n) local g = GImGui local column = table_.Columns[column_n] local window = table_.InnerWindow table_.CurrentColumn = column_n -- Start position is roughly ~~ CellRect.Min + CellPadding + Indent local start_x = column.WorkMinX if band(column.Flags, ImGuiTableColumnFlags.IndentEnable) ~= 0 then start_x = start_x + table_.RowIndentOffsetX -- ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row. end window.DC.CursorPos.x = start_x window.DC.CursorPos.y = table_.RowPosY1 + table_.RowCellPaddingY window.DC.CursorMaxPos.x = window.DC.CursorPos.x window.DC.ColumnsOffset.x = start_x - window.Pos.x - window.DC.Indent.x -- FIXME-WORKRECT window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x -- PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns. window.DC.CurrLineTextBaseOffset = table_.RowTextBaseline window.DC.NavLayerCurrent = column.NavLayerCurrent -- Note how WorkRect.Max.y is only set once during layout window.WorkRect.Min.y = window.DC.CursorPos.y window.WorkRect.Min.x = column.WorkMinX window.WorkRect.Max.x = column.WorkMaxX window.DC.ItemWidth = column.ItemWidth window.SkipItems = column.IsSkipItems if column.IsSkipItems then g.LastItemData.ID = 0 g.LastItemData.StatusFlags = 0 end -- Also see TablePushColumnChannel() if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then -- FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_NOCLIP) else -- FIXME-TABLE: Could avoid this if draw channel is dummy channel? ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect) table_.DrawSplitter:SetCurrentChannel(window.DrawList, column.DrawChannelCurrent) end -- Logging if g.LogEnabled and not column.IsSkipItems then ImGui.LogRenderedText(window.DC.CursorPos, "|") g.LogLinePosY = FLT_MAX end end -- [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn() function ImGui.TableEndCell(table_) local column = table_.Columns[table_.CurrentColumn] local window = table_.InnerWindow if window.DC.IsSetPos then ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries() end -- Report maximum position so we can infer content size per column. local x = window.DC.CursorMaxPos.x if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then column.ContentMaxXHeadersUsed = ImMax(column.ContentMaxXHeadersUsed, x) -- Useful in case user submit contents in header row that is not a TableHeader() call elseif table_.IsUnfrozenRows then column.ContentMaxXUnfrozen = ImMax(column.ContentMaxXUnfrozen, x) else column.ContentMaxXFrozen = ImMax(column.ContentMaxXFrozen, x) end if column.IsEnabled then table_.RowPosY2 = ImMax(table_.RowPosY2, window.DC.CursorMaxPos.y + table_.RowCellPaddingY) end column.ItemWidth = window.DC.ItemWidth -- Propagate text baseline for the entire row table_.RowTextBaseline = ImMax(table_.RowTextBaseline, window.DC.PrevLineTextBaseOffset) end ---------------------------------------------------------------- -- [SECTION] Tables: Columns width management ---------------------------------------------------------------- -- Maximum column content width given current layout. Use column->MinX so this value differs on a per-column basis. function ImGui.TableCalcMaxColumnWidth(table_, column_n) local column = table_.Columns[column_n] local max_width = FLT_MAX local min_column_distance = table_.MinColumnWidth + table_.CellPaddingX * 2.0 + table_.CellSpacingX1 + table_.CellSpacingX2 if band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 then -- Frozen columns can't reach beyond visible width else scrolling will naturally break. if column.DisplayOrder < table_.FreezeColumnsRequest then max_width = (table_.InnerClipRect.Max.x - (table_.FreezeColumnsRequest - column.DisplayOrder) * min_column_distance) - column.MinX max_width = max_width - table_.OuterPaddingX - table_.CellPaddingX - table_.CellSpacingX2 end elseif band(table_.Flags, ImGuiTableFlags.NoKeepColumnsVisible) == 0 then -- If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. max_width = table_.WorkRect.Max.x - (table_.ColumnsEnabledCount - column.IndexWithinEnabledSet - 1) * min_column_distance - column.MinX max_width = max_width - table_.CellSpacingX2 max_width = max_width - table_.CellPaddingX * 2.0 max_width = max_width - table_.OuterPaddingX end return max_width end -- Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field function ImGui.TableGetColumnWidthAuto(table_, column) local content_width_body = ImMax(column.ContentMaxXFrozen, column.ContentMaxXUnfrozen) - column.WorkMinX local content_width_headers = column.ContentMaxXHeadersIdeal - column.WorkMinX local width_auto = content_width_body if band(column.Flags, ImGuiTableColumnFlags.NoHeaderWidth) == 0 then width_auto = ImMax(width_auto, content_width_headers) end -- Non-resizable fixed columns preserve their requested width if band(column.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 and column.InitStretchWeightOrWidth > 0.0 then if band(table_.Flags, ImGuiTableFlags.Resizable) == 0 or band(column.Flags, ImGuiTableColumnFlags.NoResize) ~= 0 then width_auto = column.InitStretchWeightOrWidth end end return ImMax(width_auto, table_.MinColumnWidth) end -- 'width' = inner column width, without padding function ImGui.TableSetColumnWidth(column_n, width) local g = GImGui local table_ = g.CurrentTable IM_ASSERT(table_ ~= nil and table_.IsLayoutLocked == false) IM_ASSERT(column_n >= 0 and column_n < table_.ColumnsCount) local column_0 = table_.Columns[column_n] local column_0_width = width -- Apply constraints early IM_ASSERT(table_.MinColumnWidth > 0.0) local min_width = table_.MinColumnWidth local max_width = ImMax(min_width, column_0.WidthMax) -- Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933) column_0_width = ImClamp(column_0_width, min_width, max_width) if column_0.WidthGiven == column_0_width or column_0.WidthRequest == column_0_width then return end local column_1 = (column_0.NextEnabledColumn ~= -1) and table_.Columns[column_0.NextEnabledColumn] or nil -- If we have all Fixed columns OR resizing a Fixed column that doesn't come after a Stretch one, we can do an offsetting resize. -- This is the preferred resize path if band(column_0.Flags, ImGuiTableColumnFlags.WidthFixed) ~= 0 then if not column_1 or table_.LeftMostStretchedColumn == -1 or table_.Columns[table_.LeftMostStretchedColumn].DisplayOrder >= column_0.DisplayOrder then column_0.WidthRequest = column_0_width table_.IsSettingsDirty = true return end end -- We can also use previous column if there's no next one (this is used when doing an auto-fit on the right-most stretch column) if column_1 == nil then column_1 = (column_0.PrevEnabledColumn ~= -1) and table_.Columns[column_0.PrevEnabledColumn] or nil end if column_1 == nil then return end -- Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column. -- (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) local column_1_width = ImMax(column_1.WidthRequest - (column_0_width - column_0.WidthRequest), min_width) column_0_width = column_0.WidthRequest + column_1.WidthRequest - column_1_width IM_ASSERT(column_0_width > 0.0 and column_1_width > 0.0) column_0.WidthRequest = column_0_width column_1.WidthRequest = column_1_width if band(bor(column_0.Flags, column_1.Flags), ImGuiTableColumnFlags.WidthStretch) ~= 0 then ImGui.TableUpdateColumnsWeightFromWidth(table_) end table_.IsSettingsDirty = true end -- Disable clipping then auto-fit, will take 2 frames function ImGui.TableSetColumnWidthAutoSingle(table_, column_n) -- Single auto width uses auto-fit local column = table_.Columns[column_n] if not column.IsEnabled then return end column.CannotSkipItemsQueue = lshift(1, 0) table_.AutoFitSingleColumn = column_n end function ImGui.TableSetColumnWidthAutoAll(table_) for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.IsEnabled or band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then -- Cannot reset weight of hidden stretch column column.CannotSkipItemsQueue = lshift(1, 0) column.AutoFitQueue = lshift(1, 1) end end end function ImGui.TableUpdateColumnsWeightFromWidth(table_) IM_ASSERT(table_.LeftMostStretchedColumn ~= -1 and table_.RightMostStretchedColumn ~= -1) -- Measure existing quantities local visible_weight = 0.0 local visible_width = 0.0 for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.IsEnabled and band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then IM_ASSERT(column.StretchWeight > 0.0) visible_weight = visible_weight + column.StretchWeight visible_width = visible_width + column.WidthRequest end end IM_ASSERT(visible_weight > 0.0 and visible_width > 0.0) -- Apply new weights for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.IsEnabled and band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then column.StretchWeight = (column.WidthRequest / visible_width) * visible_weight IM_ASSERT(column.StretchWeight > 0.0) end end end ---------------------------------------------------------------- -- [SECTION] Tables: Drawing ---------------------------------------------------------------- -- Bg2 is used by Selectable (and possibly other widgets) to render to the background. function ImGui.TablePushBackgroundChannel() local g = GImGui local window = g.CurrentWindow local table_ = g.CurrentTable -- Optimization: avoid SetCurrentChannel() + PushClipRect() ImRect_Copy(table_.HostBackupInnerClipRect, window.ClipRect) ImGui.SetWindowClipRectBeforeSetChannel(window, table_.Bg2ClipRectForDrawCmd) table_.DrawSplitter:SetCurrentChannel(window.DrawList, table_.Bg2DrawChannelCurrent) end function ImGui.TablePopBackgroundChannel() local g = GImGui local window = g.CurrentWindow local table_ = g.CurrentTable -- Optimization: avoid PopClipRect() + SetCurrentChannel() ImGui.SetWindowClipRectBeforeSetChannel(window, table_.HostBackupInnerClipRect) table_.DrawSplitter:SetCurrentChannel(window.DrawList, table_.Columns[table_.CurrentColumn].DrawChannelCurrent) end -- Also see TableBeginCell() function ImGui.TablePushColumnChannel(column_n) local g = GImGui local table_ = g.CurrentTable -- Optimization: avoid SetCurrentChannel() + PushClipRect() if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then return end local window = g.CurrentWindow local column = table_.Columns[column_n] ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect) table_.DrawSplitter:SetCurrentChannel(window.DrawList, column.DrawChannelCurrent) end function ImGui.TablePopColumnChannel() local g = GImGui local table_ = g.CurrentTable -- Optimization: avoid PopClipRect() + SetCurrentChannel() if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 or (table_.CurrentColumn == -1) then -- Calling TreePop() after TableNextRow() is supported. return end local window = g.CurrentWindow local column = table_.Columns[table_.CurrentColumn] ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect) table_.DrawSplitter:SetCurrentChannel(window.DrawList, column.DrawChannelCurrent) end -- Allocate draw channels. Called by TableUpdateLayout() function ImGui.TableSetupDrawChannels(table_) local freeze_row_multiplier = (table_.FreezeRowsCount > 0) and 2 or 1 local channels_for_row = (band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0) and 1 or table_.ColumnsEnabledCount local channels_for_bg = 1 + 1 * freeze_row_multiplier local masks_differ = false for n = 0, table_.ColumnsCount - 1 do if (table_.VisibleMaskByIndex[n] == true) ~= (table_.EnabledMaskByIndex[n] == true) then masks_differ = true break end end local channels_for_dummy = (table_.ColumnsEnabledCount < table_.ColumnsCount or masks_differ) and 1 or 0 local channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy table_.DrawSplitter:Split(table_.InnerWindow.DrawList, channels_total) table_.DummyDrawChannel = (channels_for_dummy > 0) and (channels_total - 1) or -1 table_.Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN table_.Bg2DrawChannelUnfrozen = (table_.FreezeRowsCount > 0) and (2 + channels_for_row) or TABLE_DRAW_CHANNEL_BG2_FROZEN local draw_channel_current = 2 for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.IsVisibleX and column.IsVisibleY then column.DrawChannelFrozen = draw_channel_current column.DrawChannelUnfrozen = draw_channel_current + ((table_.FreezeRowsCount > 0) and (channels_for_row + 1) or 0) if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then draw_channel_current = draw_channel_current + 1 end else column.DrawChannelFrozen = table_.DummyDrawChannel column.DrawChannelUnfrozen = table_.DummyDrawChannel end column.DrawChannelCurrent = column.DrawChannelFrozen end -- Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default. ImRect_Copy(table_.BgClipRect, table_.InnerClipRect) ImRect_Copy(table_.Bg0ClipRectForDrawCmd, table_.OuterWindow.ClipRect) ImRect_Copy(table_.Bg2ClipRectForDrawCmd, table_.HostClipRect) IM_ASSERT(table_.BgClipRect.Min.y <= table_.BgClipRect.Max.y) end -- This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). -- (Lua: splitter channel n lives in splitter._Channels.Data[n + 1]; bit arrays are Lua sets) function ImGui.TableMergeDrawChannels(table_) local splitter = table_.DrawSplitter local channels = splitter._Channels.Data local has_freeze_v = (table_.FreezeRowsCount > 0) local has_freeze_h = (table_.FreezeColumnsCount > 0) IM_ASSERT(splitter._Current == 0) -- Track which groups we are going to attempt to merge, and which channels goes into each group. local merge_group_mask = 0x00 local merge_groups = {} for n = 0, 3 do merge_groups[n] = { ClipRect = ImRect(), ChannelsCount = 0, ChannelsMask = {} } end local max_draw_channels = (4 + table_.ColumnsCount * 2) local remaining_mask = {} -- 1. Scan channels and take note of those which can be merged for column_n = 0, table_.ColumnsCount - 1 do if BitTest(table_.VisibleMaskByIndex, column_n) then local column = table_.Columns[column_n] local merge_group_sub_count = has_freeze_v and 2 or 1 for merge_group_sub_n = 0, merge_group_sub_count - 1 do repeat local channel_no = (merge_group_sub_n == 0) and column.DrawChannelFrozen or column.DrawChannelUnfrozen -- Don't attempt to merge if there are multiple draw calls within the column local src_channel = channels[channel_no + 1] local cmds = src_channel._CmdBuffer if cmds.Size > 0 and cmds.Data[cmds.Size].ElemCount == 0 and cmds.Data[cmds.Size].UserCallback == nil then -- Equivalent of PopUnusedDrawCmd() cmds:pop_back() end if cmds.Size ~= 1 then break end -- Find out the width of this merge group and check if it will fit in our column if band(column.Flags, ImGuiTableColumnFlags.NoClip) == 0 then local content_max_x if not has_freeze_v then content_max_x = ImMax(column.ContentMaxXUnfrozen, column.ContentMaxXHeadersUsed) -- No row freeze elseif merge_group_sub_n == 0 then content_max_x = ImMax(column.ContentMaxXFrozen, column.ContentMaxXHeadersUsed) -- Row freeze: use width before freeze else content_max_x = column.ContentMaxXUnfrozen -- Row freeze: use width after freeze end if content_max_x > column.ClipRect.Max.x then break end end local merge_group_n = ((has_freeze_h and column_n < table_.FreezeColumnsCount) and 0 or 1) + ((has_freeze_v and merge_group_sub_n == 0) and 0 or 2) IM_ASSERT(channel_no < max_draw_channels) local merge_group = merge_groups[merge_group_n] if merge_group.ChannelsCount == 0 then merge_group.ClipRect = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) end merge_group.ChannelsMask[channel_no] = true merge_group.ChannelsCount = merge_group.ChannelsCount + 1 local cr = cmds.Data[1].ClipRect merge_group.ClipRect:Add(ImRect(cr.x, cr.y, cr.z, cr.w)) merge_group_mask = bor(merge_group_mask, lshift(1, merge_group_n)) until true end -- Invalidate current draw channel column.DrawChannelCurrent = -1 end end -- 2. Rewrite channel list in our preferred order if merge_group_mask ~= 0 then -- We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels(). local LEADING_DRAW_CHANNELS = 2 local dst_tmp = {} for n = LEADING_DRAW_CHANNELS, splitter._Count - 1 do remaining_mask[n] = true end remaining_mask[table_.Bg2DrawChannelUnfrozen] = nil IM_ASSERT(has_freeze_v == false or table_.Bg2DrawChannelUnfrozen ~= TABLE_DRAW_CHANNEL_BG2_FROZEN) local remaining_count = splitter._Count - (has_freeze_v and (LEADING_DRAW_CHANNELS + 1) or LEADING_DRAW_CHANNELS) local host_rect = table_.HostClipRect for merge_group_n = 0, 3 do local merge_channels_count = merge_groups[merge_group_n].ChannelsCount if merge_channels_count ~= 0 then local merge_group = merge_groups[merge_group_n] local merge_clip_rect = Rect(merge_group.ClipRect) -- Extend outer-most clip limits to match those of host, so draw calls can be merged even if -- outer-most columns have some outer padding offsetting them from their parent ClipRect. if band(merge_group_n, 1) == 0 or not has_freeze_h then merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x) end if band(merge_group_n, 2) == 0 or not has_freeze_v then merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y) end if band(merge_group_n, 1) ~= 0 then merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x) end if band(merge_group_n, 2) ~= 0 and band(table_.Flags, ImGuiTableFlags.NoHostExtendY) == 0 then merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y) end remaining_count = remaining_count - merge_group.ChannelsCount for n in pairs(merge_group.ChannelsMask) do remaining_mask[n] = nil end local n = 0 while n < splitter._Count and merge_channels_count ~= 0 do -- Copy + overwrite new clip rect if merge_group.ChannelsMask[n] then merge_group.ChannelsMask[n] = nil merge_channels_count = merge_channels_count - 1 local channel = channels[n + 1] IM_ASSERT(channel._CmdBuffer.Size == 1) ImVec4_Copy(channel._CmdBuffer.Data[1].ClipRect, merge_clip_rect:ToVec4()) dst_tmp[#dst_tmp + 1] = channel end n = n + 1 end end -- Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1) if merge_group_n == 1 and has_freeze_v then dst_tmp[#dst_tmp + 1] = channels[table_.Bg2DrawChannelUnfrozen + 1] end end -- Append unmergeable channels that we didn't reorder at the end of the list local n = 0 while n < splitter._Count and remaining_count ~= 0 do if remaining_mask[n] then dst_tmp[#dst_tmp + 1] = channels[n + 1] remaining_count = remaining_count - 1 end n = n + 1 end IM_ASSERT(#dst_tmp == splitter._Count - LEADING_DRAW_CHANNELS) for i = 1, #dst_tmp do channels[LEADING_DRAW_CHANNELS + i] = dst_tmp[i] end end end local function TableGetColumnBorderCol(table_, order_n, column_n) local is_hovered = (table_.HoveredColumnBorder == column_n) local is_resized = (table_.ResizedColumn == column_n) and (table_.InstanceInteracted == table_.InstanceCurrent) local is_frozen_separator = (table_.FreezeColumnsCount == order_n + 1) if is_resized or is_hovered then return ImGui.GetColorU32(is_resized and ImGuiCol.SeparatorActive or ImGuiCol.SeparatorHovered) end if is_frozen_separator or band(table_.Flags, bor(ImGuiTableFlags.NoBordersInBody, ImGuiTableFlags.NoBordersInBodyUntilResize)) ~= 0 then return table_.BorderColorStrong end return table_.BorderColorLight end -- FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) function ImGui.TableDrawBorders(table_) local inner_window = table_.InnerWindow if not table_.OuterWindow.ClipRect:Overlaps(table_.OuterRect) then return end local inner_drawlist = inner_window.DrawList table_.DrawSplitter:SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0) inner_drawlist:PushClipRect(table_.Bg0ClipRectForDrawCmd.Min, table_.Bg0ClipRectForDrawCmd.Max, false) -- Draw inner border and resizing feedback local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local border_size = TABLE_BORDER_SIZE local draw_y1 = ImMax(table_.InnerRect.Min.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight) + ((band(table_.Flags, ImGuiTableFlags.BordersOuterH) ~= 0) and border_size or 0.0) local draw_y2_body = table_.InnerRect.Max.y local draw_y2_head = table_.IsUsingHeaders and ImMin(table_.InnerRect.Max.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_instance.LastTopHeadersRowHeight) or draw_y1 if band(table_.Flags, ImGuiTableFlags.BordersInnerV) ~= 0 then for order_n = 0, table_.ColumnsCount - 1 do repeat if not BitTest(table_.EnabledMaskByDisplayOrder, order_n) then break end local column_n = table_.DisplayOrderToIndex[order_n] local column = table_.Columns[column_n] local is_hovered = (table_.HoveredColumnBorder == column_n) local is_resized = (table_.ResizedColumn == column_n) and (table_.InstanceInteracted == table_.InstanceCurrent) local is_resizable = band(column.Flags, bor(ImGuiTableColumnFlags.NoResize, ImGuiTableColumnFlags.NoDirectResize_)) == 0 local is_frozen_separator = (table_.FreezeColumnsCount == order_n + 1) if column.MaxX > table_.InnerClipRect.Max.x and not is_resized then break end -- Decide whether right-most column is visible if column.NextEnabledColumn == -1 and not is_resizable then if band(table_.Flags, ImGuiTableFlags.SizingMask_) ~= ImGuiTableFlags.SizingFixedSame or band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then break end end if column.MaxX <= column.ClipRect.Min.x then -- FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. break end -- Draw in outer window so right-most column won't be clipped local draw_y2 = draw_y2_head if is_frozen_separator then draw_y2 = draw_y2_body elseif band(table_.Flags, ImGuiTableFlags.NoBordersInBodyUntilResize) ~= 0 and (is_hovered or is_resized) then draw_y2 = draw_y2_body elseif band(table_.Flags, bor(ImGuiTableFlags.NoBordersInBodyUntilResize, ImGuiTableFlags.NoBordersInBody)) == 0 then draw_y2 = draw_y2_body end if draw_y2 > draw_y1 then inner_drawlist:AddLineV(column.MaxX, draw_y1, draw_y2, TableGetColumnBorderCol(table_, order_n, column_n), border_size) end until true end end -- Draw outer border if band(table_.Flags, ImGuiTableFlags.BordersOuter) ~= 0 then local outer_border = table_.OuterRect local outer_col = table_.BorderColorStrong if band(table_.Flags, ImGuiTableFlags.BordersOuter) == ImGuiTableFlags.BordersOuter then inner_drawlist:AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0, border_size) elseif band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0 then inner_drawlist:AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size) inner_drawlist:AddLineV(outer_border.Max.x - border_size, outer_border.Min.y, outer_border.Max.y, outer_col, border_size) elseif band(table_.Flags, ImGuiTableFlags.BordersOuterH) ~= 0 then inner_drawlist:AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size) inner_drawlist:AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y - border_size, outer_col, border_size) end end if band(table_.Flags, ImGuiTableFlags.BordersInnerH) ~= 0 and table_.RowPosY2 < table_.OuterRect.Max.y then -- Draw bottom-most row border between it is above outer border. local h_inset = (band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0) and border_size or 0.0 local border_y = table_.RowPosY2 if border_y >= table_.BgClipRect.Min.y and border_y < table_.BgClipRect.Max.y then inner_drawlist:AddLineH(table_.BorderX1 + h_inset, table_.BorderX2 - h_inset, border_y, table_.BorderColorLight, border_size) end end inner_drawlist:PopClipRect() end ---------------------------------------------------------------- -- [SECTION] Tables: Sorting ---------------------------------------------------------------- -- Return nil if no sort specs (most often when ImGuiTableFlags_Sortable is not set) -- When 'sort_specs.SpecsDirty == true' you should sort your data. Specs is a 1-based Lua array. function ImGui.TableGetSortSpecs() local g = GImGui local table_ = g.CurrentTable if table_ == nil or band(table_.Flags, ImGuiTableFlags.Sortable) == 0 then return nil end -- Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths. if not table_.IsLayoutLocked then ImGui.TableUpdateLayout(table_) end ImGui.TableSortSpecsBuild(table_) return table_.SortSpecs end local function TableGetColumnAvailSortDirection(column, n) IM_ASSERT(n < column.SortDirectionsAvailCount) return band(rshift(column.SortDirectionsAvailList, lshift(n, 1)), 0x03) end -- Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending) function ImGui.TableFixColumnSortDirection(table_, column) if column.SortOrder == -1 or band(column.SortDirectionsAvailMask, lshift(1, column.SortDirection)) ~= 0 then return end column.SortDirection = TableGetColumnAvailSortDirection(column, 0) table_.IsSortSpecsDirty = true end -- Calculate next sort direction that would be set after clicking the column function ImGui.TableGetColumnNextSortDirection(column) IM_ASSERT(column.SortDirectionsAvailCount > 0) if column.SortOrder == -1 then return TableGetColumnAvailSortDirection(column, 0) end for n = 0, 2 do if column.SortDirection == TableGetColumnAvailSortDirection(column, n) then return TableGetColumnAvailSortDirection(column, (n + 1) % column.SortDirectionsAvailCount) end end IM_ASSERT(false) return ImGuiSortDirection.None end -- Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert -- the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. function ImGui.TableSetColumnSortDirection(column_n, sort_direction, append_to_sort_specs) local g = GImGui local table_ = g.CurrentTable if band(table_.Flags, ImGuiTableFlags.SortMulti) == 0 then append_to_sort_specs = false end if band(table_.Flags, ImGuiTableFlags.SortTristate) == 0 then IM_ASSERT(sort_direction ~= ImGuiSortDirection.None) end local sort_order_max = 0 if append_to_sort_specs then for other_column_n = 0, table_.ColumnsCount - 1 do sort_order_max = ImMax(sort_order_max, table_.Columns[other_column_n].SortOrder) end end local column = table_.Columns[column_n] column.SortDirection = sort_direction if column.SortDirection == ImGuiSortDirection.None then column.SortOrder = -1 elseif column.SortOrder == -1 or not append_to_sort_specs then column.SortOrder = append_to_sort_specs and (sort_order_max + 1) or 0 end for other_column_n = 0, table_.ColumnsCount - 1 do local other_column = table_.Columns[other_column_n] if other_column ~= column and not append_to_sort_specs then other_column.SortOrder = -1 end ImGui.TableFixColumnSortDirection(table_, other_column) end table_.IsSettingsDirty = true table_.IsSortSpecsDirty = true end function ImGui.TableSortSpecsSanitize(table_) IM_ASSERT(band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0) -- Clear SortOrder from hidden column and verify that there's no gap or duplicate. local sort_order_count = 0 local sort_order_set = {} -- C++: ImU64 sort_order_mask local sort_order_dup = false for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.SortOrder ~= -1 and not column.IsEnabled then column.SortOrder = -1 end if column.SortOrder ~= -1 then sort_order_count = sort_order_count + 1 if sort_order_set[column.SortOrder] then sort_order_dup = true end sort_order_set[column.SortOrder] = true IM_ASSERT(sort_order_count < 64) end end -- need_fix_linearize: (1 << count) != (mask + 1) <=> the set of sort orders isn't exactly {0..count-1} local need_fix_linearize = sort_order_dup for n = 0, sort_order_count - 1 do if not sort_order_set[n] then need_fix_linearize = true end end local need_fix_single_sort_order = (sort_order_count > 1) and band(table_.Flags, ImGuiTableFlags.SortMulti) == 0 if need_fix_linearize or need_fix_single_sort_order then local fixed_mask = {} for sort_n = 0, sort_order_count - 1 do -- Fix: Rewrite sort order fields if needed so they have no gap or duplicate. local column_with_smallest_sort_order = -1 for column_n = 0, table_.ColumnsCount - 1 do if not fixed_mask[column_n] and table_.Columns[column_n].SortOrder ~= -1 then if column_with_smallest_sort_order == -1 or table_.Columns[column_n].SortOrder < table_.Columns[column_with_smallest_sort_order].SortOrder then column_with_smallest_sort_order = column_n end end end IM_ASSERT(column_with_smallest_sort_order ~= -1) fixed_mask[column_with_smallest_sort_order] = true table_.Columns[column_with_smallest_sort_order].SortOrder = sort_n -- Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. if need_fix_single_sort_order then sort_order_count = 1 for column_n = 0, table_.ColumnsCount - 1 do if column_n ~= column_with_smallest_sort_order then table_.Columns[column_n].SortOrder = -1 end end break end end end -- Fallback default sort order (if no column with the ImGuiTableColumnFlags_DefaultSort flag) if sort_order_count == 0 and band(table_.Flags, ImGuiTableFlags.SortTristate) == 0 then for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.IsEnabled and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then sort_order_count = 1 column.SortOrder = 0 column.SortDirection = TableGetColumnAvailSortDirection(column, 0) break end end end table_.SortSpecsCount = sort_order_count end function ImGui.TableSortSpecsBuild(table_) local dirty = table_.IsSortSpecsDirty if dirty then ImGui.TableSortSpecsSanitize(table_) local multi = table_.SortSpecsMulti local want = (table_.SortSpecsCount <= 1) and 0 or table_.SortSpecsCount for n = #multi + 1, want do multi[n] = ImGuiTableColumnSortSpecs() end for n = #multi, want + 1, -1 do multi[n] = nil end table_.SortSpecs.SpecsDirty = true -- Mark as dirty for user table_.IsSortSpecsDirty = false -- Mark as not dirty for us end -- Write output local sort_specs if table_.SortSpecsCount == 0 then sort_specs = nil elseif table_.SortSpecsCount == 1 then table_.SortSpecsSingleArray = table_.SortSpecsSingleArray or { table_.SortSpecsSingle } sort_specs = table_.SortSpecsSingleArray else sort_specs = table_.SortSpecsMulti end if dirty and sort_specs ~= nil then for column_n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[column_n] if column.SortOrder ~= -1 then IM_ASSERT(column.SortOrder < table_.SortSpecsCount) local sort_spec = sort_specs[column.SortOrder + 1] sort_spec.ColumnUserID = column.UserData sort_spec.ColumnIndex = column_n sort_spec.SortOrder = column.SortOrder sort_spec.SortDirection = column.SortDirection end end end table_.SortSpecs.Specs = sort_specs table_.SortSpecs.SpecsCount = table_.SortSpecsCount end ---------------------------------------------------------------- -- [SECTION] Tables: Headers ---------------------------------------------------------------- function ImGui.TableGetHeaderRowHeight() -- Calculate row height, for the unlikely case that some labels may be taller than others. local g = GImGui local table_ = g.CurrentTable local row_height = g.FontSize for column_n = 0, table_.ColumnsCount - 1 do if BitTest(table_.EnabledMaskByIndex, column_n) then if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.NoHeaderLabel) == 0 then row_height = ImMax(row_height, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n)).y) end end end return row_height + g.Style.CellPadding.y * 2.0 end function ImGui.TableGetHeaderAngledMaxLabelWidth() local g = GImGui local table_ = g.CurrentTable local width = 0.0 for column_n = 0, table_.ColumnsCount - 1 do if BitTest(table_.EnabledMaskByIndex, column_n) then if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then width = ImMax(width, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n), nil, true).x) end end end return width + g.Style.CellPadding.y * 2.0 -- Swap padding end -- [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). function ImGui.TableHeadersRow() local g = GImGui local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") -- Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make -- it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. if not table_.IsLayoutLocked then ImGui.TableUpdateLayout(table_) end -- Open row local row_height = ImGui.TableGetHeaderRowHeight() ImGui.TableNextRow(ImGuiTableRowFlags.Headers, row_height) local row_y1 = ImGui.GetCursorScreenPos().y if table_.HostSkipItems then -- Merely an optimization, you may skip in your own code. return end local columns_count = ImGui.TableGetColumnCount() for column_n = 0, columns_count - 1 do if ImGui.TableSetColumnIndex(column_n) or table_.LastHeldHeaderColumn == column_n then -- Push an id to allow empty/unnamed headers. This is also idiomatic as it ensure there is a consistent ID path to access columns (for e.g. automation) local name = (band(ImGui.TableGetColumnFlags(column_n), ImGuiTableColumnFlags.NoHeaderLabel) ~= 0) and "" or ImGui.TableGetColumnName(column_n) ImGui.PushID(column_n) ImGui.TableHeader(name) ImGui.PopID() end end -- Allow opening popup from the right-most section after the last column. local mouse_pos = ImGui.GetMousePos() if ImGui.IsMouseReleased(1) and ImGui.TableGetHoveredColumn() == columns_count then if mouse_pos.y >= row_y1 and mouse_pos.y < row_y1 + row_height then ImGui.TableOpenContextMenu(columns_count) -- Will open a non-column-specific popup. end end end -- Emit a column header (text + optional sort order) -- We cpu-clip text here so that all columns headers can be merged into a same draw call. function ImGui.TableHeader(label) local g = GImGui local window = g.CurrentWindow if window.SkipItems then return end local table_ = g.CurrentTable IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") IM_ASSERT(table_.CurrentColumn ~= -1) local column_n = table_.CurrentColumn local column = table_.Columns[column_n] -- Label if label == nil then label = "" end local label_end = ImGui.FindRenderedTextEnd(label) local label_size = ImGui.CalcTextSize(label, label_end, false) local label_pos = V2(window.DC.CursorPos) -- If we already got a row height, there's use that. local cell_r = ImGui.TableGetCellBgRect(table_, column_n) local label_height = ImMax(label_size.y, table_.RowMinHeight - table_.RowCellPaddingY * 2.0) -- Calculate ideal size for sort order arrow local w_arrow = 0.0 local w_sort_text = 0.0 local sort_arrow = false local sort_order_suf = "" local ARROW_SCALE = 0.65 if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then w_arrow = ImTrunc(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x) if column.SortOrder ~= -1 then sort_arrow = true end if column.SortOrder > 0 then sort_order_suf = string.format("%d", column.SortOrder + 1) w_sort_text = g.Style.ItemInnerSpacing.x + ImGui.CalcTextSize(sort_order_suf).x end end -- We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging. local max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow column.ContentMaxXHeadersUsed = ImMax(column.ContentMaxXHeadersUsed, sort_arrow and cell_r.Max.x or ImMin(max_pos_x, cell_r.Max.x)) column.ContentMaxXHeadersIdeal = ImMax(column.ContentMaxXHeadersIdeal, max_pos_x) -- Keep header highlighted when context menu is open. local id = window:GetID(label) local bb = ImRect(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0)) ImGui.ItemSize(ImVec2(0.0, label_height)) -- Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal if not ImGui.ItemAdd(bb, id) then return end -- Using AllowOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items. local highlight = (table_.HighlightColumnHeader == column_n) local pressed, hovered, held = ImGui.ButtonBehavior(bb, id, ImGuiButtonFlags.AllowOverlap) if held or hovered or highlight then local col = ImGui.GetColorU32(held and ImGuiCol.HeaderActive or (hovered and ImGuiCol.HeaderHovered or ImGuiCol.Header)) ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, col, table_.CurrentColumn) else -- Submit single cell bg color in the case we didn't submit a full header row if band(table_.RowFlags, ImGuiTableRowFlags.Headers) == 0 then ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, ImGui.GetColorU32(ImGuiCol.TableHeaderBg), table_.CurrentColumn) end end ImGui.RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags.Compact, 0.0) if held then table_.HeldHeaderColumn = column_n end window.DC.CursorPos.y = window.DC.CursorPos.y - g.Style.ItemSpacing.y * 0.5 -- Drag and drop to re-order columns. if held and band(table_.Flags, ImGuiTableFlags.Reorderable) ~= 0 and ImGui.IsMouseDragging(0) and not g.DragDropActive then table_.InstanceInteracted = table_.InstanceCurrent if g.IO.MouseDelta.x < 0.0 and g.IO.MousePos.x < cell_r.Min.x then local prev_column = (column.PrevEnabledColumn ~= -1) and table_.Columns[column.PrevEnabledColumn] or nil if prev_column then ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, prev_column.DisplayOrder) end end if g.IO.MouseDelta.x > 0.0 and g.IO.MousePos.x > cell_r.Max.x then local next_column = (column.NextEnabledColumn ~= -1) and table_.Columns[column.NextEnabledColumn] or nil if next_column then ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, next_column.DisplayOrder) end end end -- Sort order arrow local ellipsis_max = ImMax(cell_r.Max.x - (sort_arrow and (w_arrow + w_sort_text) or 0.0), label_pos.x) if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then if column.SortOrder ~= -1 then local x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text) local y = label_pos.y if column.SortOrder > 0 then ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.Text, 0.70)) ImGui.RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf) ImGui.PopStyleColor() x = x + w_sort_text end ImGui.RenderArrow(window.DrawList, ImVec2(x, y), ImGui.GetColorU32(ImGuiCol.Text), (column.SortDirection == ImGuiSortDirection.Ascending) and ImGuiDir.Up or ImGuiDir.Down, ARROW_SCALE) end -- Handle clicking on column header to adjust Sort Order if pressed and table_.ReorderColumn ~= column_n then local sort_direction = ImGui.TableGetColumnNextSortDirection(column) ImGui.TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift) end end -- Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will -- be merged into a single draw call. ImGui.RenderTextEllipsis(window.DrawList, label_pos, ImVec2(ellipsis_max, bb.Max.y), ellipsis_max, label, label_end, label_size) local text_clipped = label_size.x > (ellipsis_max - label_pos.x) if text_clipped and hovered and g.ActiveId == 0 then ImGui.SetItemTooltip("%s", string.sub(label, 1, label_end - 1)) end -- We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden if ImGui.IsPopupOpenRequestForItem(ImGuiPopupFlags.None, id) then ImGui.TableOpenContextMenu(column_n) end end -- Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets. function ImGui.TableAngledHeadersRow() local g = GImGui local table_ = g.CurrentTable local temp_data = table_.TempData temp_data.AngledHeadersRequests:resize(0) -- Which column needs highlight? local row_id = ImGui.GetID("##AngledHeaders") local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local highlight_column_n = (table_.LastHeldHeaderColumn ~= -1) and table_.LastHeldHeaderColumn or table_.HighlightColumnHeader if highlight_column_n == -1 and table_.HoveredColumnBody ~= -1 then if table_instance.HoveredRowLast == 0 and table_.HoveredColumnBorder == -1 and (g.ActiveId == 0 or g.ActiveId == row_id or (table_.IsActiveIdInTable or g.DragDropActive)) then highlight_column_n = table_.HoveredColumnBody end end -- Build up request local col_header_bg = ImGui.GetColorU32(ImGuiCol.TableHeaderBg) local col_text = ImGui.GetColorU32(ImGuiCol.Text) for order_n = 0, table_.ColumnsCount - 1 do if BitTest(table_.EnabledMaskByDisplayOrder, order_n) then local column_n = table_.DisplayOrderToIndex[order_n] local column = table_.Columns[column_n] if band(column.Flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then -- Note: can't rely on ImGuiTableColumnFlags_IsVisible test here. temp_data.AngledHeadersRequests:push_back({ Index = column_n, TextColor = col_text, BgColor0 = col_header_bg, BgColor1 = (column_n == highlight_column_n) and ImGui.GetColorU32(ImGuiCol.Header) or 0 }) end end end -- Render row ImGui.TableAngledHeadersRowEx(row_id, g.Style.TableAngledHeadersAngle, 0.0, temp_data.AngledHeadersRequests.Data, temp_data.AngledHeadersRequests.Size) end local function ImTextCountLines(text, text_begin, text_end) local count = 0 local p = text_begin while p < text_end do local line_end = string.find(text, "\n", p, true) if line_end == nil or line_end >= text_end then p = text_end else p = line_end + 1 end count = count + 1 end return count end -- Important: data must be fed left to right --- @param data ImGuiTableHeaderData[] # 1-based array --- @param data_count int function ImGui.TableAngledHeadersRowEx(row_id, angle, max_label_width, data, data_count) local g = GImGui local table_ = g.CurrentTable local window = g.CurrentWindow local draw_list = window.DrawList IM_ASSERT_USER_ERROR_RET(table_ ~= nil, "Call should only be done while in BeginTable() scope!") IM_ASSERT(table_.CurrentRow == -1, "Must be first row") if max_label_width == 0.0 then max_label_width = ImGui.TableGetHeaderAngledMaxLabelWidth() end -- Angle argument expressed in (-IM_PI/2 .. +IM_PI/2) as it is easier to think about for user. local flip_label = (angle < 0.0) angle = angle - IM_PI * 0.5 local cos_a = ImCos(angle) local sin_a = ImSin(angle) local label_cos_a = flip_label and ImCos(angle + IM_PI) or cos_a local label_sin_a = flip_label and ImSin(angle + IM_PI) or sin_a local unit_right = ImVec2(cos_a, sin_a) -- Calculate our base metrics and set angled headers data _before_ the first call to TableNextRow() local header_height = g.FontSize + g.Style.CellPadding.x * 2.0 local row_height = ImTrunc(ImAbs(ImRotate(ImVec2(max_label_width, flip_label and header_height or -header_height), cos_a, sin_a).y)) table_.AngledHeadersHeight = row_height table_.AngledHeadersSlope = (sin_a ~= 0.0) and (cos_a / sin_a) or 0.0 local header_angled_vector = unit_right * (row_height / -sin_a) -- vector from bottom-left to top-left, and from bottom-right to top-right -- Declare row, override and draw our own background ImGui.TableNextRow(ImGuiTableRowFlags.Headers, row_height) ImGui.TableNextColumn() local row_r = ImRect(table_.WorkRect.Min.x, table_.BgClipRect.Min.y, table_.WorkRect.Max.x, table_.RowPosY2) table_.DrawSplitter:SetCurrentChannel(draw_list, TABLE_DRAW_CHANNEL_BG0) local clip_rect_min_x = table_.BgClipRect.Min.x if table_.FreezeColumnsCount > 0 then clip_rect_min_x = ImMax(clip_rect_min_x, table_.Columns[table_.DisplayOrderToIndex[table_.FreezeColumnsCount - 1]].MaxX) end ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, 0) -- Cancel ImGui.PushClipRect(table_.BgClipRect.Min, table_.BgClipRect.Max, false) -- Span all columns draw_list:AddRectFilled(ImVec2(table_.BgClipRect.Min.x, row_r.Min.y), ImVec2(table_.BgClipRect.Max.x, row_r.Max.y), ImGui.GetColorU32(ImGuiCol.TableHeaderBg, 0.25)) -- FIXME-STYLE: Change row background with an arbitrary color. ImGui.PushClipRect(ImVec2(clip_rect_min_x, table_.BgClipRect.Min.y), table_.BgClipRect.Max, true) -- Span all columns ImGui.ButtonBehavior(row_r, row_id) ImGui.KeepAliveID(row_id) local ascent_scaled = g.FontBaked.Ascent * g.FontBakedScale -- FIXME: Standardize those scaling factors better local line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5, 0.0) / -sin_a) * (flip_label and -1.0 or 1.0) local padding = g.Style.CellPadding -- We will always use swapped component local align = g.Style.TableAngledHeadersTextAlign -- Draw background and labels in first pass, then all borders. local max_x = -FLT_MAX for pass = 0, 1 do for order_n = 0, data_count - 1 do local request = data[order_n + 1] local column_n = request.Index local column = table_.Columns[column_n] local bg_shape = {} bg_shape[0] = ImVec2(column.MaxX, row_r.Max.y) bg_shape[1] = ImVec2(column.MinX, row_r.Max.y) bg_shape[2] = bg_shape[1] + header_angled_vector bg_shape[3] = bg_shape[0] + header_angled_vector if pass == 0 then -- Draw shape draw_list:AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request.BgColor0) draw_list:AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request.BgColor1) -- Optional highlight max_x = ImMax(max_x, bg_shape[3].x) -- Draw label local label_name = ImGui.TableGetColumnName(table_, column_n) local label_name_end = ImGui.FindRenderedTextEnd(label_name) local line_off_step_x = (g.FontSize / -sin_a) local label_lines = ImTextCountLines(label_name, 1, label_name_end) -- Left<>Right alignment local line_off_curr_x = flip_label and ((label_lines - 1) * line_off_step_x) or 0.0 local line_off_for_align_x = ImFloor(ImMax((((column.MaxX - column.MinX) - padding.x * 2.0) - (label_lines * line_off_step_x)), 0.0) * align.x) line_off_curr_x = line_off_curr_x + line_off_for_align_x - line_off_for_ascent_x -- Register header width local w = column.WorkMinX + ImCeil(label_lines * line_off_step_x - line_off_for_align_x) column.ContentMaxXHeadersUsed = w; column.ContentMaxXHeadersIdeal = w local p = 1 while p < label_name_end do local label_name_eol = string.find(label_name, "\n", p, true) if label_name_eol == nil or label_name_eol > label_name_end then label_name_eol = label_name_end end local line = string.sub(label_name, p, label_name_eol - 1) -- FIXME: Individual line clipping for right-most column is broken for negative angles. local label_size = ImGui.CalcTextSize(line) local clip_width = max_label_width - padding.y -- Using padding.y*2.0f would be symmetrical but hide more text. local clip_height = ImMin(label_size.y, column.ClipRect.Max.x - column.WorkMinX - line_off_curr_x) local clip_r = ImRect(window.ClipRect.Min, window.ClipRect.Min + ImVec2(clip_width, clip_height)) local vtx_idx_begin = draw_list._VtxCurrentIdx ImGui.PushStyleColor(ImGuiCol.Text, request.TextColor) ImGui.RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, line, #line + 1, label_size) ImGui.PopStyleColor() local vtx_idx_end = draw_list._VtxCurrentIdx -- Up<>Down alignment local available_space = ImMax(clip_width - label_size.x + ImAbs(padding.x * cos_a) * 2.0 - ImAbs(padding.y * sin_a) * 2.0, 0.0) local vertical_offset = available_space * align.y * (flip_label and -1.0 or 1.0) -- Rotate and offset label local pivot_in = ImVec2(window.ClipRect.Min.x - vertical_offset, window.ClipRect.Min.y + label_size.y) local pivot_out = ImVec2(column.WorkMinX, row_r.Max.y) line_off_curr_x = line_off_curr_x + (flip_label and -line_off_step_x or line_off_step_x) pivot_out = pivot_out + unit_right * padding.y if flip_label then pivot_out = pivot_out + unit_right * (clip_width - ImMax(0.0, clip_width - label_size.x)) end pivot_out.x = pivot_out.x + (flip_label and (line_off_curr_x + line_off_step_x) or line_off_curr_x) ImGui.ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out) -- Rotate and offset p = label_name_eol + 1 end end if pass == 1 then -- Draw border local border_size = TABLE_BORDER_SIZE draw_list:AddLine(bg_shape[0] + ImVec2(border_size * 0.5, 0.0), bg_shape[3] + ImVec2(border_size * 0.5, 0.0), TableGetColumnBorderCol(table_, order_n, column_n), border_size) end end end ImGui.PopClipRect() ImGui.PopClipRect() table_.TempData.AngledHeadersExtraWidth = ImMax(0.0, max_x - table_.Columns[table_.RightMostEnabledColumn].MaxX) end ---------------------------------------------------------------- -- [SECTION] Tables: Context Menu ---------------------------------------------------------------- -- Use -1 to open menu not specific to a given column. function ImGui.TableOpenContextMenu(column_n) if column_n == nil then column_n = -1 end local g = GImGui local table_ = g.CurrentTable if column_n == -1 and table_.CurrentColumn ~= -1 then -- When called within a column automatically use this one (for consistency) column_n = table_.CurrentColumn end if column_n == table_.ColumnsCount then -- To facilitate using with TableGetHoveredColumn() column_n = -1 end IM_ASSERT(column_n >= -1 and column_n < table_.ColumnsCount) if band(table_.Flags, bor(ImGuiTableFlags.Resizable, ImGuiTableFlags.Reorderable, ImGuiTableFlags.Hideable)) ~= 0 then table_.IsContextPopupOpen = true table_.ContextPopupColumn = column_n table_.InstanceInteracted = table_.InstanceCurrent local context_menu_id = ImHashStr("##ContextMenu", nil, table_.ID) ImGui.OpenPopupEx(context_menu_id, ImGuiPopupFlags.None) end end function ImGui.TableBeginContextMenuPopup(table_) if not table_.IsContextPopupOpen or table_.InstanceCurrent ~= table_.InstanceInteracted then return false end local context_menu_id = ImHashStr("##ContextMenu", nil, table_.ID) if ImGui.BeginPopupEx(context_menu_id, bor(ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoSavedSettings)) then return true end table_.IsContextPopupOpen = false return false end -- FIXME: Copied from MenuItem() for the purpose of being able to pass _SelectOnRelease (#9312) local function MenuItemForColumnReorder(label, selected, enabled) local g = GImGui local window = g.CurrentWindow local label_size = ImGui.CalcTextSize(label, nil, true) local offsets = window.DC.MenuColumns local checkmark_w = IM_TRUNC(g.FontSize * 1.20) local min_w = offsets:DeclColumns(0.0, label_size.x, 0.0, checkmark_w) -- Feedback for next frame local stretch_w = ImMax(0.0, ImGui.GetContentRegionAvail().x - min_w) local text_pos = ImVec2(window.DC.CursorPos.x, window.DC.CursorPos.y + window.DC.CurrLineTextBaseOffset) local id = ImGui.GetID(label) local selectable_flags = bor(ImGuiSelectableFlags.SelectOnRelease, ImGuiSelectableFlags.SpanAvailWidth) if g.ActiveId == id then selectable_flags = bor(selectable_flags, ImGuiSelectableFlags.Highlight) -- Stays highlighted while dragging. end local has_been_moved = (g.ActiveId == id) and g.ActiveIdHasBeenEditedBefore -- But disable toggling once moved. ImGui.BeginDisabled(not enabled) -- Don't use ImGuiSelectableFlags_Disabled so that Check mark is also affected. local ret = ImGui.Selectable(label, false, selectable_flags, ImVec2(min_w, label_size.y)) and not has_been_moved if band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Visible) ~= 0 and selected then ImGui.RenderCheckMark(window.DrawList, text_pos + ImVec2(offsets.OffsetMark + stretch_w + g.FontSize * 0.40, g.FontSize * 0.134 * 0.5), ImGui.GetColorU32(ImGuiCol.Text), g.FontSize * 0.866) end ImGui.EndDisabled() return ret end -- Output context menu into current window (generally a popup) function ImGui.TableDrawDefaultContextMenu(table_, flags_for_section_to_display) local g = GImGui local window = g.CurrentWindow if window.SkipItems then return end local want_separator = false local context_column_n = (table_.ContextPopupColumn >= 0 and table_.ContextPopupColumn < table_.ColumnsCount) and table_.ContextPopupColumn or -1 local context_column = (context_column_n ~= -1) and table_.Columns[context_column_n] or nil -- Sizing if band(flags_for_section_to_display, ImGuiTableFlags.Resizable) ~= 0 then if context_column ~= nil then local can_resize = band(context_column.Flags, ImGuiTableColumnFlags.NoResize) == 0 and context_column.IsEnabled if ImGui.MenuItem(ImGui.LocalizeGetMsg(ImGuiLocKey.TableSizeOne), nil, false, can_resize) then -- "###SizeOne" ImGui.TableSetColumnWidthAutoSingle(table_, context_column_n) end end local size_all_desc if table_.ColumnsEnabledFixedCount == table_.ColumnsEnabledCount and band(table_.Flags, ImGuiTableFlags.SizingMask_) ~= ImGuiTableFlags.SizingFixedSame then size_all_desc = ImGui.LocalizeGetMsg(ImGuiLocKey.TableSizeAllFit) -- "###SizeAll" All fixed else size_all_desc = ImGui.LocalizeGetMsg(ImGuiLocKey.TableSizeAllDefault) -- "###SizeAll" All stretch or mixed end if ImGui.MenuItem(size_all_desc, nil) then ImGui.TableSetColumnWidthAutoAll(table_) end want_separator = true end -- Reset Order/Visibility etc. if band(flags_for_section_to_display, bor(ImGuiTableFlags.Reorderable, ImGuiTableFlags.Hideable)) ~= 0 then if ImGui.BeginMenu(ImGui.LocalizeGetMsg(ImGuiLocKey.TableReset)) then if band(flags_for_section_to_display, ImGuiTableFlags.Reorderable) ~= 0 then if ImGui.MenuItem(ImGui.LocalizeGetMsg(ImGuiLocKey.TableResetOrder), nil, false, not table_.IsDefaultDisplayOrder) then -- PS: cannot be hidden because it would mess with drag reordering. table_.IsResetDisplayOrderRequest = true end end if band(flags_for_section_to_display, ImGuiTableFlags.Hideable) ~= 0 then if ImGui.MenuItem(ImGui.LocalizeGetMsg(ImGuiLocKey.TableResetVisibility), nil, false, not table_.IsDefaultVisibility) then table_.IsResetVisibilityRequest = true end end ImGui.EndMenu() end end -- Hiding / Visibility if band(flags_for_section_to_display, ImGuiTableFlags.Hideable) ~= 0 then if want_separator then ImGui.Separator() end want_separator = true -- While reordering: we calculate min/max allowed range once here so we can avoid a O(N log N) in the loop. local is_reordering = (g.ActiveId ~= 0 and g.ActiveIdWindow == g.CurrentWindow and table_.ReorderColumn ~= -1 and g.ActiveIdHasBeenEditedBefore) -- FIXME: This is a bit of a hack. local reorder_src_order = is_reordering and table_.Columns[table_.ReorderColumn].DisplayOrder or -1 local reorder_min_order = is_reordering and TableGetMaxDisplayOrderAllowed(table_, reorder_src_order, 0) or 0 local reorder_max_order = is_reordering and TableGetMaxDisplayOrderAllowed(table_, reorder_src_order, table_.ColumnsCount - 1) or (table_.ColumnsCount - 1) ImGui.PushItemFlag(ImGuiItemFlags.AutoClosePopups, false) for order_n = 0, table_.ColumnsCount - 1 do local column_n = table_.DisplayOrderToIndex[order_n] local column = table_.Columns[column_n] if band(column.Flags, ImGuiTableColumnFlags.Disabled) == 0 then local name = ImGui.TableGetColumnName(table_, column_n) if name == nil or name == "" then name = "" end -- Make sure we can't hide the last active column local menu_item_enabled = band(column.Flags, ImGuiTableColumnFlags.NoHide) == 0 if column.IsUserEnabled and table_.ColumnsEnabledCount <= 1 then menu_item_enabled = false end if is_reordering and (column.DisplayOrder < reorder_min_order or column.DisplayOrder > reorder_max_order) then menu_item_enabled = false end if MenuItemForColumnReorder(name, column.IsUserEnabled, menu_item_enabled) then column.IsUserEnabledNextFrame = not column.IsUserEnabled end -- Drag to reorder if ImGui.IsItemActive() and ImGui.IsMouseDragging(0) and g.ActiveIdSource == ImGuiInputSource.Mouse and band(table_.Flags, ImGuiTableFlags.Reorderable) ~= 0 then g.ActiveIdHasBeenEditedBefore = true -- Disable toggle in MenuItemForColumnReorder() + start dimming to display allowed reorder targets. table_.ReorderColumn = column_n if not ImGui.IsItemHovered() then local reorder_dir = (g.IO.MousePos.y < (g.LastItemData.Rect.Min.y + g.LastItemData.Rect.Max.y) * 0.5) and -1 or 1 local reorder_amount = ((reorder_dir < 0) and (g.LastItemData.Rect.Min.y - g.IO.MousePos.y) or (g.IO.MousePos.y - g.LastItemData.Rect.Max.y)) / g.LastItemData.Rect:GetHeight() local dst_order = column.DisplayOrder + ImTrunc(ImCeil(reorder_amount)) * reorder_dir -- Estimated target order, will be validated and clamped. ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, dst_order) end end end end ImGui.PopItemFlag() end end ---------------------------------------------------------------- -- [SECTION] Tables: Settings (.ini data) ---------------------------------------------------------------- -- Clear and initialize empty settings instance local function TableSettingsInit(settings, id, columns_count, columns_count_max) for k, v in pairs(ImGuiTableSettings()) do settings[k] = v end for n = 0, columns_count_max - 1 do settings.Columns[n] = ImGuiTableColumnSettings() end settings.ID = id settings.ColumnsCount = columns_count settings.ColumnsCountMax = columns_count_max settings.WantApply = true end function ImGui.TableSettingsCreate(id, columns_count) local g = GImGui local settings = ImGui.TableSettingsFindByID(id) if settings then if settings.ColumnsCountMax >= columns_count then TableSettingsInit(settings, id, columns_count, settings.ColumnsCountMax) -- Recycle return settings end settings.ID = 0 -- Invalidate storage, we won't fit because of a count change end settings = ImGuiTableSettings() g.SettingsTables:push_back(settings) TableSettingsInit(settings, id, columns_count, columns_count) return settings end -- Find existing settings function ImGui.TableSettingsFindByID(id) local g = GImGui for _, settings in g.SettingsTables:iter() do if settings.ID == id then return settings end end return nil end local function SettingsTablesOffsetFromPtr(settings) local g = GImGui return g.SettingsTables:find_index(settings) or -1 end -- Get settings for a given table, NULL if none function ImGui.TableGetBoundSettings(table_) if table_.SettingsOffset == -1 then return nil end local g = GImGui local settings = g.SettingsTables.Data[table_.SettingsOffset] IM_ASSERT(settings.ID == table_.ID) return settings end -- Restore initial state of table (with or without saved settings) function ImGui.TableResetSettings(table_) table_.IsInitializing = true; table_.IsSettingsDirty = true table_.IsResetAllRequest = false table_.IsSettingsRequestLoad = false -- Don't reload from ini table_.SettingsLoadedFlags = ImGuiTableFlags.None -- Mark as nothing loaded so our initialized data becomes authoritative end function ImGui.TableSaveSettings(table_) table_.IsSettingsDirty = false if band(table_.Flags, ImGuiTableFlags.NoSavedSettings) ~= 0 then return end -- Bind or create settings data local settings = ImGui.TableGetBoundSettings(table_) if settings ~= nil and table_.ColumnsCount > settings.ColumnsCountMax then settings.ID = 0 -- Invalidate storage, we won't fit because of a count change settings = nil end if settings == nil then settings = ImGui.TableSettingsCreate(table_.ID, table_.ColumnsCount) table_.SettingsOffset = SettingsTablesOffsetFromPtr(settings) end settings.ColumnsCount = table_.ColumnsCount -- Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings IM_ASSERT(settings.ID == table_.ID) IM_ASSERT(settings.ColumnsCount == table_.ColumnsCount and settings.ColumnsCountMax >= settings.ColumnsCount) local save_ref_scale = false settings.SaveFlags = ImGuiTableFlags.None for n = 0, table_.ColumnsCount - 1 do local column = table_.Columns[n] local column_settings = settings.Columns[n] local width_or_weight = (band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0) and column.StretchWeight or column.WidthRequest column_settings.ID = column.ID column_settings.WidthOrWeight = width_or_weight column_settings.Index = n column_settings.DisplayOrder = column.DisplayOrder column_settings.SortOrder = column.SortOrder column_settings.SortDirection = column.SortDirection column_settings.IsEnabled = column.IsUserEnabled and 1 or 0 column_settings.IsStretch = (band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0) and 1 or 0 if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) == 0 then save_ref_scale = true end -- We skip saving some data in the .ini file when they are unnecessary to restore our state. if width_or_weight ~= column.InitStretchWeightOrWidth then settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Resizable) end if column.DisplayOrder ~= n then settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Reorderable) end if column.SortOrder ~= -1 then settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Sortable, ImGuiTableFlags.Reorderable) -- Because SortOrder saving itself is gated, make sure every column is saved (#9519) end if column.IsUserEnabled ~= (band(column.Flags, ImGuiTableColumnFlags.DefaultHide) == 0) then settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Hideable) end end settings.SaveFlags = band(settings.SaveFlags, table_.Flags) settings.RefScale = save_ref_scale and table_.RefScale or 0.0 ImGui.MarkIniSettingsDirty() end function ImGui.TableLoadSettings(table_) if band(table_.Flags, ImGuiTableFlags.NoSavedSettings) ~= 0 then table_.IsSettingsRequestLoad = false -- Done return end -- Bind settings local settings if table_.SettingsOffset == -1 then settings = ImGui.TableSettingsFindByID(table_.ID) if settings == nil then return end if settings.ColumnsCount ~= table_.ColumnsCount then -- Allow settings if columns count changed. We could otherwise decide to return... table_.IsSettingsDirty = true end table_.SettingsOffset = SettingsTablesOffsetFromPtr(settings) else settings = ImGui.TableGetBoundSettings(table_) end table_.SettingsLoadedFlags = settings.SaveFlags table_.RefScale = settings.RefScale -- TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data. end -- NB: This was written to be similar to the logic in TableReconcileColumns(). function ImGui.TableLoadSettingsForColumns(table_) for n = 0, table_.ColumnsSize - 1 do table_.Columns[n].IsLoadedSettings = false end local settings = ImGui.TableGetBoundSettings(table_) if settings == nil then return end table_.SettingsLoadedFlags = bor(table_.SettingsLoadedFlags, ImGuiTableFlags.Reorderable) -- We handle above in code above. -- Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn local column_settings = settings.Columns -- Fast path local matches = 0 for n = 0, table_.ColumnsCount - 1 do if n >= settings.ColumnsCount or column_settings[n].ID ~= table_.Columns[n].ID then break end ImGui.TableLoadSettingsForColumn(table_.Columns[n], column_settings[n], settings.SaveFlags) matches = matches + 1 end if matches == settings.ColumnsCount then return end local settings_start_n = matches -- Small optimization for n = settings_start_n, settings.ColumnsCount - 1 do column_settings[n].IsLoaded = false end -- Find matches for named columns for c = 0, table_.ColumnsSize - 1 do local column = table_.Columns[c] if column.ID ~= 0 and not column.IsLoadedSettings then for n = settings_start_n, settings.ColumnsCount - 1 do if column_settings[n].ID == column.ID and not column_settings[n].IsLoaded then ImGui.TableLoadSettingsForColumn(column, column_settings[n], settings.SaveFlags) matches = matches + 1 break end end end end -- Remaining entries are matched sequentially local dst_idx = 0 for n = settings_start_n, settings.ColumnsCount - 1 do if not column_settings[n].IsLoaded then while dst_idx < table_.ColumnsCount and table_.Columns[dst_idx].IsLoadedSettings do dst_idx = dst_idx + 1 end if dst_idx >= table_.ColumnsCount then break end ImGui.TableLoadSettingsForColumn(table_.Columns[dst_idx], column_settings[n], settings.SaveFlags) dst_idx = dst_idx + 1 end end end function ImGui.TableLoadSettingsForColumn(column, column_settings, load_flags) column.IsLoadedSettings = true column_settings.IsLoaded = true if band(load_flags, ImGuiTableFlags.Resizable) ~= 0 then if column_settings.IsStretch ~= 0 then column.StretchWeight = column_settings.WidthOrWeight else column.WidthRequest = column_settings.WidthOrWeight end column.AutoFitQueue = 0x00 end if band(load_flags, ImGuiTableFlags.Reorderable) ~= 0 then column.DisplayOrder = column_settings.DisplayOrder else column.DisplayOrder = column_settings.Index -- Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults() end if band(load_flags, ImGuiTableFlags.Hideable) ~= 0 and column_settings.IsEnabled ~= -1 then local v = (column_settings.IsEnabled == 1) column.IsUserEnabled = v; column.IsUserEnabledNextFrame = v end column.SortOrder = column_settings.SortOrder column.SortDirection = column_settings.SortDirection end -- Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2) function ImGui.TableFixDisplayOrder(table_) if band(table_.Flags, ImGuiTableFlags.Reorderable) == 0 then for n = 0, table_.ColumnsCount - 1 do table_.Columns[n].DisplayOrder = n table_.DisplayOrderToIndex[n] = n end return end local fdo_columns = {} for n = 0, table_.ColumnsCount - 1 do fdo_columns[n + 1] = n end -- Sort by DisplayOrder and then Index. -1 (unsigned short 0xFFFF) always sorts after. local function key(idx) local o = table_.Columns[idx].DisplayOrder if o < 0 then o = o + 0x10000 end return o end table.sort(fdo_columns, function(lhs_idx, rhs_idx) local a, b = key(lhs_idx), key(rhs_idx) if a ~= b then return a < b end return lhs_idx < rhs_idx end) for n = 0, table_.ColumnsCount - 1 do table_.Columns[fdo_columns[n + 1]].DisplayOrder = n end for n = 0, table_.ColumnsCount - 1 do table_.DisplayOrderToIndex[table_.Columns[n].DisplayOrder] = n end end local function ForEachTable(g, fn) for i = 0, g.Tables:GetMapSize() - 1 do local t = g.Tables:GetByIndex(i) if t then fn(t) end end end local function TableSettingsHandler_ClearAll(ctx, handler) local g = ctx ForEachTable(g, function(t) t.SettingsOffset = -1 end) g.SettingsTables:clear() end -- Apply to existing windows (if any) local function TableSettingsHandler_ApplyAll(ctx, handler) local g = ctx ForEachTable(g, function(t) t.IsSettingsRequestLoad = true t.SettingsOffset = -1 end) end local function TableSettingsHandler_ReadOpen(ctx, handler, name) local id, columns_count = string.match(name, "^0x(%x+),(%-?%d+)") if id == nil then return nil end id = tonumber(id, 16); columns_count = tonumber(columns_count) if columns_count <= 0 or columns_count >= IMGUI_TABLE_MAX_COLUMNS then return nil end return ImGui.TableSettingsCreate(id, columns_count) end local function TableSettingsHandler_ReadLine(ctx, handler, settings, line) -- "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" if settings == nil then return end local f = string.match(line, "^RefScale=([%-%d%.eE+]+)") if f then settings.RefScale = tonumber(f); return end local n = string.match(line, "^LastUsed=(%d+)") if n then settings.LastUsedDate = tonumber(n); return end local column_n, rest = string.match(line, "^Column (%-?%d+)(.*)$") if column_n then column_n = tonumber(column_n) if column_n < 0 or column_n >= settings.ColumnsCount then return end local column = settings.Columns[column_n] column.Index = column_n local function take(pattern) local a, b, r = string.match(rest, "^%s*" .. pattern .. "()") if a == nil then return nil end if r == nil then r = b; b = nil end rest = string.sub(rest, r) return a, b end take("UserID=0x(%x+)") -- FIXME-LEGACY: Removed 2025/11/12, was never properly set. local v = take("Width=(%-?%d+)") if v then column.WidthOrWeight = tonumber(v); column.IsStretch = 0; settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Resizable) end v = take("Weight=([%-%d%.eE+]+)") if v then column.WidthOrWeight = tonumber(v); column.IsStretch = 1; settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Resizable) end v = take("Visible=(%-?%d+)") if v then column.IsEnabled = tonumber(v); settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Hideable) end v = take("Order=(%-?%d+)") if v then column.DisplayOrder = tonumber(v); settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Reorderable) end local c v, c = take("Sort=(%-?%d+)(.)") if v then column.SortOrder = tonumber(v); column.SortDirection = (c == "^") and ImGuiSortDirection.Descending or ImGuiSortDirection.Ascending; settings.SaveFlags = bor(settings.SaveFlags, ImGuiTableFlags.Sortable) end v = take("ID=0x(%x+)") if v then column.ID = tonumber(v, 16) end end end local function TableSettingsHandler_WriteAll(ctx, handler, buf) local g = ctx for _, settings in g.SettingsTables:iter() do if settings.ID ~= 0 then -- Skip ditched settings local save_size = band(settings.SaveFlags, ImGuiTableFlags.Resizable) ~= 0 local save_visible = band(settings.SaveFlags, ImGuiTableFlags.Hideable) ~= 0 local save_order = band(settings.SaveFlags, ImGuiTableFlags.Reorderable) ~= 0 local save_sort = band(settings.SaveFlags, ImGuiTableFlags.Sortable) ~= 0 -- We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings". buf:appendf("[%s][0x%08X,%d]\n", handler.TypeName, settings.ID, settings.ColumnsCount) if settings.RefScale ~= 0.0 then buf:appendf("RefScale=%g\n", settings.RefScale) end for column_n = 0, settings.ColumnsCount - 1 do local column = settings.Columns[column_n] local save_column = save_size or save_visible or save_order or (save_sort and column.SortOrder ~= -1) if save_column then buf:appendf("Column %-2d", column_n) if save_size and column.IsStretch ~= 0 then buf:appendf(" Weight=%.4f", column.WidthOrWeight) end if save_size and column.IsStretch == 0 then buf:appendf(" Width=%d", ImTrunc(column.WidthOrWeight)) end if save_visible then buf:appendf(" Visible=%d", column.IsEnabled) end if save_order then buf:appendf(" Order=%d", column.DisplayOrder) end if save_sort and column.SortOrder ~= -1 then buf:appendf(" Sort=%d%s", column.SortOrder, (column.SortDirection == ImGuiSortDirection.Ascending) and "v" or "^") end if column.ID ~= 0 then buf:appendf(" ID=0x%08X", column.ID) end buf:append("\n") end end if g.IO.ConfigIniSettingsSaveLastUsedDate and settings.LastUsedDate ~= 0 then buf:appendf("LastUsed=%08d\n", settings.LastUsedDate) end buf:append("\n") end end end function ImGui.TableSettingsAddSettingsHandler() local ini_handler = ImGuiSettingsHandler() ini_handler.TypeName = "Table" ini_handler.TypeHash = ImHashStr("Table") ini_handler.ClearAllFn = TableSettingsHandler_ClearAll ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen ini_handler.ReadLineFn = TableSettingsHandler_ReadLine ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll ini_handler.WriteAllFn = TableSettingsHandler_WriteAll ImGui.AddSettingsHandler(ini_handler) end ---------------------------------------------------------------- -- [SECTION] Tables: Garbage Collection ---------------------------------------------------------------- -- Remove Table data (currently only used by TestEngine) function ImGui.TableRemove(table_) local g = GImGui local table_idx = g.Tables:GetIndex(table_) g.Tables:Remove(table_.ID, table_) g.TablesLastTimeActive.Data[table_idx + 1] = -1.0 end -- Free up/compact internal Table buffers for when it gets unused -- Overloads: (ImGuiTable) / (ImGuiTableTempData) function ImGui.TableGcCompactTransientBuffers(t) if not t then return end if t.TableIndex ~= nil then -- ImGuiTableTempData t.AngledHeadersRequests:clear() t.DrawSplitter:ClearFreeMemory() t.LastTimeActive = -1.0 return end local g = GImGui local table_ = t IM_ASSERT(table_.MemoryCompacted == false) table_.SortSpecs.Specs = nil table.clear(table_.SortSpecsMulti) table_.IsSortSpecsDirty = true -- FIXME: In theory shouldn't have to leak into user performing a sort on resume. table.clear(table_.ColumnsNames) table_.MemoryCompacted = true for n = 0, table_.ColumnsCount - 1 do table_.Columns[n].NameOffset = -1 end g.TablesLastTimeActive.Data[g.Tables:GetIndex(table_) + 1] = -1.0 end -- Compact and remove unused or resize settings data function ImGui.TableGcCompactSettings() local g = GImGui local has_dead = false for _, settings in g.SettingsTables:iter() do if settings.ID == 0 then has_dead = true end end if not has_dead then return end local new_list = ImVector() for _, settings in g.SettingsTables:iter() do if settings.ID ~= 0 then new_list:push_back(settings) end end g.SettingsTables = new_list ForEachTable(g, function(t) t.SettingsOffset = -1 end) end ---------------------------------------------------------------- -- [SECTION] Columns, BeginColumns, EndColumns, etc. -- (This is a legacy API, prefer using BeginTable/EndTable!) ---------------------------------------------------------------- -- (Lua: columns.Columns is an ImVector, column n lives in .Data[n + 1]) function ImGui.GetColumnIndex() local window = ImGui.GetCurrentWindowRead() return window.DC.CurrentColumns and window.DC.CurrentColumns.Current or 0 end function ImGui.GetColumnsCount() local window = ImGui.GetCurrentWindowRead() return window.DC.CurrentColumns and window.DC.CurrentColumns.Count or 1 end function ImGui.GetColumnOffsetFromNorm(columns, offset_norm) return offset_norm * (columns.OffMaxX - columns.OffMinX) end function ImGui.GetColumnNormFromOffset(columns, offset) return offset / (columns.OffMaxX - columns.OffMinX) end local COLUMNS_HIT_RECT_HALF_THICKNESS = 4.0 local function GetDraggedColumnOffset(columns, column_index) -- Active (dragged) column always follow mouse. local g = GImGui local window = g.CurrentWindow IM_ASSERT(column_index > 0) -- We are not supposed to drag column 0. IM_ASSERT(g.ActiveId == band(columns.ID + column_index, 0xFFFFFFFF)) local x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale) - window.Pos.x x = ImMax(x, ImGui.GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing) if band(columns.Flags, ImGuiOldColumnFlags.NoPreserveWidths) ~= 0 then x = ImMin(x, ImGui.GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing) end return x end function ImGui.GetColumnOffset(column_index) if column_index == nil then column_index = -1 end local window = ImGui.GetCurrentWindowRead() local columns = window.DC.CurrentColumns if columns == nil then return 0.0 end if column_index < 0 then column_index = columns.Current end IM_ASSERT(column_index < columns.Columns.Size) local t = columns.Columns.Data[column_index + 1].OffsetNorm local x_offset = ImLerp(columns.OffMinX, columns.OffMaxX, t) return x_offset end local function GetColumnWidthEx(columns, column_index, before_resize) if column_index < 0 then column_index = columns.Current end local d = columns.Columns.Data local offset_norm if before_resize then offset_norm = d[column_index + 2].OffsetNormBeforeResize - d[column_index + 1].OffsetNormBeforeResize else offset_norm = d[column_index + 2].OffsetNorm - d[column_index + 1].OffsetNorm end return ImGui.GetColumnOffsetFromNorm(columns, offset_norm) end function ImGui.GetColumnWidth(column_index) if column_index == nil then column_index = -1 end local g = GImGui local window = g.CurrentWindow local columns = window.DC.CurrentColumns if columns == nil then return ImGui.GetContentRegionAvail().x end if column_index < 0 then column_index = columns.Current end local d = columns.Columns.Data return ImGui.GetColumnOffsetFromNorm(columns, d[column_index + 2].OffsetNorm - d[column_index + 1].OffsetNorm) end function ImGui.SetColumnOffset(column_index, offset) local g = GImGui local window = g.CurrentWindow local columns = window.DC.CurrentColumns IM_ASSERT(columns ~= nil) if column_index < 0 then column_index = columns.Current end IM_ASSERT(column_index < columns.Columns.Size) local preserve_width = band(columns.Flags, ImGuiOldColumnFlags.NoPreserveWidths) == 0 and (column_index < columns.Count - 1) local width = preserve_width and GetColumnWidthEx(columns, column_index, columns.IsBeingResized) or 0.0 if band(columns.Flags, ImGuiOldColumnFlags.NoForceWithinWindow) == 0 then offset = ImMin(offset, columns.OffMaxX - g.Style.ColumnsMinSpacing * (columns.Count - column_index)) end columns.Columns.Data[column_index + 1].OffsetNorm = ImGui.GetColumnNormFromOffset(columns, offset - columns.OffMinX) if preserve_width then ImGui.SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)) end end function ImGui.SetColumnWidth(column_index, width) local window = ImGui.GetCurrentWindowRead() local columns = window.DC.CurrentColumns IM_ASSERT(columns ~= nil) if column_index < 0 then column_index = columns.Current end ImGui.SetColumnOffset(column_index + 1, ImGui.GetColumnOffset(column_index) + width) end function ImGui.PushColumnClipRect(column_index) local window = ImGui.GetCurrentWindowRead() local columns = window.DC.CurrentColumns if column_index < 0 then column_index = columns.Current end local column = columns.Columns.Data[column_index + 1] ImGui.PushClipRect(column.ClipRect.Min, column.ClipRect.Max, false) end -- Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns) function ImGui.PushColumnsBackground() local window = ImGui.GetCurrentWindowRead() local columns = window.DC.CurrentColumns if columns.Count == 1 then return end -- Optimization: avoid SetCurrentChannel() + PushClipRect() ImRect_Copy(columns.HostBackupClipRect, window.ClipRect) ImGui.SetWindowClipRectBeforeSetChannel(window, columns.HostInitialClipRect) columns.Splitter:SetCurrentChannel(window.DrawList, 0) end function ImGui.PopColumnsBackground() local window = ImGui.GetCurrentWindowRead() local columns = window.DC.CurrentColumns if columns.Count == 1 then return end -- Optimization: avoid PopClipRect() + SetCurrentChannel() ImGui.SetWindowClipRectBeforeSetChannel(window, columns.HostBackupClipRect) columns.Splitter:SetCurrentChannel(window.DrawList, columns.Current + 1) end function ImGui.FindOrCreateColumns(window, id) -- We have few columns per window so for now we don't need bother much with turning this into a faster lookup. for _, c in window.ColumnsStorage:iter() do if c.ID == id then return c end end local columns = ImGuiOldColumns() window.ColumnsStorage:push_back(columns) columns.ID = id return columns end function ImGui.GetColumnsID(str_id, columns_count) local window = ImGui.GetCurrentWindow() -- Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. ImGui.PushID(0x11223347 + (str_id and 0 or columns_count)) local id = window:GetID(str_id or "columns") ImGui.PopID() return id end function ImGui.BeginColumns(str_id, columns_count, flags) if flags == nil then flags = 0 end local g = GImGui local window = ImGui.GetCurrentWindow() IM_ASSERT(columns_count >= 1) IM_ASSERT(window.DC.CurrentColumns == nil) -- Nested columns are currently not supported -- Acquire storage for the columns set local id = ImGui.GetColumnsID(str_id, columns_count) local columns = ImGui.FindOrCreateColumns(window, id) IM_ASSERT(columns.ID == id) columns.Current = 0 columns.Count = columns_count columns.Flags = flags window.DC.CurrentColumns = columns window.DC.NavIsScrollPushableX = false -- Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); columns.HostCursorPosY = window.DC.CursorPos.y columns.HostCursorMaxPosX = window.DC.CursorMaxPos.x ImRect_Copy(columns.HostInitialClipRect, window.ClipRect) ImRect_Copy(columns.HostBackupParentWorkRect, window.ParentWorkRect) ImRect_Copy(window.ParentWorkRect, window.WorkRect) -- Set state for first column local column_padding = g.Style.ItemSpacing.x local half_clip_extend_x = ImTrunc(ImMax(window.WindowPadding.x * 0.5, window.WindowBorderSize)) local max_1 = window.WorkRect.Max.x + column_padding - ImMax(column_padding - window.WindowPadding.x, 0.0) local max_2 = window.WorkRect.Max.x + half_clip_extend_x columns.OffMinX = window.DC.Indent.x - column_padding + ImMax(column_padding - window.WindowPadding.x, 0.0) columns.OffMaxX = ImMax(ImMin(max_1, max_2) - window.Pos.x, columns.OffMinX + 1.0) columns.LineMinY = window.DC.CursorPos.y; columns.LineMaxY = window.DC.CursorPos.y -- Clear data if columns count changed if columns.Columns.Size ~= 0 and columns.Columns.Size ~= columns_count + 1 then columns.Columns:resize(0) end -- Initialize default widths columns.IsFirstFrame = (columns.Columns.Size == 0) if columns.Columns.Size == 0 then for n = 0, columns_count do local column = ImGuiOldColumnData() column.OffsetNorm = n / columns_count columns.Columns:push_back(column) end end for n = 0, columns_count - 1 do -- Compute clipping rectangle local column = columns.Columns.Data[n + 1] local clip_x1 = IM_ROUND(window.Pos.x + ImGui.GetColumnOffset(n)) local clip_x2 = IM_ROUND(window.Pos.x + ImGui.GetColumnOffset(n + 1) - 1.0) column.ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, FLT_MAX) column.ClipRect:ClipWithFull(window.ClipRect) end if columns.Count > 1 then columns.Splitter:Split(window.DrawList, 1 + columns.Count) columns.Splitter:SetCurrentChannel(window.DrawList, 1) ImGui.PushColumnClipRect(0) end -- We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user. local offset_0 = ImGui.GetColumnOffset(columns.Current) local offset_1 = ImGui.GetColumnOffset(columns.Current + 1) local width = offset_1 - offset_0 ImGui.PushItemWidth(width * 0.65) window.DC.ColumnsOffset.x = ImMax(column_padding - window.WindowPadding.x, 0.0) window.DC.CursorPos.x = IM_TRUNC(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) window.WorkRect.Max.x = window.Pos.x + offset_1 - column_padding window.WorkRect.Max.y = window.ContentRegionRect.Max.y end function ImGui.NextColumn() local window = ImGui.GetCurrentWindow() if window.SkipItems or window.DC.CurrentColumns == nil then return end local g = GImGui local columns = window.DC.CurrentColumns if columns.Count == 1 then window.DC.CursorPos.x = IM_TRUNC(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) IM_ASSERT(columns.Current == 0) return end -- Next column columns.Current = columns.Current + 1 if columns.Current == columns.Count then columns.Current = 0 end ImGui.PopItemWidth() -- Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect() local column = columns.Columns.Data[columns.Current + 1] ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect) columns.Splitter:SetCurrentChannel(window.DrawList, columns.Current + 1) local column_padding = g.Style.ItemSpacing.x columns.LineMaxY = ImMax(columns.LineMaxY, window.DC.CursorPos.y) if columns.Current > 0 then -- Columns 1+ ignore IndentX (by canceling it out) window.DC.ColumnsOffset.x = ImGui.GetColumnOffset(columns.Current) - window.DC.Indent.x + column_padding else -- New row/line: column 0 honor IndentX. window.DC.ColumnsOffset.x = ImMax(column_padding - window.WindowPadding.x, 0.0) window.DC.IsSameLine = false columns.LineMinY = columns.LineMaxY end window.DC.CursorPos.x = IM_TRUNC(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) window.DC.CursorPos.y = columns.LineMinY window.DC.CurrLineSize.x = 0.0; window.DC.CurrLineSize.y = 0.0 window.DC.CurrLineTextBaseOffset = 0.0 local offset_0 = ImGui.GetColumnOffset(columns.Current) local offset_1 = ImGui.GetColumnOffset(columns.Current + 1) local width = offset_1 - offset_0 ImGui.PushItemWidth(width * 0.65) window.WorkRect.Max.x = window.Pos.x + offset_1 - column_padding end function ImGui.EndColumns() local g = GImGui local window = ImGui.GetCurrentWindow() local columns = window.DC.CurrentColumns IM_ASSERT(columns ~= nil) ImGui.PopItemWidth() if columns.Count > 1 then ImGui.PopClipRect() columns.Splitter:Merge(window.DrawList) end local flags = columns.Flags columns.LineMaxY = ImMax(columns.LineMaxY, window.DC.CursorPos.y) window.DC.CursorPos.y = columns.LineMaxY if band(flags, ImGuiOldColumnFlags.GrowParentContentsSize) == 0 then window.DC.CursorMaxPos.x = columns.HostCursorMaxPosX -- Restore cursor max pos, as columns don't grow parent end -- Draw columns borders and handle resize local is_being_resized = false if band(flags, ImGuiOldColumnFlags.NoBorder) == 0 and not window.SkipItems then -- We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers. local y1 = ImMax(columns.HostCursorPosY, window.ClipRect.Min.y) local y2 = ImMin(window.DC.CursorPos.y, window.ClipRect.Max.y) local dragging_column = -1 for n = 1, columns.Count - 1 do local column = columns.Columns.Data[n + 1] local x = window.Pos.x + ImGui.GetColumnOffset(n) local column_id = band(columns.ID + n, 0xFFFFFFFF) local column_hit_hw = ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale) local column_hit_rect = ImRect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)) if ImGui.ItemAdd(column_hit_rect, column_id, nil, ImGuiItemFlags.NoNav) then local hovered, held = false, false if band(flags, ImGuiOldColumnFlags.NoResize) == 0 then local _ _, hovered, held = ImGui.ButtonBehavior(column_hit_rect, column_id) if hovered or held then ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeEW) end if held and band(column.Flags, ImGuiOldColumnFlags.NoResize) == 0 then dragging_column = n end end -- Draw column local col = ImGui.GetColorU32(held and ImGuiCol.SeparatorActive or (hovered and ImGuiCol.SeparatorHovered or ImGuiCol.Separator)) local xi = IM_TRUNC(x) window.DrawList:AddLineV(xi, y1 + 1.0, y2, col) end end -- Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. if dragging_column ~= -1 then if not columns.IsBeingResized then for n = 0, columns.Count do local c = columns.Columns.Data[n + 1] c.OffsetNormBeforeResize = c.OffsetNorm end end columns.IsBeingResized = true; is_being_resized = true local x = GetDraggedColumnOffset(columns, dragging_column) ImGui.SetColumnOffset(dragging_column, x) end end columns.IsBeingResized = is_being_resized ImRect_Copy(window.WorkRect, window.ParentWorkRect) ImRect_Copy(window.ParentWorkRect, columns.HostBackupParentWorkRect) window.DC.CurrentColumns = nil window.DC.ColumnsOffset.x = 0.0 window.DC.CursorPos.x = IM_TRUNC(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) ImGui.NavUpdateCurrentWindowIsScrollPushableX() end --- @param columns_count? int --- @param id? string --- @param borders? bool function ImGui.Columns(columns_count, id, borders) if columns_count == nil then columns_count = 1 end if borders == nil then borders = true end local window = ImGui.GetCurrentWindow() IM_ASSERT(columns_count >= 1) local flags = borders and 0 or ImGuiOldColumnFlags.NoBorder local columns = window.DC.CurrentColumns if columns ~= nil and columns.Count == columns_count and columns.Flags == flags then return end if columns ~= nil then ImGui.EndColumns() end if columns_count ~= 1 then ImGui.BeginColumns(id, columns_count, flags) end end return true -- [Roblox] ModuleScripts must return exactly one value