core: ListClipper, TextFilter, error recovery + error tooltip, NavEndFrame; widgets: Combo, ListBox, TreeNodeEx/TreePush, MainMenuBar, ViewportSideBar; tree node fixes

This commit is contained in:
4DBug
2026-10-08 06:35:35 -05:00
parent c563739688
commit 9860373ce5
6 changed files with 2291 additions and 61 deletions
+734 -23
View File
@@ -8299,6 +8299,11 @@ function ImGui.NewFrame()
ImGui.SetNextWindowSize(ImVec2(400, 400), ImGuiCond.FirstUseEver)
ImGui.Begin("Debug##Default")
IM_ASSERT(g.CurrentWindow.IsFallbackWindow == true)
-- Store stack sizes
g.ErrorCountCurrentFrame = 0
g.StackSizesInNewFrame = g.StackSizesInNewFrame or ImGuiErrorRecoveryState()
ImGui.ErrorRecoveryStoreState(g.StackSizesInNewFrame)
end
function ImGui.EndFrame()
@@ -8310,6 +8315,12 @@ function ImGui.EndFrame()
end
IM_ASSERT_USER_ERROR(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?")
-- [EXPERIMENTAL] Recover from errors
if g.IO.ConfigErrorRecovery and g.StackSizesInNewFrame then
ImGui.ErrorRecoveryTryToRecoverState(g.StackSizesInNewFrame)
end
ImGui.ErrorCheckEndFrameFinalizeErrorTooltip()
-- Notify Platform when our Input Method Editor cursor has moved
local ime_data = g.PlatformImeData
if g.PlatformIO.Platform_SetImeDataFn ~= nil and not ImGuiPlatformImeData_Compare(ime_data, g.PlatformImeDataPrev) then
@@ -8329,7 +8340,7 @@ function ImGui.EndFrame()
ImGui.End()
-- Update navigation: Ctrl+Tab, wrap-around requests
if ImGui.NavEndFrame then ImGui.NavEndFrame() end
ImGui.NavEndFrame()
-- Update docking
ImGui.DockContextEndFrame(g)
@@ -12919,32 +12930,732 @@ function ImGui.BeginTooltipHidden()
return ret
end
-- Baseline fallbacks (the real ports, if defined earlier in this file, win)
if not ImGuiErrorRecoveryState then
function ImGuiErrorRecoveryState()
return { SizeOfWindowStack = 0, SizeOfIDStack = 0, SizeOfTreeStack = 0, SizeOfColorStack = 0, SizeOfStyleVarStack = 0,
SizeOfFontStack = 0, SizeOfFocusScopeStack = 0, SizeOfGroupStack = 0, SizeOfItemFlagsStack = 0,
SizeOfBeginPopupStack = 0, SizeOfDisabledStack = 0 }
-- [SECTION] NAV END FRAME
local NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15
local function GetFallbackWindowNameForWindowingList(window)
if bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 then
return "(Popup)"
end
if bit32.band(window.Flags, ImGuiWindowFlags.MenuBar) ~= 0 and window.Name == "##MainMenuBar" then
return "(Main menu bar)"
end
if window.DockNodeAsHost then
return "(Dock node)"
end
return "(Untitled)"
end
function ImGui.NavUpdateWindowingOverlay()
local g = GImGui
IM_ASSERT(g.NavWindowingTarget ~= nil)
if g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY then
return
end
local viewport = ImGui.GetMainViewport()
ImGui.SetNextWindowSizeConstraints(ImVec2(viewport.Size.x * 0.20, viewport.Size.y * 0.20), ImVec2(FLT_MAX, FLT_MAX))
ImGui.SetNextWindowPos(viewport:GetCenter(), ImGuiCond.Always, ImVec2(0.5, 0.5))
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, g.Style.WindowPadding * 2.0)
ImGui.Begin("##NavWindowingOverlay", nil, bit32.bor(ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoFocusOnAppearing, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoInputs, ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoSavedSettings))
g.NavWindowingListWindow = g.CurrentWindow
for n = g.WindowsFocusOrder.Size, 1, -1 do
local window = g.WindowsFocusOrder.Data[n]
IM_ASSERT(window ~= nil)
if ImGui.IsWindowNavFocusable(window) then
local label = window.Name
if ImGui.FindRenderedTextEnd(label) == 1 then
label = GetFallbackWindowNameForWindowingList(window)
end
ImGui.Selectable(label, g.NavWindowingTarget == window)
end
end
ImGui.End()
ImGui.PopStyleVar()
end
local function NavUpdateCreateWrappingRequest()
local g = GImGui
local window = g.NavWindow
local do_forward = false
local src = window.NavRectRel[g.NavLayer]
local bb_rel = ImRect(src.Min.x, src.Min.y, src.Max.x, src.Max.y)
local clip_dir = g.NavMoveDir
local move_flags = g.NavMoveFlags
local wrap_size = (g.NavLayer == ImGuiNavLayer.Menu) and window.Size or (window.ContentSize + window.WindowPadding)
if g.NavMoveDir == ImGuiDir.Left and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapX, ImGuiNavMoveFlags.LoopX)) ~= 0 then
bb_rel.Min.x = wrap_size.x; bb_rel.Max.x = wrap_size.x
if bit32.band(move_flags, ImGuiNavMoveFlags.WrapX) ~= 0 then
bb_rel:TranslateY(-bb_rel:GetHeight())
clip_dir = ImGuiDir.Up
end
do_forward = true
end
if g.NavMoveDir == ImGuiDir.Right and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapX, ImGuiNavMoveFlags.LoopX)) ~= 0 then
bb_rel.Min.x = -window.WindowPadding.x; bb_rel.Max.x = -window.WindowPadding.x
if bit32.band(move_flags, ImGuiNavMoveFlags.WrapX) ~= 0 then
bb_rel:TranslateY(bb_rel:GetHeight())
clip_dir = ImGuiDir.Down
end
do_forward = true
end
if g.NavMoveDir == ImGuiDir.Up and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapY, ImGuiNavMoveFlags.LoopY)) ~= 0 then
bb_rel.Min.y = wrap_size.y; bb_rel.Max.y = wrap_size.y
if bit32.band(move_flags, ImGuiNavMoveFlags.WrapY) ~= 0 then
bb_rel:TranslateX(-bb_rel:GetWidth())
clip_dir = ImGuiDir.Left
end
do_forward = true
end
if g.NavMoveDir == ImGuiDir.Down and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapY, ImGuiNavMoveFlags.LoopY)) ~= 0 then
bb_rel.Min.y = -window.WindowPadding.y; bb_rel.Max.y = -window.WindowPadding.y
if bit32.band(move_flags, ImGuiNavMoveFlags.WrapY) ~= 0 then
bb_rel:TranslateX(bb_rel:GetWidth())
clip_dir = ImGuiDir.Right
end
do_forward = true
end
if not do_forward then
return
end
window.NavRectRel[g.NavLayer] = bb_rel
ImGui.NavClearPreferredPosForAxis(ImGuiAxis.X)
ImGui.NavClearPreferredPosForAxis(ImGuiAxis.Y)
ImGui.NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags)
end
function ImGui.NavEndFrame()
local g = GImGui
if g.NavWindowingTarget ~= nil then
ImGui.NavUpdateWindowingOverlay()
end
if g.NavWindow and ImGui.NavMoveRequestButNoResultYet() and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.WrapMask_) ~= 0 and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.Forwarded) == 0 then
NavUpdateCreateWrappingRequest()
end
end
if not ImGui.ErrorRecoveryStoreState then
function ImGui.ErrorRecoveryStoreState(state_out)
local g = GImGui
state_out.SizeOfWindowStack = g.CurrentWindowStack.Size
state_out.SizeOfIDStack = g.CurrentWindow.IDStack.Size
state_out.SizeOfTreeStack = g.CurrentWindow.DC.TreeDepth
state_out.SizeOfColorStack = g.ColorStack.Size
state_out.SizeOfStyleVarStack = g.StyleVarStack.Size
state_out.SizeOfFontStack = g.FontStack.Size
state_out.SizeOfFocusScopeStack = g.FocusScopeStack.Size
state_out.SizeOfGroupStack = g.GroupStack.Size
state_out.SizeOfItemFlagsStack = g.ItemFlagsStack.Size
state_out.SizeOfBeginPopupStack = g.BeginPopupStack.Size
state_out.SizeOfDisabledStack = g.DisabledStackSize or 0
-- [SECTION] ImGuiTextFilter
--- @class ImGuiTextFilter
MT.ImGuiTextFilter = {}
MT.ImGuiTextFilter.__index = MT.ImGuiTextFilter
--- @param default_filter? string
function ImGuiTextFilter(default_filter)
local this = setmetatable({ InputBuf = {}, _CountExclude = 0, _Items = ImVector() }, MT.ImGuiTextFilter)
for i = 1, 256 do this.InputBuf[i] = 0 end
if default_filter and default_filter ~= "" then
for i = 1, ImMin(#default_filter, 255) do this.InputBuf[i] = string.byte(default_filter, i) end
this:Build()
end
return this
end
function MT.ImGuiTextFilter:Draw(label, width)
if label == nil then label = "Filter (inc,-exc)" end
if width ~= nil and width ~= 0.0 then ImGui.SetNextItemWidth(width) end
return self:DrawWithHint(label, "incl -excl")
end
function MT.ImGuiTextFilter:DrawWithHint(label, hint)
if label == nil then label = "Filter" end
if hint == nil then hint = "incl -excl" end
local value_changed = ImGui.InputTextWithHint(label, hint, self.InputBuf, 256)
if value_changed then
self:Build()
end
return value_changed
end
function MT.ImGuiTextFilter:Clear() self.InputBuf[1] = 0; self:Build() end
function MT.ImGuiTextFilter:IsActive() return self._Items.Size ~= 0 end
-- Items are { Text = string (lowercased), CountInclude = int }
function MT.ImGuiTextFilter:Build()
local items = self._Items
items:resize(0)
self._CountExclude = 0
local n = 0
while self.InputBuf[n + 1] ~= nil and self.InputBuf[n + 1] ~= 0 do n = n + 1 end
local buf = ImGui._ByteArrayToString(self.InputBuf, 1, n + 1)
local buf_e = n + 1 -- exclusive, 1-based
local word_b = 1
local seq_start_idx = -1 -- 1-based index into items, -1 = none
while word_b < buf_e do
while word_b < buf_e and ImCharIsBlankA(string.byte(buf, word_b)) do
word_b = word_b + 1
end
local is_excl = word_b < buf_e and string.byte(buf, word_b) == 45 -- '-'
if is_excl then word_b = word_b + 1 end
local is_quote = word_b < buf_e and string.byte(buf, word_b) == 34 -- '"'
local word_e
if is_quote then
word_b = word_b + 1
word_e = string.find(buf, '"', word_b, true) or buf_e
if word_e > buf_e then word_e = buf_e end
else
word_e = word_b
while word_e < buf_e do
local c = string.byte(buf, word_e)
if c == 32 or c == 44 then break end
word_e = word_e + 1
end
end
if word_e - word_b > 0 then
local item = { Text = string.lower(string.sub(buf, word_b, word_e - 1)), CountInclude = 0 }
if is_excl then
items:insert(self._CountExclude + 1, item)
self._CountExclude = self._CountExclude + 1
if seq_start_idx ~= -1 then seq_start_idx = seq_start_idx + 1 end
else
if seq_start_idx == -1 then seq_start_idx = items.Size + 1 end
items:push_back(item)
items.Data[seq_start_idx].CountInclude = items.Data[seq_start_idx].CountInclude + 1
end
end
if word_e < buf_e and string.byte(buf, word_e) == 44 then -- ','
seq_start_idx = -1
end
word_b = word_e + 1
end
end
if not ImGui.ErrorRecoveryTryToRecoverWindowState then
function ImGui.ErrorRecoveryTryToRecoverWindowState(state_in) end -- TODO(core agent)
--- @param text string?
--- @param text_end? int # exclusive, 1-based
function MT.ImGuiTextFilter:PassFilter(text, text_end)
local items = self._Items
if items.Size == 0 then return true end
if text == nil then text = "" end
if text_end ~= nil then text = string.sub(text, 1, text_end - 1) end
text = string.lower(text)
local seq = 1
local seq_excl_end = self._CountExclude + 1
while seq < seq_excl_end do
if string.find(text, items.Data[seq].Text, 1, true) then return false end
seq = seq + 1
end
local seq_end = items.Size + 1
if seq == seq_end then return true end
while seq < seq_end do
local seq_incl_end = seq + items.Data[seq].CountInclude
while seq < seq_incl_end do
if not string.find(text, items.Data[seq].Text, 1, true) then break end
seq = seq + 1
end
if seq == seq_incl_end then return true end
seq = seq_incl_end
end
return false
end
-- [SECTION] ImGuiListClipper
ImGuiListClipperFlags = ImGuiListClipperFlags or { None = 0, NoSetTableRowCounters = bit32.lshift(1, 0) }
local function GetSkipItemForListClipping()
local g = GImGui
if g.CurrentTable then return g.CurrentTable.HostSkipItems end
return g.CurrentWindow.SkipItems
end
local function ClipperRangeFromIndices(min, max) return { Min = min, Max = max, PosToIndexConvert = false, PosToIndexOffsetMin = 0, PosToIndexOffsetMax = 0 } end
local function ClipperRangeFromPositions(y1, y2, off_min, off_max) return { Min = y1, Max = y2, PosToIndexConvert = true, PosToIndexOffsetMin = off_min, PosToIndexOffsetMax = off_max } end
-- `offset` is a 0-based count of leading ranges to leave alone
local function ImGuiListClipper_SortAndFuseRanges(ranges, offset)
if offset == nil then offset = 0 end
if ranges.Size - offset <= 1 then return end
local d = ranges.Data
for sort_end = ranges.Size - offset - 1, 1, -1 do
for i = offset + 1, sort_end + offset do
if d[i].Min > d[i + 1].Min then d[i], d[i + 1] = d[i + 1], d[i] end
end
end
local i = 2 + offset
while i <= ranges.Size do
IM_ASSERT(not d[i].PosToIndexConvert and not d[i - 1].PosToIndexConvert)
if d[i - 1].Max < d[i].Min then
i = i + 1
else
d[i - 1].Min = ImMin(d[i - 1].Min, d[i].Min)
d[i - 1].Max = ImMax(d[i - 1].Max, d[i].Max)
ranges:erase(i)
end
end
end
local function ImGuiListClipper_SeekCursorAndSetupPrevLine(clipper, pos_y, line_height)
local g = GImGui
local window = g.CurrentWindow
local off_y = pos_y - window.DC.CursorPos.y
window.DC.CursorPos.y = pos_y
window.DC.CursorMaxPos.y = ImMax(window.DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y)
window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y - line_height
window.DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y)
local columns = window.DC.CurrentColumns
if columns then
columns.LineMinY = window.DC.CursorPos.y
end
local tbl = g.CurrentTable
if tbl then
if tbl.IsInsideRow then
ImGui.TableEndRow(tbl)
end
local row_increase = math.floor((off_y / line_height) + 0.5)
if row_increase > 0 and bit32.band(clipper.Flags, ImGuiListClipperFlags.NoSetTableRowCounters) == 0 then
tbl.CurrentRow = tbl.CurrentRow + row_increase
tbl.RowBgColorCounter = tbl.RowBgColorCounter + row_increase
end
tbl.RowPosY2 = window.DC.CursorPos.y
end
end
--- @class ImGuiListClipper
MT.ImGuiListClipper = {}
MT.ImGuiListClipper.__index = MT.ImGuiListClipper
function ImGuiListClipper()
return setmetatable({ DisplayStart = 0, DisplayEnd = 0, UserIndex = 0, ItemsCount = 0, ItemsHeight = 0.0, Flags = 0,
StartPosY = 0.0, StartSeekOffsetY = 0.0, Ctx = nil, TempData = nil }, MT.ImGuiListClipper)
end
--- @param items_count int
--- @param items_height? float
function MT.ImGuiListClipper:Begin(items_count, items_height)
if items_height == nil then items_height = -1.0 end
self.Ctx = ImGui.GetCurrentContext()
local g = self.Ctx
local window = g.CurrentWindow
local tbl = g.CurrentTable
if tbl and tbl.IsInsideRow then
ImGui.TableEndRow(tbl)
end
self.StartPosY = window.DC.CursorPos.y
self.ItemsHeight = items_height
self.ItemsCount = items_count
self.DisplayStart = -1
self.DisplayEnd = 0
g.ClipperTempDataStacked = g.ClipperTempDataStacked + 1
while g.ClipperTempDataStacked > g.ClipperTempData.Size do
g.ClipperTempData:push_back({ ListClipper = nil, LossynessOffset = 0.0, StepNo = 0, ItemsFrozen = 0, Ranges = ImVector() })
end
local data = g.ClipperTempData.Data[g.ClipperTempDataStacked]
data.ListClipper = self; data.StepNo = 0; data.ItemsFrozen = 0; data.Ranges:resize(0)
data.LossynessOffset = window.DC.CursorStartPosLossyness.y
self.TempData = data
self.StartSeekOffsetY = data.LossynessOffset
end
function MT.ImGuiListClipper:End()
local data = self.TempData
if data then
local g = self.Ctx
if self.ItemsCount >= 0 and self.ItemsCount < INT_MAX and self.DisplayStart >= 0 then
self:SeekCursorForItem(self.ItemsCount)
end
IM_ASSERT(data.ListClipper == self)
data.StepNo = data.Ranges.Size
g.ClipperTempDataStacked = g.ClipperTempDataStacked - 1
if g.ClipperTempDataStacked > 0 then
data = g.ClipperTempData.Data[g.ClipperTempDataStacked]
data.ListClipper.TempData = data
end
self.TempData = nil
end
self.DisplayStart = self.ItemsCount
self.DisplayEnd = self.ItemsCount
self.ItemsCount = -1
end
function MT.ImGuiListClipper:IncludeItemByIndex(item_index) self:IncludeItemsByIndex(item_index, item_index + 1) end
function MT.ImGuiListClipper:IncludeItemsByIndex(item_begin, item_end)
local data = self.TempData
IM_ASSERT(self.DisplayStart < 0)
IM_ASSERT(item_begin <= item_end)
if item_begin < item_end then
data.Ranges:push_back(ClipperRangeFromIndices(item_begin, item_end))
end
end
function MT.ImGuiListClipper:SeekCursorForItem(item_n)
local pos_y = self.StartPosY + self.StartSeekOffsetY + item_n * self.ItemsHeight
ImGuiListClipper_SeekCursorAndSetupPrevLine(self, pos_y, self.ItemsHeight)
end
local function ImGuiListClipper_StepInternal(clipper)
local g = clipper.Ctx
local window = g.CurrentWindow
local data = clipper.TempData
IM_ASSERT(data ~= nil, "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?")
local tbl = g.CurrentTable
if tbl and tbl.IsInsideRow then
ImGui.TableEndRow(tbl)
end
if clipper.ItemsCount == 0 or GetSkipItemForListClipping() then
return false
end
if data.StepNo == 0 and tbl ~= nil and not tbl.IsUnfrozenRows then
clipper.DisplayStart = data.ItemsFrozen
clipper.DisplayEnd = ImMin(data.ItemsFrozen + 1, clipper.ItemsCount)
if clipper.DisplayStart < clipper.DisplayEnd then
data.ItemsFrozen = data.ItemsFrozen + 1
end
return true
end
local calc_clipping = false
if data.StepNo == 0 then
clipper.StartPosY = window.DC.CursorPos.y
if clipper.ItemsHeight <= 0.0 then
data.Ranges:push_front(ClipperRangeFromIndices(data.ItemsFrozen, data.ItemsFrozen + 1))
clipper.DisplayStart = ImMax(data.Ranges.Data[1].Min, data.ItemsFrozen)
clipper.DisplayEnd = ImMin(data.Ranges.Data[1].Max, clipper.ItemsCount)
data.StepNo = 1
return true
end
calc_clipping = true
end
if clipper.ItemsHeight <= 0.0 then
IM_ASSERT(data.StepNo == 1)
-- (no float precision mitigation needed: Luau numbers are doubles)
clipper.ItemsHeight = (window.DC.CursorPos.y - clipper.StartPosY) / (clipper.DisplayEnd - clipper.DisplayStart)
if clipper.ItemsHeight == 0.0 and clipper.ItemsCount == INT_MAX then
return false
end
if clipper.ItemsHeight <= 0.0 then
IM_ASSERT_USER_ERROR(clipper.ItemsHeight > 0.0, "ImGuiListClipper: Failed to calculate item height! First item hasn't been submitted by user code, or has not moved the cursor vertically!")
return false
end
calc_clipping = true
end
local already_submitted = clipper.DisplayEnd
if calc_clipping then
clipper.StartSeekOffsetY = data.LossynessOffset - data.ItemsFrozen * clipper.ItemsHeight
if g.LogEnabled then
data.Ranges:push_back(ClipperRangeFromIndices(0, clipper.ItemsCount))
else
local is_nav_request = (g.NavMoveScoringItems and g.NavWindow and g.NavWindow.RootWindowForNav == window.RootWindowForNav) and true or false
local nav_off_min = (is_nav_request and g.NavMoveClipDir == ImGuiDir.Up) and -1 or 0
local nav_off_max = (is_nav_request and g.NavMoveClipDir == ImGuiDir.Down) and 1 or 0
if is_nav_request then
data.Ranges:push_back(ClipperRangeFromPositions(g.NavScoringRect.Min.y, g.NavScoringRect.Max.y, nav_off_min, nav_off_max))
if not g.NavScoringNoClipRect:IsInverted() then
data.Ranges:push_back(ClipperRangeFromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, nav_off_min, nav_off_max))
end
end
if is_nav_request and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 and g.NavTabbingDir == -1 then
data.Ranges:push_back(ClipperRangeFromIndices(clipper.ItemsCount - 1, clipper.ItemsCount))
end
local nav_rect_abs = ImGui.WindowRectRelToAbs(window, window.NavRectRel[0])
if g.NavId ~= 0 and window.NavLastIds[0] == g.NavId then
data.Ranges:push_back(ClipperRangeFromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0))
end
local min_y = window.ClipRect.Min.y
local max_y = window.ClipRect.Max.y
local bs = g.BoxSelectState
if bs and bs.IsActive and bs.Window == window then
local pad_y = g.Style.ItemSpacing.y
min_y = min_y - pad_y
max_y = max_y + pad_y
if bs.UnclipMode then
data.Ranges:push_back(ClipperRangeFromPositions(bs.UnclipRect.Min.y - pad_y, bs.UnclipRect.Max.y + pad_y, 0, 0))
end
end
data.Ranges:push_back(ClipperRangeFromPositions(min_y, max_y, nav_off_min, nav_off_max))
end
for _, range in data.Ranges:iter() do
if range.PosToIndexConvert then
local m1 = ImTrunc((range.Min - window.DC.CursorPos.y - data.LossynessOffset) / clipper.ItemsHeight)
local m2 = ImTrunc(((range.Max - window.DC.CursorPos.y - data.LossynessOffset) / clipper.ItemsHeight) + 0.999999)
range.Min = ImClamp(already_submitted + m1 + range.PosToIndexOffsetMin, already_submitted, clipper.ItemsCount - 1)
range.Max = ImClamp(already_submitted + m2 + range.PosToIndexOffsetMax, range.Min + 1, clipper.ItemsCount)
range.PosToIndexConvert = false
end
end
ImGuiListClipper_SortAndFuseRanges(data.Ranges, data.StepNo)
end
while data.StepNo < data.Ranges.Size do
local r = data.Ranges.Data[data.StepNo + 1]
clipper.DisplayStart = ImMax(r.Min, already_submitted)
clipper.DisplayEnd = ImMin(r.Max, clipper.ItemsCount)
data.StepNo = data.StepNo + 1
if clipper.DisplayStart < clipper.DisplayEnd then
if clipper.DisplayStart > already_submitted then
clipper:SeekCursorForItem(clipper.DisplayStart)
end
return true
end
end
if clipper.ItemsCount < INT_MAX then
clipper:SeekCursorForItem(clipper.ItemsCount)
end
return false
end
function MT.ImGuiListClipper:Step()
local ret = ImGuiListClipper_StepInternal(self)
if ret and (self.DisplayStart >= self.DisplayEnd) then
ret = false
end
if not ret then
self:End()
end
return ret
end
function ImGui.CalcClipRectVisibleItemsY(clip_rect, pos, items_height)
local out_visible_start = ImMax(ImTrunc((clip_rect.Min.y - pos.y) / items_height), 0)
local out_visible_end = ImMax(ImTrunc(ImCeil((clip_rect.Max.y - pos.y) / items_height)), out_visible_start)
return out_visible_start, out_visible_end
end
-- [SECTION] ERROR CHECKING, STATE RECOVERY
function ImGuiErrorRecoveryState()
return { SizeOfWindowStack = 0, SizeOfIDStack = 0, SizeOfTreeStack = 0, SizeOfColorStack = 0, SizeOfStyleVarStack = 0,
SizeOfFontStack = 0, SizeOfFocusScopeStack = 0, SizeOfGroupStack = 0, SizeOfItemFlagsStack = 0,
SizeOfBeginPopupStack = 0, SizeOfDisabledStack = 0 }
end
function ImGui.ErrorRecoveryStoreState(state_out)
local g = GImGui
state_out.SizeOfWindowStack = g.CurrentWindowStack.Size
state_out.SizeOfIDStack = g.CurrentWindow.IDStack.Size
state_out.SizeOfTreeStack = g.CurrentWindow.DC.TreeDepth -- NOT g.TreeNodeStack.Size which is a partial stack!
state_out.SizeOfColorStack = g.ColorStack.Size
state_out.SizeOfStyleVarStack = g.StyleVarStack.Size
state_out.SizeOfFontStack = g.FontStack.Size
state_out.SizeOfFocusScopeStack = g.FocusScopeStack.Size
state_out.SizeOfGroupStack = g.GroupStack.Size
state_out.SizeOfItemFlagsStack = g.ItemFlagsStack.Size
state_out.SizeOfBeginPopupStack = g.BeginPopupStack.Size
state_out.SizeOfDisabledStack = g.DisabledStackSize
end
function ImGui.ErrorRecoveryTryToRecoverState(state_in)
local g = GImGui
while g.CurrentWindowStack.Size > state_in.SizeOfWindowStack do
local window = g.CurrentWindow
if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then
if g.CurrentTable ~= nil and g.CurrentTable.InnerWindow == g.CurrentWindow then
IM_ASSERT_USER_ERROR(false, "Missing EndTable()")
ImGui.EndTable()
else
IM_ASSERT_USER_ERROR(false, "Missing EndChild()")
ImGui.EndChild()
end
else
IM_ASSERT_USER_ERROR(false, "Missing End()")
ImGui.End()
end
end
if g.CurrentWindowStack.Size == state_in.SizeOfWindowStack then
ImGui.ErrorRecoveryTryToRecoverWindowState(state_in)
end
end
function ImGui.ErrorRecoveryTryToRecoverWindowState(state_in)
local g = GImGui
while g.CurrentTable ~= nil and g.CurrentTable.InnerWindow == g.CurrentWindow do
IM_ASSERT_USER_ERROR(false, "Missing EndTable()")
ImGui.EndTable()
end
local window = g.CurrentWindow
-- FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
while g.CurrentTabBar ~= nil and g.CurrentTabBar.Window == window do
IM_ASSERT_USER_ERROR(false, "Missing EndTabBar()")
ImGui.EndTabBar()
end
while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window and ImGui.EndMultiSelect do
IM_ASSERT_USER_ERROR(false, "Missing EndMultiSelect()")
ImGui.EndMultiSelect()
end
if window.DC.MenuBarAppending then
IM_ASSERT_USER_ERROR(false, "Missing EndMenuBar()")
ImGui.EndMenuBar()
end
while window.DC.TreeDepth > state_in.SizeOfTreeStack do
IM_ASSERT_USER_ERROR(false, "Missing TreePop()")
ImGui.TreePop()
end
while g.GroupStack.Size > state_in.SizeOfGroupStack do
IM_ASSERT_USER_ERROR(false, "Missing EndGroup()")
ImGui.EndGroup()
end
IM_ASSERT(g.GroupStack.Size == state_in.SizeOfGroupStack)
while window.IDStack.Size > state_in.SizeOfIDStack do
IM_ASSERT_USER_ERROR(false, "Missing PopID()")
ImGui.PopID()
end
while g.DisabledStackSize > state_in.SizeOfDisabledStack do
IM_ASSERT_USER_ERROR(false, "Missing EndDisabled()")
if bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0 then
ImGui.EndDisabled()
else
ImGui.EndDisabledOverrideReenable()
g.CurrentWindowStack:back().DisabledOverrideReenable = false
end
end
IM_ASSERT(g.DisabledStackSize == state_in.SizeOfDisabledStack)
while g.ColorStack.Size > state_in.SizeOfColorStack do
IM_ASSERT_USER_ERROR(false, "Missing PopStyleColor()")
ImGui.PopStyleColor()
end
while g.ItemFlagsStack.Size > state_in.SizeOfItemFlagsStack do
IM_ASSERT_USER_ERROR(false, "Missing PopItemFlag()")
ImGui.PopItemFlag()
end
while g.StyleVarStack.Size > state_in.SizeOfStyleVarStack do
IM_ASSERT_USER_ERROR(false, "Missing PopStyleVar()")
ImGui.PopStyleVar()
end
while g.FontStack.Size > state_in.SizeOfFontStack do
IM_ASSERT_USER_ERROR(false, "Missing PopFont()")
ImGui.PopFont()
end
while g.FocusScopeStack.Size > state_in.SizeOfFocusScopeStack do
IM_ASSERT_USER_ERROR(false, "Missing PopFocusScope()")
ImGui.PopFocusScope()
end
end
-- Lines are stored in g.DebugLogBuf (one chunk per call)
function ImGui.DebugLog(fmt, ...)
local g = GImGui
g.DebugLogBuf = g.DebugLogBuf or ImGuiTextBuffer()
local str = string.format("[%05d] ", g.FrameCount) .. ((select("#", ...) > 0) and string.format(fmt, ...) or fmt)
g.DebugLogBuf:append(str)
if bit32.band(g.DebugLogFlags, ImGuiDebugLogFlags.OutputToTTY) ~= 0 then
print(str)
end
end
--- @return bool should_assert
function ImGui.ErrorLog(msg)
local g = GImGui
if g == nil then return true end
local window = g.CurrentWindow
if g.IO.ConfigErrorRecoveryEnableDebugLog then
if g.ErrorFirst then
ImGui.DebugLog(string.format("[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\n",
g.IO.ConfigErrorRecoveryEnableAssert and 1 or 0, g.IO.ConfigErrorRecoveryEnableDebugLog and 1 or 0, g.IO.ConfigErrorRecoveryEnableTooltip and 1 or 0))
end
ImGui.DebugLog(string.format("[imgui-error] In window '%s': %s\n", window and window.Name or "NULL", tostring(msg)))
end
g.ErrorFirst = false
if g.IO.ConfigErrorRecoveryEnableTooltip then
if g.WithinFrameScope and ImGui.BeginErrorTooltip() then
if g.ErrorCountCurrentFrame < 20 then
ImGui.Text("In window '%s': %s", window and window.Name or "NULL", tostring(msg))
if window and (not window.IsFallbackWindow or window.WasActive) then
ImGui.GetForegroundDrawList(window.Viewport):AddRect(window.Pos, window.Pos + window.Size, IM_COL32(255, 0, 0, 255))
end
end
if g.ErrorCountCurrentFrame == 20 then
ImGui.Text("(and more errors)")
end
ImGui.EndErrorTooltip()
end
g.ErrorCountCurrentFrame = g.ErrorCountCurrentFrame + 1
end
if g.ErrorCallback ~= nil then
g.ErrorCallback(g, g.ErrorCallbackUserData, msg)
end
return g.IO.ConfigErrorRecoveryEnableAssert
end
function ImGui.ErrorCheckEndFrameFinalizeErrorTooltip()
local g = GImGui
if g.DebugDrawIdConflictsId ~= 0 and g.IO.KeyCtrl == false then
g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount
end
if g.DebugDrawIdConflictsId ~= 0 and not g.DebugItemPickerActive and ImGui.BeginErrorTooltip() then
ImGui.Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount)
ImGui.BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!")
ImGui.BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!")
ImGui.BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds.")
ImGui.Separator()
ImGui.Text("(Hold Ctrl to: ")
ImGui.SameLine(0.0, 0.0)
ImGui.TextLinkOpenURL("read FAQ \"About ID Stack System\"", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage")
ImGui.SameLine(0.0, 0.0)
ImGui.Text(")")
ImGui.EndErrorTooltip()
end
if g.ErrorCountCurrentFrame > 0 and ImGui.BeginErrorTooltip() then
ImGui.Separator()
ImGui.Text("(Hold Ctrl to: ")
ImGui.SameLine(0.0, 0.0)
if ImGui.SmallButton("Enable Asserts") then
g.IO.ConfigErrorRecoveryEnableAssert = true
end
ImGui.SameLine(0, 0)
ImGui.Text(")")
ImGui.EndErrorTooltip()
end
end
function ImGui.BeginErrorTooltip()
local g = GImGui
local window = ImGui.FindWindowByName("##Tooltip_Error")
local use_locked_pos = g.IO.KeyCtrl and window ~= nil and window.WasActive
local c = g.Style.Colors[ImGuiCol.PopupBg]
ImGui.PushStyleColor(ImGuiCol.PopupBg, ImVec4(ImLerp(c.x, 1.0, 0.15), ImLerp(c.y, 0.0, 0.15), ImLerp(c.z, 0.0, 0.15), ImLerp(c.w, 1.0, 0.15)))
if use_locked_pos then
ImGui.SetNextWindowPos(g.ErrorTooltipLockedPos)
end
local _, is_visible = ImGui.Begin("##Tooltip_Error", nil, bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.NoDecoration, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoSavedSettings, ImGuiWindowFlags.AlwaysAutoResize))
ImGui.PopStyleColor()
if is_visible and g.CurrentWindow.BeginCount == 1 then
ImGui.SeparatorText("MESSAGE FROM DEAR IMGUI")
ImGui.BringWindowToDisplayFront(g.CurrentWindow)
ImGui.BringWindowToFocusFront(g.CurrentWindow)
g.ErrorTooltipLockedPos = ImGui.GetWindowPos()
elseif not is_visible then
ImGui.End()
end
return is_visible
end
function ImGui.EndErrorTooltip()
ImGui.End()
end
return true -- [Roblox] ModuleScripts must return exactly one value