WIP: tabs, docking, dragdrop, settings, logging, harness

This commit is contained in:
4DBug
2026-10-08 05:35:25 -05:00
parent 693ced0511
commit fee9a84902
26 changed files with 128896 additions and 1836 deletions
+1
View File
@@ -0,0 +1 @@
[[13, "imgui.lua"], [12931, "imgui_demo.lua"], [13824, "imgui_demo_2.lua"], [16712, "imgui_docking.lua"], [19839, "imgui_dragdrop.lua"], [20131, "imgui_draw.lua"], [25106, "imgui_h.lua"], [27260, "imgui_impl_roblox.lua"], [28016, "imgui_impl_roblox_fonts.lua"], [28794, "imgui_internal.lua"], [31943, "imgui_tables.lua"], [31990, "imgui_tabs.lua"], [33157, "imgui_widgets.lua"], [41247, "imstb_rectpack.lua"], [41693, "imstb_textedit.lua"], [42934, "imstb_truetype.lua"], [45402, "imstd_minstdio.lua"]]
+10818 -917
View File
File diff suppressed because it is too large Load Diff
+1727 -52
View File
File diff suppressed because it is too large Load Diff
+752 -835
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+289
View File
@@ -0,0 +1,289 @@
--- ImGui Sincerely
-- Drag and drop: 1:1 port of imgui.cpp [SECTION] DRAG AND DROP (docking branch)
-- Payload `Data` is any Lua value (no copy is made). `data_size` is optional and only kept for API parity.
function ImGui.IsDragDropActive()
return ImGui.GetCurrentContext().DragDropActive
end
function ImGui.ClearDragDrop()
local g = ImGui.GetCurrentContext()
g.DragDropActive = false
ImGuiPayload_Clear(g.DragDropPayload)
g.DragDropAcceptFlagsCurr = ImGuiDragDropFlags.None
g.DragDropAcceptIdCurr = 0
g.DragDropAcceptIdPrev = 0
g.DragDropAcceptIdCurrRectSurface = FLT_MAX
g.DragDropAcceptFrameCount = -1
end
function ImGui.BeginDragDropSource(flags)
if flags == nil then flags = 0 end
local g = ImGui.GetCurrentContext()
local window = g.CurrentWindow
local mouse_button = ImGuiMouseButton.Left
local source_drag_active = false
local source_id = 0
local source_parent_id = 0
if bit32.band(flags, ImGuiDragDropFlags.SourceExtern) == 0 then
source_id = g.LastItemData.ID
if source_id ~= 0 then
if g.ActiveId ~= source_id then return false end
if g.ActiveIdMouseButton ~= -1 then mouse_button = g.ActiveIdMouseButton end
if g.IO.MouseDown[mouse_button] == false or window.SkipItems then return false end
g.ActiveIdAllowOverlap = false
else
if g.IO.MouseDown[mouse_button] == false or window.SkipItems then return false end
if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) == 0 and (g.ActiveId == 0 or g.ActiveIdWindow ~= window) then
return false
end
if bit32.band(flags, ImGuiDragDropFlags.SourceAllowNullID) == 0 then
IM_ASSERT(false, "Use ImGuiDragDropFlags.SourceAllowNullID for items without an ID")
return false
end
source_id = window:GetIDFromRectangle(g.LastItemData.Rect)
g.LastItemData.ID = source_id
ImGui.KeepAliveID(source_id)
local is_hovered = ImGui.ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.ItemFlags)
if is_hovered and g.IO.MouseClicked[mouse_button] then
ImGui.SetActiveID(source_id, window)
ImGui.FocusWindow(window)
end
if g.ActiveId == source_id then g.ActiveIdAllowOverlap = is_hovered end
end
if g.ActiveId ~= source_id then return false end
source_parent_id = window.IDStack.Data[window.IDStack.Size]
source_drag_active = ImGui.IsMouseDragging(mouse_button)
ImGui.SetActiveIdUsingAllKeyboardKeys()
else
window = nil
source_id = ImHashStr("#SourceExtern")
source_drag_active = true
mouse_button = g.IO.MouseDown[0] and 0 or -1
ImGui.KeepAliveID(source_id)
ImGui.SetActiveID(source_id, nil)
end
IM_ASSERT(g.DragDropWithinTarget == false)
if not source_drag_active then return false end
if not g.DragDropActive then
IM_ASSERT(source_id ~= 0)
ImGui.ClearDragDrop()
local payload = g.DragDropPayload
payload.SourceId = source_id
payload.SourceParentId = source_parent_id
g.DragDropActive = true
g.DragDropSourceFlags = flags
g.DragDropMouseButton = mouse_button
if payload.SourceId == g.ActiveId then g.ActiveIdNoClearOnFocusLoss = true end
end
g.DragDropSourceFrameCount = g.FrameCount
g.DragDropWithinSource = true
if bit32.band(flags, ImGuiDragDropFlags.SourceNoPreviewTooltip) == 0 then
local ret
if g.DragDropAcceptIdPrev ~= 0 and bit32.band(g.DragDropAcceptFlagsPrev, ImGuiDragDropFlags.AcceptNoPreviewTooltip) ~= 0 then
ret = ImGui.BeginTooltipHidden()
else
ret = ImGui.BeginTooltip()
end
IM_ASSERT(ret)
end
if bit32.band(flags, ImGuiDragDropFlags.SourceNoDisableHover) == 0 and bit32.band(flags, ImGuiDragDropFlags.SourceExtern) == 0 then
g.LastItemData.StatusFlags = bit32.band(g.LastItemData.StatusFlags, bit32.bnot(ImGuiItemStatusFlags.HoveredRect))
end
return true
end
function ImGui.EndDragDropSource()
local g = ImGui.GetCurrentContext()
IM_ASSERT(g.DragDropActive)
IM_ASSERT(g.DragDropWithinSource, "Not after a BeginDragDropSource()?")
if bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceNoPreviewTooltip) == 0 then
ImGui.EndTooltip()
end
if g.DragDropPayload.DataFrameCount == -1 then ImGui.ClearDragDrop() end
g.DragDropWithinSource = false
end
--- @param data any # any Lua value
--- @return bool accepted
function ImGui.SetDragDropPayload(type, data, data_size, cond)
local g = ImGui.GetCurrentContext()
local payload = g.DragDropPayload
if cond == nil or cond == 0 then cond = ImGuiCond.Always end
IM_ASSERT(type ~= nil)
IM_ASSERT(#type < 32, "Payload type can be at most 32 characters long")
IM_ASSERT(cond == ImGuiCond.Always or cond == ImGuiCond.Once)
IM_ASSERT(payload.SourceId ~= 0)
if cond == ImGuiCond.Always or payload.DataFrameCount == -1 then
payload.DataType = type
payload.Data = data
payload.DataSize = data_size or ((data ~= nil) and 1 or 0)
end
payload.DataFrameCount = g.FrameCount
return (g.DragDropAcceptFrameCount == g.FrameCount) or (g.DragDropAcceptFrameCount == g.FrameCount - 1)
end
function ImGui.BeginDragDropTargetCustom(bb, id)
local g = ImGui.GetCurrentContext()
if not g.DragDropActive then return false end
local window = g.CurrentWindow
local hovered_window = g.HoveredWindowUnderMovingWindow
if hovered_window == nil or window.RootWindowDockTree ~= hovered_window.RootWindowDockTree then return false end
IM_ASSERT(id ~= 0)
if not ImGui.IsMouseHoveringRect(bb.Min, bb.Max) or (id == g.DragDropPayload.SourceId) then return false end
if window.SkipItems then return false end
IM_ASSERT(g.DragDropWithinTarget == false and g.DragDropWithinSource == false)
g.DragDropTargetRect = ImRect(bb.Min, bb.Max)
g.DragDropTargetClipRect = ImRect(window.ClipRect.Min, window.ClipRect.Max)
g.DragDropTargetId = id
g.DragDropTargetFullViewport = 0
g.DragDropWithinTarget = true
return true
end
function ImGui.BeginDragDropTargetViewport(viewport, p_bb)
local g = ImGui.GetCurrentContext()
if not g.DragDropActive then return false end
local bb = p_bb or viewport:GetWorkRect()
local id = viewport.ID
if g.MouseViewport ~= viewport or not ImGui.IsMouseHoveringRect(bb.Min, bb.Max, false) or (id == g.DragDropPayload.SourceId) then
return false
end
IM_ASSERT(g.DragDropWithinTarget == false and g.DragDropWithinSource == false)
g.DragDropTargetRect = ImRect(bb.Min, bb.Max)
g.DragDropTargetClipRect = ImRect(bb.Min, bb.Max)
g.DragDropTargetId = id
g.DragDropTargetFullViewport = id
g.DragDropWithinTarget = true
return true
end
function ImGui.BeginDragDropTarget()
local g = ImGui.GetCurrentContext()
if not g.DragDropActive then return false end
local window = g.CurrentWindow
if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) == 0 then return false end
local hovered_window = g.HoveredWindowUnderMovingWindow
if hovered_window == nil or window.RootWindowDockTree ~= hovered_window.RootWindowDockTree or window.SkipItems then return false end
local display_rect = (bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasDisplayRect) ~= 0) and g.LastItemData.DisplayRect or g.LastItemData.Rect
local id = g.LastItemData.ID
if id == 0 then
id = window:GetIDFromRectangle(display_rect)
ImGui.KeepAliveID(id)
end
if g.DragDropPayload.SourceId == id then return false end
IM_ASSERT(g.DragDropWithinTarget == false and g.DragDropWithinSource == false)
g.DragDropTargetRect = ImRect(display_rect.Min, display_rect.Max)
local clip = (bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasClipRect) ~= 0) and g.LastItemData.ClipRect or window.ClipRect
g.DragDropTargetClipRect = ImRect(clip.Min, clip.Max)
g.DragDropTargetId = id
g.DragDropWithinTarget = true
return true
end
function ImGui.IsDragDropPayloadBeingAccepted()
local g = ImGui.GetCurrentContext()
return g.DragDropActive and g.DragDropAcceptIdPrev ~= 0
end
--- @return ImGuiPayload?
function ImGui.AcceptDragDropPayload(type, flags)
if flags == nil then flags = 0 end
local g = ImGui.GetCurrentContext()
local payload = g.DragDropPayload
IM_ASSERT(g.DragDropActive)
IM_ASSERT(payload.DataFrameCount ~= -1)
if type ~= nil and not ImGuiPayload_IsDataType(payload, type) then return nil end
local was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId)
local r = g.DragDropTargetRect
local r_surface = r:GetWidth() * r:GetHeight()
if r_surface > g.DragDropAcceptIdCurrRectSurface then return nil end
g.DragDropAcceptFlagsCurr = flags
g.DragDropAcceptIdCurr = g.DragDropTargetId
g.DragDropAcceptIdCurrRectSurface = r_surface
payload.Preview = was_accepted_previously
flags = bit32.bor(flags, bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.AcceptNoDrawDefaultRect))
local draw_target_rect = payload.Preview and bit32.band(flags, ImGuiDragDropFlags.AcceptNoDrawDefaultRect) == 0
if draw_target_rect and g.DragDropTargetFullViewport ~= 0 then
ImGui.RenderDragDropTargetRectForViewport(g.DragDropTargetFullViewport, g.DragDropTargetRect)
elseif draw_target_rect then
ImGui.RenderDragDropTargetRectForItem(r)
end
g.DragDropAcceptFrameCount = g.FrameCount
if bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceExtern) ~= 0 and g.DragDropMouseButton == -1 then
payload.Delivery = was_accepted_previously and (g.DragDropSourceFrameCount < g.FrameCount)
else
payload.Delivery = was_accepted_previously and not ImGui.IsMouseDown(g.DragDropMouseButton)
end
if not payload.Delivery and bit32.band(flags, ImGuiDragDropFlags.AcceptBeforeDelivery) == 0 then return nil end
return payload
end
function ImGui.RenderDragDropTargetRectForItem(bb)
local g = ImGui.GetCurrentContext()
local window = g.CurrentWindow
local bb_display = ImRect(bb.Min, bb.Max)
bb_display:ClipWith(g.DragDropTargetClipRect)
bb_display:Expand(g.Style.DragDropTargetPadding)
local push_clip_rect = not window.ClipRect:Contains(bb_display)
if push_clip_rect then window.DrawList:PushClipRectFullScreen() end
ImGui.RenderDragDropTargetRectEx(window.DrawList, bb_display, g.Style.DragDropTargetRounding)
if push_clip_rect then window.DrawList:PopClipRect() end
end
function ImGui.RenderDragDropTargetRectForViewport(viewport_id, bb)
local g = ImGui.GetCurrentContext()
local viewport = ImGui.FindViewportByID(viewport_id)
IM_ASSERT(viewport ~= nil)
local bb_padded = ImRect(bb.Min, bb.Max)
bb_padded:Expand(-g.Style.DragDropTargetPadding)
ImGui.RenderDragDropTargetRectEx(ImGui.GetForegroundDrawList(viewport), bb_padded, g.Style.DragDropTargetRounding)
end
function ImGui.RenderDragDropTargetRectEx(draw_list, bb, rounding)
local g = ImGui.GetCurrentContext()
draw_list:AddRectFilled(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTargetBg), rounding, 0)
draw_list:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTarget), rounding, 0, g.Style.DragDropTargetBorderSize)
end
--- @return ImGuiPayload?
function ImGui.GetDragDropPayload()
local g = ImGui.GetCurrentContext()
return (g.DragDropActive and g.DragDropPayload.DataFrameCount ~= -1) and g.DragDropPayload or nil
end
function ImGui.EndDragDropTarget()
local g = ImGui.GetCurrentContext()
IM_ASSERT(g.DragDropActive)
IM_ASSERT(g.DragDropWithinTarget)
g.DragDropWithinTarget = false
if g.DragDropPayload.Delivery then ImGui.ClearDragDrop() end
end
-- Payload methods (C++ ImGuiPayload members): payload:IsDataType(t) etc. also work through these globals
function ImGui.PayloadIsDataType(payload, t) return ImGuiPayload_IsDataType(payload, t) end
return true
+218 -1
View File
@@ -137,6 +137,8 @@ function ImGui.StyleColorsDark(dst)
colors[ImGuiCol.TabDimmed] = ImLerpV4V4F(colors[ImGuiCol.Tab], colors[ImGuiCol.TitleBg], 0.80) colors[ImGuiCol.TabDimmed] = ImLerpV4V4F(colors[ImGuiCol.Tab], colors[ImGuiCol.TitleBg], 0.80)
colors[ImGuiCol.TabDimmedSelected] = ImLerpV4V4F(colors[ImGuiCol.TabSelected], colors[ImGuiCol.TitleBg], 0.40) colors[ImGuiCol.TabDimmedSelected] = ImLerpV4V4F(colors[ImGuiCol.TabSelected], colors[ImGuiCol.TitleBg], 0.40)
colors[ImGuiCol.TabDimmedSelectedOverline] = ImVec4(0.50, 0.50, 0.50, 0.00) colors[ImGuiCol.TabDimmedSelectedOverline] = ImVec4(0.50, 0.50, 0.50, 0.00)
colors[ImGuiCol.DockingPreview] = ImVec4(colors[ImGuiCol.HeaderActive].x, colors[ImGuiCol.HeaderActive].y, colors[ImGuiCol.HeaderActive].z, colors[ImGuiCol.HeaderActive].w * 0.7)
colors[ImGuiCol.DockingEmptyBg] = ImVec4(0.20, 0.20, 0.20, 1.00)
colors[ImGuiCol.PlotLines] = ImVec4(0.61, 0.61, 0.61, 1.00) colors[ImGuiCol.PlotLines] = ImVec4(0.61, 0.61, 0.61, 1.00)
colors[ImGuiCol.PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00) colors[ImGuiCol.PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00)
colors[ImGuiCol.PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00) colors[ImGuiCol.PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00)
@@ -205,6 +207,8 @@ function ImGui.StyleColorsClassic(dst)
colors[ImGuiCol.TabDimmed] = ImLerpV4V4F(colors[ImGuiCol.Tab], colors[ImGuiCol.TitleBg], 0.80) colors[ImGuiCol.TabDimmed] = ImLerpV4V4F(colors[ImGuiCol.Tab], colors[ImGuiCol.TitleBg], 0.80)
colors[ImGuiCol.TabDimmedSelected] = ImLerpV4V4F(colors[ImGuiCol.TabSelected], colors[ImGuiCol.TitleBg], 0.40) colors[ImGuiCol.TabDimmedSelected] = ImLerpV4V4F(colors[ImGuiCol.TabSelected], colors[ImGuiCol.TitleBg], 0.40)
colors[ImGuiCol.TabDimmedSelectedOverline] = ImVec4(0.53, 0.53, 0.87, 0.00) colors[ImGuiCol.TabDimmedSelectedOverline] = ImVec4(0.53, 0.53, 0.87, 0.00)
colors[ImGuiCol.DockingPreview] = ImVec4(colors[ImGuiCol.Header].x, colors[ImGuiCol.Header].y, colors[ImGuiCol.Header].z, colors[ImGuiCol.Header].w * 0.7)
colors[ImGuiCol.DockingEmptyBg] = ImVec4(0.20, 0.20, 0.20, 1.00)
colors[ImGuiCol.PlotLines] = ImVec4(1.00, 1.00, 1.00, 1.00) colors[ImGuiCol.PlotLines] = ImVec4(1.00, 1.00, 1.00, 1.00)
colors[ImGuiCol.PlotLinesHovered] = ImVec4(0.90, 0.70, 0.00, 1.00) colors[ImGuiCol.PlotLinesHovered] = ImVec4(0.90, 0.70, 0.00, 1.00)
colors[ImGuiCol.PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00) colors[ImGuiCol.PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00)
@@ -274,6 +278,8 @@ function ImGui.StyleColorsLight(dst)
colors[ImGuiCol.TabDimmed] = ImLerpV4V4F(colors[ImGuiCol.Tab], colors[ImGuiCol.TitleBg], 0.80) colors[ImGuiCol.TabDimmed] = ImLerpV4V4F(colors[ImGuiCol.Tab], colors[ImGuiCol.TitleBg], 0.80)
colors[ImGuiCol.TabDimmedSelected] = ImLerpV4V4F(colors[ImGuiCol.TabSelected], colors[ImGuiCol.TitleBg], 0.40) colors[ImGuiCol.TabDimmedSelected] = ImLerpV4V4F(colors[ImGuiCol.TabSelected], colors[ImGuiCol.TitleBg], 0.40)
colors[ImGuiCol.TabDimmedSelectedOverline] = ImVec4(0.26, 0.59, 1.00, 0.00) colors[ImGuiCol.TabDimmedSelectedOverline] = ImVec4(0.26, 0.59, 1.00, 0.00)
colors[ImGuiCol.DockingPreview] = ImVec4(colors[ImGuiCol.Header].x, colors[ImGuiCol.Header].y, colors[ImGuiCol.Header].z, colors[ImGuiCol.Header].w * 0.7)
colors[ImGuiCol.DockingEmptyBg] = ImVec4(0.68, 0.69, 0.73, 1.00)
colors[ImGuiCol.PlotLines] = ImVec4(0.39, 0.39, 0.39, 1.00) colors[ImGuiCol.PlotLines] = ImVec4(0.39, 0.39, 0.39, 1.00)
colors[ImGuiCol.PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00) colors[ImGuiCol.PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00)
colors[ImGuiCol.PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00) colors[ImGuiCol.PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00)
@@ -328,6 +334,18 @@ function ImGui.ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx,
end end
end end
-- Rotate and translate vertices [vert_start_idx, vert_end_idx) (1-based, like _VtxCurrentIdx)
function ImGui.ShadeVertsTransformPos(draw_list, vert_start_idx, vert_end_idx, pivot_in, cos_a, sin_a, pivot_out)
local data = draw_list.VtxBuffer.Data
local pix, piy, pox, poy = pivot_in.x, pivot_in.y, pivot_out.x, pivot_out.y
for i = vert_start_idx, vert_end_idx - 1 do
local pos = data[i][1]
local x, y = pos[1] - pix, pos[2] - piy
pos[1] = x * cos_a - y * sin_a + pox
pos[2] = x * sin_a + y * cos_a + poy
end
end
-- Distribute UV over (a, b) rectangle -- Distribute UV over (a, b) rectangle
--- @param draw_list ImDrawList --- @param draw_list ImDrawList
--- @param vert_start_idx int --- @param vert_start_idx int
@@ -3145,7 +3163,7 @@ function MT.ImDrawList:_ClearFreeMemory()
self._TextureStack:clear() self._TextureStack:clear()
-- self._CallbacksDataBuf:clear() -- self._CallbacksDataBuf:clear()
self._Path:clear() self._Path:clear()
-- self._Splitter.ClearFreeMemory() self._Splitter:ClearFreeMemory()
end end
function MT.ImDrawList:AddDrawCmd() function MT.ImDrawList:AddDrawCmd()
@@ -3312,6 +3330,184 @@ function MT.ImDrawList:_OnChangedVtxOffset()
curr_cmd.VtxOffset = self._CmdHeader.VtxOffset curr_cmd.VtxOffset = self._CmdHeader.VtxOffset
end end
----------------------------------------------------------------
-- [SECTION] ImDrawListSplitter
----------------------------------------------------------------
-- Lua: channels swap ImVector objects with the draw list (C++ memcpy of the vector headers).
-- Channel 0 holds nothing while it is the current channel (its buffers live in the draw list).
-- Copy ClipRect, TexRef, VtxOffset (TexRef is assigned by reference, never mutated)
local function ImDrawCmd_HeaderCopyRef(dst, src)
ImVec4_Copy(dst.ClipRect, src.ClipRect)
dst.TexRef = src.TexRef
dst.VtxOffset = src.VtxOffset
end
function MT.ImDrawListSplitter:Clear()
self._Current = 0
self._Count = 1
end
function MT.ImDrawListSplitter:ClearFreeMemory()
for i = 1, self._Channels.Size do
local ch = self._Channels.Data[i]
if i - 1 == self._Current then
-- Current channel is a copy of CmdBuffer/IdxBuffer, don't destruct again
self._Channels.Data[i] = ImDrawChannel()
else
ch._CmdBuffer:clear()
ch._IdxBuffer:clear()
end
end
self._Current = 0
self._Count = 1
self._Channels:clear()
end
--- @param draw_list ImDrawList
--- @param channels_count int
function MT.ImDrawListSplitter:Split(draw_list, channels_count)
IM_ASSERT(self._Current == 0 and self._Count <= 1, "Nested channel splitting is not supported. Please use separate instances of ImDrawListSplitter.")
local old_channels_count = self._Channels.Size
if old_channels_count < channels_count then
self._Channels:reserve(channels_count)
self._Channels:resize(channels_count)
end
self._Count = channels_count
-- Channel 0 is the draw list itself (memset to 0 in C++)
local channels = self._Channels.Data
channels[1] = { _CmdBuffer = nil, _IdxBuffer = nil }
for i = 2, channels_count do
if i > old_channels_count or channels[i] == nil then
channels[i] = ImDrawChannel()
else
channels[i]._CmdBuffer:resize(0)
channels[i]._IdxBuffer:resize(0)
end
end
end
--- @param draw_list ImDrawList
function MT.ImDrawListSplitter:Merge(draw_list)
-- Note that we never use or rely on _Channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use.
if self._Count <= 1 then
return
end
self:SetCurrentChannel(draw_list, 0)
draw_list:_PopUnusedDrawCmd()
-- Calculate our final buffer sizes. Also fix the incorrect IdxOffset values in each command.
local new_cmd_buffer_count = 0
local new_idx_buffer_count = 0
local last_cmd = (self._Count > 0 and draw_list.CmdBuffer.Size > 0) and draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size] or nil
local idx_offset = last_cmd and (last_cmd.IdxOffset + last_cmd.ElemCount) or 0
local channels = self._Channels.Data
for i = 2, self._Count do
local ch = channels[i]
local cmds = ch._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 > 0 and last_cmd ~= nil then
-- Do not include ImDrawCmd_AreSequentialIdxOffset() in the compare as we rebuild IdxOffset values ourselves.
-- Manipulating IdxOffset (e.g. by reordering draw commands like done by RenderDimmedBackgroundBehindWindow()) is not supported within a splitter.
local next_cmd = cmds.Data[1]
if ImDrawCmd_HeaderCompare(last_cmd, next_cmd) and last_cmd.UserCallback == nil and next_cmd.UserCallback == nil then
-- Merge previous channel last draw command with current channel first draw command if matching.
last_cmd.ElemCount = last_cmd.ElemCount + next_cmd.ElemCount
idx_offset = idx_offset + next_cmd.ElemCount
cmds:erase(1) -- FIXME-OPT: Improve for multiple merges.
end
end
if cmds.Size > 0 then
last_cmd = cmds.Data[cmds.Size]
end
new_cmd_buffer_count = new_cmd_buffer_count + cmds.Size
new_idx_buffer_count = new_idx_buffer_count + ch._IdxBuffer.Size
for cmd_n = 1, cmds.Size do
cmds.Data[cmd_n].IdxOffset = idx_offset
idx_offset = idx_offset + cmds.Data[cmd_n].ElemCount
end
end
local cmd_write = draw_list.CmdBuffer.Size + 1
local idx_write = draw_list.IdxBuffer.Size + 1
draw_list.CmdBuffer:resize(draw_list.CmdBuffer.Size + new_cmd_buffer_count)
draw_list.IdxBuffer:resize(draw_list.IdxBuffer.Size + new_idx_buffer_count)
-- Write commands and indices in order (they are fairly small structures, we don't copy vertices only indices)
local dst_cmds = draw_list.CmdBuffer.Data
local dst_idx = draw_list.IdxBuffer.Data
for i = 2, self._Count do
local ch = channels[i]
local src_cmds = ch._CmdBuffer.Data
for n = 1, ch._CmdBuffer.Size do
dst_cmds[cmd_write] = src_cmds[n]
src_cmds[n] = nil -- ownership moved to the draw list
cmd_write = cmd_write + 1
end
local src_idx = ch._IdxBuffer.Data
for n = 1, ch._IdxBuffer.Size do
dst_idx[idx_write] = src_idx[n]
idx_write = idx_write + 1
end
end
draw_list._IdxWritePtr = idx_write
-- Ensure there's always a non-callback draw command trailing the command-buffer
if draw_list.CmdBuffer.Size == 0 or draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size].UserCallback ~= nil then
draw_list:AddDrawCmd()
end
-- If current command is used with different settings we need to add a new command
local curr_cmd = draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size]
if curr_cmd.ElemCount == 0 then
ImDrawCmd_HeaderCopyRef(curr_cmd, draw_list._CmdHeader) -- Copy ClipRect, TexRef, VtxOffset
elseif not ImDrawCmd_HeaderCompare(curr_cmd, draw_list._CmdHeader) then
draw_list:AddDrawCmd()
end
self._Count = 1
end
--- @param draw_list ImDrawList
--- @param idx int # 0-based channel index
function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx)
IM_ASSERT(idx >= 0 and idx < self._Count)
if self._Current == idx then
return
end
-- Overwrite ImVector (12/16 bytes), four times. This is merely a silly optimization instead of doing .swap()
local channels = self._Channels.Data
local cur = channels[self._Current + 1]
cur._CmdBuffer = draw_list.CmdBuffer
cur._IdxBuffer = draw_list.IdxBuffer
self._Current = idx
local ch = channels[idx + 1]
draw_list.CmdBuffer = ch._CmdBuffer
draw_list.IdxBuffer = ch._IdxBuffer
draw_list._IdxWritePtr = draw_list.IdxBuffer.Size + 1
-- If current command is used with different settings we need to add a new command
local curr_cmd = (draw_list.CmdBuffer.Size == 0) and nil or draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size]
if curr_cmd == nil then
draw_list:AddDrawCmd()
elseif curr_cmd.ElemCount == 0 then
ImDrawCmd_HeaderCopyRef(curr_cmd, draw_list._CmdHeader) -- Copy ClipRect, TexRef, VtxOffset
elseif not ImDrawCmd_HeaderCompare(curr_cmd, draw_list._CmdHeader) then
draw_list:AddDrawCmd()
end
end
--- @param count int
function MT.ImDrawList:ChannelsSplit(count) self._Splitter:Split(self, count) end
function MT.ImDrawList:ChannelsMerge() self._Splitter:Merge(self) end
--- @param n int # 0-based
function MT.ImDrawList:ChannelsSetCurrent(n) self._Splitter:SetCurrentChannel(self, n) end
--- @param points ImVec2[] --- @param points ImVec2[]
--- @param points_count int --- @param points_count int
--- @param col ImU32 --- @param col ImU32
@@ -3912,6 +4108,27 @@ function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness)
self:PathStroke(col, thickness, ImDrawFlags.Closed) self:PathStroke(col, thickness, ImDrawFlags.Closed)
end end
function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness)
if thickness == nil then thickness = 1.0 end
if bit32.band(col, IM_COL32_A_MASK) == 0 then return end
self:PathLineTo(p1)
self:PathLineTo(p2)
self:PathLineTo(p3)
self:PathLineTo(p4)
self:PathStroke(col, thickness, ImDrawFlags.Closed)
end
function MT.ImDrawList:AddQuadFilled(p1, p2, p3, p4, col)
if bit32.band(col, IM_COL32_A_MASK) == 0 then return end
self:PathLineTo(p1)
self:PathLineTo(p2)
self:PathLineTo(p3)
self:PathLineTo(p4)
self:PathFillConvex(col)
end
function MT.ImDrawList:AddTriangleFilled(p1, p2, p3, col) function MT.ImDrawList:AddTriangleFilled(p1, p2, p3, col)
if bit32.band(col, IM_COL32_A_MASK) == 0 then return end if bit32.band(col, IM_COL32_A_MASK) == 0 then return end
+174 -22
View File
@@ -513,7 +513,9 @@ function ImDrawChannel() return { _CmdBuffer = ImVector(), _IdxBuffer = ImVector
--- @return ImDrawListSplitter --- @return ImDrawListSplitter
--- @nodiscard --- @nodiscard
function ImDrawListSplitter() return { _Current = 0, _Count = 0, _Channels = ImVector() } end MT.ImDrawListSplitter = {} -- methods in imgui_draw.lua
MT.ImDrawListSplitter.__index = MT.ImDrawListSplitter
function ImDrawListSplitter() return setmetatable({ _Current = 0, _Count = 0, _Channels = ImVector() }, MT.ImDrawListSplitter) end
--- @class ImDrawList --- @class ImDrawList
--- @field CmdBuffer ImVector<ImDrawCmd> --- @field CmdBuffer ImVector<ImDrawCmd>
@@ -1000,6 +1002,7 @@ ImGuiConfigFlags = {
NoMouse = bitLShift(1, 4), NoMouse = bitLShift(1, 4),
NoMouseCursorChange = bitLShift(1, 5), NoMouseCursorChange = bitLShift(1, 5),
NoKeyboard = bitLShift(1, 6), NoKeyboard = bitLShift(1, 6),
DockingEnable = bitLShift(1, 7),
ViewportsEnable = bitLShift(1, 10), ViewportsEnable = bitLShift(1, 10),
IsSRGB = bitLShift(1, 20), IsSRGB = bitLShift(1, 20),
IsTouchScreen = bitLShift(1, 21) IsTouchScreen = bitLShift(1, 21)
@@ -1054,7 +1057,25 @@ function ImGuiIO()
ConfigWindowsResizeFromEdges = true, ConfigWindowsResizeFromEdges = true,
ConfigDebugIniSettings = false, ConfigDebugIniSettings = false,
ConfigWindowsCopyContentsWithCtrlC = false,
ConfigWindowsMoveFromTitleBarOnly = false,
ConfigErrorRecovery = true,
ConfigErrorRecoveryEnableAssert = true,
ConfigErrorRecoveryEnableDebugLog = true,
ConfigErrorRecoveryEnableTooltip = true,
ConfigDebugHighlightIdConflicts = false,
ConfigDebugHighlightIdConflictsShowItemPicker = true,
IniFilename = "imgui.ini", -- Roblox: no disk access, only used as a flag (nil disables .ini handling)
IniSavingRate = 5.0,
LogFilename = "imgui_log.txt",
WantSaveIniSettings = false,
ConfigDockingNoSplit = false,
ConfigDockingNoDockingOver = false,
ConfigDockingWithShift = false,
ConfigDockingAlwaysTabBar = false,
ConfigDockingTransparentPayload = false,
ConfigViewportsNoAutoMerge = false, ConfigViewportsNoAutoMerge = false,
ConfigViewportsNoTaskBarIcon = false, ConfigViewportsNoTaskBarIcon = false,
ConfigViewportsNoDecoration = true, ConfigViewportsNoDecoration = true,
@@ -1084,6 +1105,7 @@ function ImGuiIO()
MouseDoubleClicked = {[0] = false, [1] = false, [2] = false}, MouseDoubleClicked = {[0] = false, [1] = false, [2] = false},
MouseDoubleClickTime = 0.30, MouseDoubleClickTime = 0.30,
MouseSingleClickDelay = 0.50,
MouseDoubleClickMaxDist = 6.0, MouseDoubleClickMaxDist = 6.0,
MouseDragThreshold = 6.0, MouseDragThreshold = 6.0,
KeyRepeatDelay = 0.275, KeyRepeatDelay = 0.275,
@@ -1546,6 +1568,7 @@ ImGuiHoveredFlags = {
RootWindow = bitLShift(1, 1), RootWindow = bitLShift(1, 1),
AnyWindow = bitLShift(1, 2), AnyWindow = bitLShift(1, 2),
NoPopupHierarchy = bitLShift(1, 3), NoPopupHierarchy = bitLShift(1, 3),
DockHierarchy = bitLShift(1, 4),
AllowWhenBlockedByPopup = bitLShift(1, 5), AllowWhenBlockedByPopup = bitLShift(1, 5),
AllowWhenBlockedByActiveItem = bitLShift(1, 7), AllowWhenBlockedByActiveItem = bitLShift(1, 7),
AllowWhenOverlappedByItem = bitLShift(1, 8), AllowWhenOverlappedByItem = bitLShift(1, 8),
@@ -1560,11 +1583,22 @@ ImGuiHoveredFlags = {
NoSharedDelay = bitLShift(1, 17) NoSharedDelay = bitLShift(1, 17)
} }
--- @enum ImGuiFocusedFlags
ImGuiFocusedFlags = {
None = 0,
ChildWindows = bitLShift(1, 0), -- Return true if any children of the window is focused
RootWindow = bitLShift(1, 1), -- Test from root window (top most parent of the current hierarchy)
AnyWindow = bitLShift(1, 2), -- Return true if any window is focused
NoPopupHierarchy = bitLShift(1, 3), -- Do not consider popup hierarchy
DockHierarchy = bitLShift(1, 4), -- Consider docking hierarchy
}
ImGuiFocusedFlags.RootAndChildWindows = bitOr(ImGuiFocusedFlags.RootWindow, ImGuiFocusedFlags.ChildWindows)
ImGuiHoveredFlags.AllowWhenOverlapped = bitOr(ImGuiHoveredFlags.AllowWhenOverlappedByItem, ImGuiHoveredFlags.AllowWhenOverlappedByWindow) ImGuiHoveredFlags.AllowWhenOverlapped = bitOr(ImGuiHoveredFlags.AllowWhenOverlappedByItem, ImGuiHoveredFlags.AllowWhenOverlappedByWindow)
ImGuiHoveredFlags.RectOnly = bitOr(ImGuiHoveredFlags.AllowWhenBlockedByPopup, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, ImGuiHoveredFlags.AllowWhenOverlapped) ImGuiHoveredFlags.RectOnly = bitOr(ImGuiHoveredFlags.AllowWhenBlockedByPopup, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, ImGuiHoveredFlags.AllowWhenOverlapped)
ImGuiHoveredFlags.RootAndChildWindows = bitOr(ImGuiHoveredFlags.RootWindow, ImGuiHoveredFlags.ChildWindows) ImGuiHoveredFlags.RootAndChildWindows = bitOr(ImGuiHoveredFlags.RootWindow, ImGuiHoveredFlags.ChildWindows)
ImGuiHoveredFlags.DelayMask_ = bitOr(ImGuiHoveredFlags.DelayNone, ImGuiHoveredFlags.DelayShort, ImGuiHoveredFlags.DelayNormal, ImGuiHoveredFlags.NoSharedDelay) ImGuiHoveredFlags.DelayMask_ = bitOr(ImGuiHoveredFlags.DelayNone, ImGuiHoveredFlags.DelayShort, ImGuiHoveredFlags.DelayNormal, ImGuiHoveredFlags.NoSharedDelay)
ImGuiHoveredFlags.AllowedMaskForIsWindowHovered = bitOr(ImGuiHoveredFlags.ChildWindows, ImGuiHoveredFlags.RootWindow, ImGuiHoveredFlags.AnyWindow, ImGuiHoveredFlags.NoPopupHierarchy, ImGuiHoveredFlags.AllowWhenBlockedByPopup, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, ImGuiHoveredFlags.ForTooltip, ImGuiHoveredFlags.Stationary) ImGuiHoveredFlags.AllowedMaskForIsWindowHovered = bitOr(ImGuiHoveredFlags.ChildWindows, ImGuiHoveredFlags.RootWindow, ImGuiHoveredFlags.AnyWindow, ImGuiHoveredFlags.NoPopupHierarchy, ImGuiHoveredFlags.DockHierarchy, ImGuiHoveredFlags.AllowWhenBlockedByPopup, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, ImGuiHoveredFlags.ForTooltip, ImGuiHoveredFlags.Stationary)
ImGuiHoveredFlags.AllowedMaskForIsItemHovered = bitOr(ImGuiHoveredFlags.AllowWhenBlockedByPopup, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, ImGuiHoveredFlags.AllowWhenOverlapped, ImGuiHoveredFlags.AllowWhenDisabled, ImGuiHoveredFlags.NoNavOverride, ImGuiHoveredFlags.ForTooltip, ImGuiHoveredFlags.Stationary, ImGuiHoveredFlags.DelayMask_) ImGuiHoveredFlags.AllowedMaskForIsItemHovered = bitOr(ImGuiHoveredFlags.AllowWhenBlockedByPopup, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem, ImGuiHoveredFlags.AllowWhenOverlapped, ImGuiHoveredFlags.AllowWhenDisabled, ImGuiHoveredFlags.NoNavOverride, ImGuiHoveredFlags.ForTooltip, ImGuiHoveredFlags.Stationary, ImGuiHoveredFlags.DelayMask_)
--- @enum ImGuiKey --- @enum ImGuiKey
@@ -1683,26 +1717,28 @@ ImGuiCol = {
TabDimmed = 39, TabDimmed = 39,
TabDimmedSelected = 40, TabDimmedSelected = 40,
TabDimmedSelectedOverline = 41, TabDimmedSelectedOverline = 41,
PlotLines = 42, DockingPreview = 42,
PlotLinesHovered = 43, DockingEmptyBg = 43,
PlotHistogram = 44, PlotLines = 44,
PlotHistogramHovered = 45, PlotLinesHovered = 45,
TableHeaderBg = 46, PlotHistogram = 46,
TableBorderStrong = 47, PlotHistogramHovered = 47,
TableBorderLight = 48, TableHeaderBg = 48,
TableRowBg = 49, TableBorderStrong = 49,
TableRowBgAlt = 50, TableBorderLight = 50,
TextLink = 51, TableRowBg = 51,
TextSelectedBg = 52, TableRowBgAlt = 52,
TreeLines = 53, TextLink = 53,
DragDropTarget = 54, TextSelectedBg = 54,
DragDropTargetBg = 55, TreeLines = 55,
UnsavedMarker = 56, DragDropTarget = 56,
NavCursor = 57, DragDropTargetBg = 57,
NavWindowingHighlight = 58, UnsavedMarker = 58,
NavWindowingDimBg = 59, NavCursor = 59,
ModalWindowDimBg = 60, NavWindowingHighlight = 60,
COUNT = 61 NavWindowingDimBg = 61,
ModalWindowDimBg = 62,
COUNT = 63
} }
--- @enum ImGuiBackendFlags --- @enum ImGuiBackendFlags
@@ -1993,7 +2029,123 @@ ImGuiTreeNodeFlags.DrawLinesMask_ = bitOr(ImGuiTreeNodeFlags.DrawLinesNone, ImGu
ImGuiTableFlags = ImGuiTableFlags =
{ {
None = 0, None = 0,
Resizable = bitLShift(1, 0),
Reorderable = bitLShift(1, 1),
Hideable = bitLShift(1, 2),
Sortable = bitLShift(1, 3),
NoSavedSettings = bitLShift(1, 4),
ContextMenuInBody = bitLShift(1, 5),
RowBg = bitLShift(1, 6),
BordersInnerH = bitLShift(1, 7),
BordersOuterH = bitLShift(1, 8),
BordersInnerV = bitLShift(1, 9),
BordersOuterV = bitLShift(1, 10),
NoBordersInBody = bitLShift(1, 11),
NoBordersInBodyUntilResize = bitLShift(1, 12),
SizingFixedFit = bitLShift(1, 13),
SizingFixedSame = bitLShift(2, 13),
SizingStretchProp = bitLShift(3, 13),
SizingStretchSame = bitLShift(4, 13),
NoHostExtendX = bitLShift(1, 16),
NoHostExtendY = bitLShift(1, 17),
NoKeepColumnsVisible = bitLShift(1, 18),
PreciseWidths = bitLShift(1, 19),
NoClip = bitLShift(1, 20),
PadOuterX = bitLShift(1, 21),
NoPadOuterX = bitLShift(1, 22),
NoPadInnerX = bitLShift(1, 23),
ScrollX = bitLShift(1, 24),
ScrollY = bitLShift(1, 25),
SortMulti = bitLShift(1, 26),
SortTristate = bitLShift(1, 27),
HighlightHoveredColumn = bitLShift(1, 28),
} }
ImGuiTableFlags.BordersH = bitOr(ImGuiTableFlags.BordersInnerH, ImGuiTableFlags.BordersOuterH)
ImGuiTableFlags.BordersV = bitOr(ImGuiTableFlags.BordersInnerV, ImGuiTableFlags.BordersOuterV)
ImGuiTableFlags.BordersInner = bitOr(ImGuiTableFlags.BordersInnerV, ImGuiTableFlags.BordersInnerH)
ImGuiTableFlags.BordersOuter = bitOr(ImGuiTableFlags.BordersOuterV, ImGuiTableFlags.BordersOuterH)
ImGuiTableFlags.Borders = bitOr(ImGuiTableFlags.BordersInner, ImGuiTableFlags.BordersOuter)
ImGuiTableFlags.SizingMask_ = bitOr(ImGuiTableFlags.SizingFixedFit, ImGuiTableFlags.SizingFixedSame, ImGuiTableFlags.SizingStretchProp, ImGuiTableFlags.SizingStretchSame)
--- @enum ImGuiTableColumnFlags
ImGuiTableColumnFlags =
{
None = 0,
Disabled = bitLShift(1, 0),
DefaultHide = bitLShift(1, 1),
DefaultSort = bitLShift(1, 2),
WidthStretch = bitLShift(1, 3),
WidthFixed = bitLShift(1, 4),
NoResize = bitLShift(1, 5),
NoReorder = bitLShift(1, 6),
NoHide = bitLShift(1, 7),
NoClip = bitLShift(1, 8),
NoSort = bitLShift(1, 9),
NoSortAscending = bitLShift(1, 10),
NoSortDescending = bitLShift(1, 11),
NoHeaderLabel = bitLShift(1, 12),
NoHeaderWidth = bitLShift(1, 13),
PreferSortAscending = bitLShift(1, 14),
PreferSortDescending = bitLShift(1, 15),
IndentEnable = bitLShift(1, 16),
IndentDisable = bitLShift(1, 17),
AngledHeader = bitLShift(1, 18),
IsEnabled = bitLShift(1, 24),
IsVisible = bitLShift(1, 25),
IsSorted = bitLShift(1, 26),
IsHovered = bitLShift(1, 27),
NoDirectResize_ = bitLShift(1, 30),
}
ImGuiTableColumnFlags.WidthMask_ = bitOr(ImGuiTableColumnFlags.WidthStretch, ImGuiTableColumnFlags.WidthFixed)
ImGuiTableColumnFlags.IndentMask_ = bitOr(ImGuiTableColumnFlags.IndentEnable, ImGuiTableColumnFlags.IndentDisable)
ImGuiTableColumnFlags.StatusMask_ = bitOr(ImGuiTableColumnFlags.IsEnabled, ImGuiTableColumnFlags.IsVisible, ImGuiTableColumnFlags.IsSorted, ImGuiTableColumnFlags.IsHovered)
--- @enum ImGuiTableRowFlags
ImGuiTableRowFlags = { None = 0, Headers = bitLShift(1, 0) }
--- @enum ImGuiTableBgTarget
ImGuiTableBgTarget = { None = 0, RowBg0 = 1, RowBg1 = 2, CellBg = 3 }
--- @enum ImGuiSortDirection
ImGuiSortDirection = { None = 0, Ascending = 1, Descending = 2 }
--- @class ImGuiPayload
--- Drag and drop payload. `Data` is any Lua value (C++ stores raw bytes); `DataSize` kept for API parity.
local IM_PAYLOAD = {}
IM_PAYLOAD.__index = IM_PAYLOAD
function IM_PAYLOAD:Clear() ImGuiPayload_Clear(self) end
function IM_PAYLOAD:IsDataType(t) return ImGuiPayload_IsDataType(self, t) end
function IM_PAYLOAD:IsPreview() return self.Preview end
function IM_PAYLOAD:IsDelivery() return self.Delivery end
function ImGuiPayload()
return setmetatable({ Data = nil, DataSize = 0, SourceId = 0, SourceParentId = 0, DataFrameCount = -1, DataType = "", Preview = false, Delivery = false }, IM_PAYLOAD)
end
function ImGuiPayload_Clear(p) p.SourceId = 0; p.SourceParentId = 0; p.Data = nil; p.DataSize = 0; p.DataType = ""; p.DataFrameCount = -1; p.Preview = false; p.Delivery = false end
function ImGuiPayload_IsDataType(p, t) return p.DataFrameCount ~= -1 and p.DataType == t end
function ImGuiPayload_IsPreview(p) return p.Preview end
function ImGuiPayload_IsDelivery(p) return p.Delivery end
--- @class ImGuiTextBuffer # string builder: Buf is a list of string chunks
local ImGuiTextBufferMT = {}
ImGuiTextBufferMT.__index = ImGuiTextBufferMT
function ImGuiTextBuffer() return setmetatable({ Buf = {} }, ImGuiTextBufferMT) end
function ImGuiTextBufferMT:clear() self.Buf = {} end
function ImGuiTextBufferMT:empty() return #self.Buf == 0 end
function ImGuiTextBufferMT:append(str) self.Buf[#self.Buf + 1] = str end
function ImGuiTextBufferMT:appendf(fmt, ...) self.Buf[#self.Buf + 1] = string.format(fmt, ...) end
function ImGuiTextBufferMT:c_str() return table.concat(self.Buf) end
function ImGuiTextBufferMT:size() return #self:c_str() end
--- @class ImGuiSettingsHandler
--- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line);
--- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer)
function ImGuiSettingsHandler()
return { TypeName = nil, TypeHash = 0, ClearAllFn = nil, ReadInitFn = nil, ReadOpenFn = nil, ReadLineFn = nil,
ApplyAllFn = nil, WriteAllFn = nil, CleanupFn = nil, UserData = nil }
end
return true -- [Roblox] ModuleScripts must return exactly one value return true -- [Roblox] ModuleScripts must return exactly one value
+223
View File
@@ -33,6 +33,44 @@ function ImGui._ByteArrayToString(buf, buf_begin, buf_end)
return table.concat(str) return table.concat(str)
end end
--- ImPool<T>: pool of objects keyed by ID. Public indices are 0-based like upstream (Buf is stored 1-based).
--- `ctor` creates a new element. Removed slots are recycled via FreeIdx (-1 = none; free slots chain through `_NextFree`).
local IM_POOL = {}
IM_POOL.__index = IM_POOL
function ImPool(ctor) return setmetatable({ Buf = {}, Map = {}, FreeIdx = 0, AliveCount = 0, BufSize = 0, _Ctor = ctor }, IM_POOL) end
function IM_POOL:GetByKey(key) local i = self.Map[key]; return i and self.Buf[i + 1] or nil end
function IM_POOL:GetByIndex(n) return self.Buf[n + 1] end
function IM_POOL:GetIndex(p) return p._PoolIdx end
function IM_POOL:Contains(p) return p ~= nil and self.Buf[(p._PoolIdx or -1) + 1] == p end
function IM_POOL:GetMapSize() return self.BufSize end
function IM_POOL:Clear() self.Buf = {}; self.Map = {}; self.FreeIdx = 0; self.AliveCount = 0; self.BufSize = 0 end
function IM_POOL:Add()
local idx
if self._Free and #self._Free > 0 then idx = table.remove(self._Free) else idx = self.BufSize; self.BufSize = self.BufSize + 1 end
local p = self._Ctor and self._Ctor() or {}
p._PoolIdx = idx
self.Buf[idx + 1] = p
self.AliveCount = self.AliveCount + 1
return p
end
function IM_POOL:GetOrAddByKey(key)
local i = self.Map[key]
if i then return self.Buf[i + 1] end
local p = self:Add(); self.Map[key] = p._PoolIdx; return p
end
function IM_POOL:Remove(key, p)
if type(key) == "table" then p = key; key = nil end
if key == nil then for k, i in pairs(self.Map) do if i == p._PoolIdx then key = k break end end end
if key ~= nil then self.Map[key] = nil end
self.Buf[p._PoolIdx + 1] = false
self._Free = self._Free or {}
table.insert(self._Free, p._PoolIdx)
self.AliveCount = self.AliveCount - 1
end
function IM_POOL:Reserve(n) end
-- NOTE: removed slots hold `false`; iterate with `for i = 0, pool:GetMapSize() - 1 do local t = pool:GetByIndex(i) if t then ... end end`
--- @type ImGuiContext? --- @type ImGuiContext?
local GImGui local GImGui
@@ -1162,6 +1200,15 @@ function ImGuiNextWindowData()
ScrollVal = ImVec2(), ScrollVal = ImVec2(),
WindowFlags = nil, WindowFlags = nil,
ChildFlags = nil, ChildFlags = nil,
WindowFlagsSet = 0,
WindowFlagsClear = 0,
ChildFlagsSet = 0,
ChildFlagsClear = 0,
ViewportId = 0,
DockId = 0,
DockCond = 0,
PosUndock = false,
WindowClass = nil,
CollapsedVal = nil, CollapsedVal = nil,
SizeConstraintRect = nil, SizeConstraintRect = nil,
SizeCallback = nil, SizeCallback = nil,
@@ -1293,6 +1340,7 @@ function ImGuiStyle()
PopupBorderSize = 1.0, PopupBorderSize = 1.0,
DockingSeparatorSize = 2.0, DockingSeparatorSize = 2.0,
DockingNodeHasCloseButton = true,
MouseCursorScale = 1.0, MouseCursorScale = 1.0,
ItemSpacing = ImVec2(8, 4), ItemSpacing = ImVec2(8, 4),
@@ -1655,6 +1703,91 @@ end
--- @field DebugAllocInfo ImGuiDebugAllocInfo --- @field DebugAllocInfo ImGuiDebugAllocInfo
--- @field TempBuffer ImVector<char> --- @field TempBuffer ImVector<char>
----------------------------------------------------------------
-- [SECTION] Tab bar, Tab item support (public flags from imgui.h + private extensions)
----------------------------------------------------------------
--- @enum ImGuiTabBarFlags
ImGuiTabBarFlags = {
None = 0,
Reorderable = bitLShift(1, 0),
AutoSelectNewTabs = bitLShift(1, 1),
TabListPopupButton = bitLShift(1, 2),
NoCloseWithMiddleMouseButton = bitLShift(1, 3),
NoTabListScrollingButtons = bitLShift(1, 4),
NoTooltip = bitLShift(1, 5),
DrawSelectedOverline = bitLShift(1, 6),
FittingPolicyMixed = bitLShift(1, 7),
FittingPolicyShrink = bitLShift(1, 8),
FittingPolicyScroll = bitLShift(1, 9),
-- Private
DockNode = bitLShift(1, 20),
IsFocused = bitLShift(1, 21),
SaveSettings = bitLShift(1, 22),
}
ImGuiTabBarFlags.FittingPolicyMask_ = bitOr(ImGuiTabBarFlags.FittingPolicyMixed, ImGuiTabBarFlags.FittingPolicyShrink, ImGuiTabBarFlags.FittingPolicyScroll)
ImGuiTabBarFlags.FittingPolicyDefault_ = ImGuiTabBarFlags.FittingPolicyMixed
ImGuiTabBarFlags.FittingPolicyResizeDown = ImGuiTabBarFlags.FittingPolicyShrink
--- @enum ImGuiTabItemFlags
ImGuiTabItemFlags = {
None = 0,
UnsavedDocument = bitLShift(1, 0),
SetSelected = bitLShift(1, 1),
NoCloseWithMiddleMouseButton = bitLShift(1, 2),
NoPushId = bitLShift(1, 3),
NoTooltip = bitLShift(1, 4),
NoReorder = bitLShift(1, 5),
Leading = bitLShift(1, 6),
Trailing = bitLShift(1, 7),
NoAssumedClosure = bitLShift(1, 8),
-- Private
NoCloseButton = bitLShift(1, 20),
Button = bitLShift(1, 21),
Invisible = bitLShift(1, 22),
Unsorted = bitLShift(1, 23),
}
ImGuiTabItemFlags.SectionMask_ = bitOr(ImGuiTabItemFlags.Leading, ImGuiTabItemFlags.Trailing)
-- Storage for one active tab item. In this port the label is stored in `Name` (C++ uses NameOffset into TabBar.TabsNames)
--- @class ImGuiTabItem
--- @return ImGuiTabItem
function ImGuiTabItem()
return {
ID = 0, Flags = 0, Window = nil,
LastFrameVisible = -1, LastFrameSelected = -1,
Offset = 0.0, Width = 0.0, ContentWidth = 0.0, RequestedWidth = -1.0,
NameOffset = -1, Name = nil,
BeginOrder = -1, -- 0-based
IndexDuringLayout = -1, -- 0-based
WantClose = false,
}
end
-- Storage for a tab bar. Tabs: ImVector of ImGuiTabItem tables (references; "pointers" stay valid across reorders)
--- @class ImGuiTabBar
--- @return ImGuiTabBar
function ImGuiTabBar()
return {
Window = nil, Tabs = ImVector(), Flags = 0, ID = 0,
SelectedTabId = 0, NextSelectedTabId = 0, NextScrollToTabId = 0, VisibleTabId = 0,
CurrFrameVisible = -1, PrevFrameVisible = -1,
BarRect = ImRect(), BarRectPrevWidth = 0.0,
CurrTabsContentsHeight = 0.0, PrevTabsContentsHeight = 0.0,
WidthAllTabs = 0.0, WidthAllTabsIdeal = 0.0,
ScrollingAnim = 0.0, ScrollingTarget = 0.0, ScrollingTargetDistToVisibility = 0.0, ScrollingSpeed = 0.0,
ScrollingRectMinX = 0.0, ScrollingRectMaxX = 0.0, SeparatorMinX = 0.0, SeparatorMaxX = 0.0,
ReorderRequestTabId = 0, ReorderRequestOffset = 0, BeginCount = 0,
WantLayout = false, VisibleTabWasSubmitted = false, TabsAddedNew = false, ScrollButtonEnabled = false,
TabsActiveCount = 0,
LastTabItemIdx = -1, -- 0-based
ItemSpacingY = 0.0, FramePadding = ImVec2(), BackupCursorPos = ImVec2(),
}
end
--- @class ImGuiShrinkWidthItem
function ImGuiShrinkWidthItem() return { Index = 0, Width = 0.0, InitialWidth = 0.0 } end
--- @param shared_font_atlas? ImFontAtlas --- @param shared_font_atlas? ImFontAtlas
--- @return ImGuiContext --- @return ImGuiContext
--- @nodiscard --- @nodiscard
@@ -1881,6 +2014,15 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
ComboPreviewData = ImGuiComboPreviewData(), ComboPreviewData = ImGuiComboPreviewData(),
CurrentTabBar = nil,
TabBars = ImPool(ImGuiTabBar), -- ImPool<ImGuiTabBar>
-- Docking
DockContext = ImGuiDockContext(),
DockNodeWindowMenuHandler = nil,
DebugHoveredDockNode = nil,
CurrentTabBarStack = ImVector(), -- holds ImGuiTabBar references directly (no ImGuiPtrOrIndex needed in Lua)
ShrinkWidthBuffer = ImVector(), -- ImGuiShrinkWidthItem
WindowResizeBorderExpectedRect = ImRect(), WindowResizeBorderExpectedRect = ImRect(),
WindowResizeRelativeMode = false, WindowResizeRelativeMode = false,
@@ -1914,16 +2056,61 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
UserTextures = ImVector(), UserTextures = ImVector(),
-- Settings -- Settings
SettingsLoaded = false,
SettingsDirtyTimer = 0.0,
SettingsIniData = "", -- In memory .ini settings (string)
SettingsHandlers = ImVector(), -- List of .ini settings handlers
SettingsWindows = ImVector(), SettingsWindows = ImVector(),
SettingsTables = ImVector(),
-- Hooks
Hooks = ImVector(),
HookIdNext = 0,
-- Drag and Drop -- Drag and Drop
DragDropActive = false, DragDropActive = false,
DragDropWithinSource = false, DragDropWithinSource = false,
DragDropWithinTarget = false, DragDropWithinTarget = false,
DragDropSourceFlags = 0, DragDropSourceFlags = 0,
DragDropSourceFrameCount = -1,
DragDropMouseButton = -1,
DragDropPayload = ImGuiPayload(),
DragDropTargetRect = ImRect(),
DragDropTargetClipRect = ImRect(),
DragDropTargetId = 0,
DragDropTargetFullViewport = 0,
DragDropAcceptFlagsCurr = 0, DragDropAcceptFlagsPrev = 0,
DragDropAcceptIdCurrRectSurface = 0.0,
DragDropAcceptFrameCount = -1,
DragDropHoldJustPressedId = 0,
DragDropAcceptIdPrev = 0, DragDropAcceptIdCurr = 0, DragDropAcceptIdPrev = 0, DragDropAcceptIdCurr = 0,
-- Clipper
ClipperTempDataStacked = 0,
ClipperTempData = ImVector(),
-- Logging
LogEnabled = false,
LogLineFirstItem = false,
LogFlags = 0,
LogWindow = nil,
LogFile = nil,
LogBuffer = ImGuiTextBuffer(),
LogNextPrefix = nil, LogNextSuffix = nil,
LogLinePosY = FLT_MAX,
LogDepthRef = 0,
LogDepthToExpand = 2, LogDepthToExpandDefault = 2,
-- Error handling
ErrorCallback = nil, ErrorCallbackUserData = nil,
ErrorTooltipLockedPos = ImVec2(),
ErrorFirst = true,
ErrorCountCurrentFrame = 0,
StackSizesInNewFrame = nil,
ContextName = "",
DebugBreakInWindow = 0,
LocalizationTable = {}, LocalizationTable = {},
DebugLogFlags = bitOr(ImGuiDebugLogFlags.EventError, ImGuiDebugLogFlags.OutputToTTY), DebugLogFlags = bitOr(ImGuiDebugLogFlags.EventError, ImGuiDebugLogFlags.OutputToTTY),
@@ -1942,6 +2129,16 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
WantCaptureKeyboardNextFrame = -1, WantCaptureKeyboardNextFrame = -1,
WantTextInputNextFrame = -1, WantTextInputNextFrame = -1,
TempBuffer = ImVector(), TempBuffer = ImVector(),
-- Tables (imgui_tables.lua)
CurrentTable = nil,
DebugBreakInTable = 0,
TablesTempDataStacked = 0,
TablesTempData = ImVector(),
Tables = ImPool(), -- ImPool<ImGuiTable>, 0-based indices (imgui_tables.lua)
TablesLastTimeActive = ImVector(), -- [table_idx + 1] = float
DrawChannelsTempMergeBuffer = ImVector(),
SettingsTables = ImVector(), -- ImChunkStream<ImGuiTableSettings>: plain vector, SettingsOffset = 1-based index
} }
for i = 0, 59 do this.FramerateSecPerFrame[i] = 0 end for i = 0, 59 do this.FramerateSecPerFrame[i] = 0 end
@@ -1989,6 +2186,12 @@ function ImGuiWindowSettings()
WantApply = false, WantApply = false,
WantDelete = false, WantDelete = false,
Name = "", Name = "",
ViewportId = 0,
ViewportPos = ImVec2(0, 0),
DockId = 0,
DockOrder = -1,
ClassId = 0,
LastUsedDate = 0,
}, IMGUI_WINDOW_SETTINGS) }, IMGUI_WINDOW_SETTINGS)
end end
@@ -2049,6 +2252,8 @@ end
--- @nodiscard --- @nodiscard
local function ImGuiWindowTempData() local function ImGuiWindowTempData()
return { return {
DockTabItemStatusFlags = 0,
DockTabItemRect = ImRect(),
CursorPos = ImVec2(), CursorPos = ImVec2(),
CursorPosPrevLine = ImVec2(), CursorPosPrevLine = ImVec2(),
CursorStartPos = ImVec2(), CursorStartPos = ImVec2(),
@@ -2082,6 +2287,8 @@ local function ImGuiWindowTempData()
TreeRecordsClippedNodesY2Mask = nil, TreeRecordsClippedNodesY2Mask = nil,
ChildWindows = ImVector(), ChildWindows = ImVector(),
CurrentTableIdx = -1, -- 0-based index into g.Tables, -1 if none
CurrentColumns = nil, -- ImGuiOldColumns (legacy Columns API)
StateStorage = nil, StateStorage = nil,
LayoutType = nil LayoutType = nil
@@ -2274,6 +2481,7 @@ function ImGuiWindow(ctx, name)
LastTimeActive = -1.0, LastTimeActive = -1.0,
StateStorage = {}, StateStorage = {},
ColumnsStorage = ImVector(), -- ImVector<ImGuiOldColumns>
WriteAccessed = false, WriteAccessed = false,
@@ -2297,6 +2505,21 @@ function ImGuiWindow(ctx, name)
this.ID = ImHashStr(name) this.ID = ImHashStr(name)
this.IDStack:push_back(this.ID) this.IDStack:push_back(this.ID)
this.MoveId = this:GetID("#MOVE") this.MoveId = this:GetID("#MOVE")
this.TabId = this:GetID("#TAB")
-- Docking
this.ChildId = this.ChildId or 0
this.SetWindowDockAllowFlags = 0
this.ParentWindowForFocusRoute = nil
this.DockIsActive = false
this.DockNodeIsVisible = false
this.DockTabIsVisible = false
this.DockTabWantClose = false
this.DockOrder = -1
this.DockStyle = { Colors = {} }
for i = 0, 8 do this.DockStyle.Colors[i] = 0 end
this.DockNode = nil
this.DockNodeAsHost = nil
this.DockId = 0
return this return this
end end
+1164
View File
File diff suppressed because it is too large Load Diff
+187
View File
@@ -301,6 +301,28 @@ function ImGui.BulletText(fmt, ...)
ImGui.RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0), text, 1, text_end, false) ImGui.RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0), text, 1, text_end, false)
end end
-- va_list variants: identical to the varargs versions in Lua
ImGui.TextColoredV = ImGui.TextColored
ImGui.TextDisabledV = ImGui.TextDisabled
ImGui.TextWrappedV = ImGui.TextWrapped
ImGui.TextAlignedV = ImGui.TextAligned
ImGui.LabelTextV = ImGui.LabelText
ImGui.BulletTextV = ImGui.BulletText
-- Value(prefix, b) / Value(prefix, int) / Value(prefix, float, float_format)
-- Lua can't tell int from float: integers without float_format print as %d, otherwise %.3f
function ImGui.Value(prefix, v, float_format)
if type(v) == "boolean" then
ImGui.Text("%s: %s", prefix, v and "true" or "false")
elseif float_format then
ImGui.Text("%s: " .. float_format, prefix, v)
elseif math.floor(v) == v then
ImGui.Text("%s: %d", prefix, v)
else
ImGui.Text("%s: %.3f", prefix, v)
end
end
---------------------------------------------------------------- ----------------------------------------------------------------
-- [SECTION] MAIN: BUTTONS, SCROLLBARS, ... -- [SECTION] MAIN: BUTTONS, SCROLLBARS, ...
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -1198,6 +1220,7 @@ function ImGui.CheckboxFlags(label, flags, flags_value)
return pressed, flags return pressed, flags
end end
ImGui.CheckboxFlagsT = ImGui.CheckboxFlags
--- @param label string --- @param label string
--- @param active bool --- @param active bool
@@ -2625,6 +2648,26 @@ function ImGui.InputFloat3(label, v, format, flags)
return ImGui.InputScalarN(label, ImGuiDataType.Float, v, 3, nil, nil, format, flags) return ImGui.InputScalarN(label, ImGuiDataType.Float, v, 3, nil, nil, format, flags)
end end
function ImGui.InputFloat2(label, v, format, flags)
return ImGui.InputScalarN(label, ImGuiDataType.Float, v, 2, nil, nil, format or "%.3f", flags)
end
function ImGui.InputFloat4(label, v, format, flags)
return ImGui.InputScalarN(label, ImGuiDataType.Float, v, 4, nil, nil, format or "%.3f", flags)
end
function ImGui.InputInt2(label, v, flags)
return ImGui.InputScalarN(label, ImGuiDataType.S32, v, 2, nil, nil, "%d", flags)
end
function ImGui.InputInt3(label, v, flags)
return ImGui.InputScalarN(label, ImGuiDataType.S32, v, 3, nil, nil, "%d", flags)
end
function ImGui.InputInt4(label, v, flags)
return ImGui.InputScalarN(label, ImGuiDataType.S32, v, 4, nil, nil, "%d", flags)
end
--- @param label string --- @param label string
--- @param v int --- @param v int
--- @param step? int --- @param step? int
@@ -3041,6 +3084,112 @@ function ImGui.DragInt(label, v, v_speed, v_min, v_max, format, flags)
return ImGui.DragScalar(label, ImGuiDataType.S32, v, v_speed, v_min, v_max, format, flags) return ImGui.DragScalar(label, ImGuiDataType.S32, v, v_speed, v_min, v_max, format, flags)
end end
-- `v` is mutated in place (1-based), returns value_changed
function ImGui.DragScalarN(label, data_type, v, components, v_speed, v_min, v_max, format, flags)
if v_speed == nil then v_speed = 1.0 end
if flags == nil then flags = 0 end
local window = ImGui.GetCurrentWindow()
if window.SkipItems then
return false
end
local g = GImGui
local value_changed = false
ImGui.BeginGroup()
ImGui.PushID(label)
ImGui.PushMultiItemsWidths(components, ImGui.CalcItemWidth())
for i = 1, components do
ImGui.PushID(i - 1)
if i > 1 then
ImGui.SameLine(0, g.Style.ItemInnerSpacing.x)
end
if bit32.band(flags, ImGuiSliderFlags.ColorMarkers) ~= 0 then
ImGui.SetNextItemColorMarker(GDefaultRgbaColorMarkers[i])
end
local changed
v[i], changed = ImGui.DragScalar("", data_type, v[i], v_speed, v_min, v_max, format, flags)
value_changed = value_changed or changed
ImGui.PopID()
ImGui.PopItemWidth()
end
ImGui.PopID()
local label_end = ImGui.FindRenderedTextEnd(label)
if label_end > 1 then
ImGui.SameLine(0, g.Style.ItemInnerSpacing.x)
ImGui.TextEx(label, label_end)
end
ImGui.EndGroup()
return value_changed
end
local function DragFloatN(n)
return function(label, v, v_speed, v_min, v_max, format, flags)
return ImGui.DragScalarN(label, ImGuiDataType.Float, v, n, v_speed or 1.0, v_min or 0.0, v_max or 0.0, format or "%.3f", flags or 0)
end
end
local function DragIntN(n)
return function(label, v, v_speed, v_min, v_max, format, flags)
return ImGui.DragScalarN(label, ImGuiDataType.S32, v, n, v_speed or 1.0, v_min or 0, v_max or 0, format or "%d", flags or 0)
end
end
ImGui.DragFloat2 = DragFloatN(2)
ImGui.DragFloat3 = DragFloatN(3)
ImGui.DragFloat4 = DragFloatN(4)
ImGui.DragInt2 = DragIntN(2)
ImGui.DragInt3 = DragIntN(3)
ImGui.DragInt4 = DragIntN(4)
-- NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
--- @return float v_current_min
--- @return float v_current_max
--- @return bool value_changed
local function DragRange2(data_type, label, v_current_min, v_current_max, v_speed, v_min, v_max, format, format_max, flags)
local window = ImGui.GetCurrentWindow()
if window.SkipItems then
return v_current_min, v_current_max, false
end
local g = GImGui
local lo = (data_type == ImGuiDataType.Float) and -FLT_MAX or INT_MIN
local hi = (data_type == ImGuiDataType.Float) and FLT_MAX or INT_MAX
ImGui.PushID(label)
ImGui.BeginGroup()
ImGui.PushMultiItemsWidths(2, ImGui.CalcItemWidth())
local min_min = (v_min >= v_max) and lo or v_min
local min_max = (v_min >= v_max) and v_current_max or ImMin(v_max, v_current_max)
local min_flags = bit32.bor(flags, (min_min == min_max) and ImGuiSliderFlags.ReadOnly or 0)
local value_changed, changed
v_current_min, value_changed = ImGui.DragScalar("##min", data_type, v_current_min, v_speed, min_min, min_max, format, min_flags)
ImGui.PopItemWidth()
ImGui.SameLine(0, g.Style.ItemInnerSpacing.x)
local max_min = (v_min >= v_max) and v_current_min or ImMax(v_min, v_current_min)
local max_max = (v_min >= v_max) and hi or v_max
local max_flags = bit32.bor(flags, (max_min == max_max) and ImGuiSliderFlags.ReadOnly or 0)
v_current_max, changed = ImGui.DragScalar("##max", data_type, v_current_max, v_speed, max_min, max_max, format_max or format, max_flags)
value_changed = value_changed or changed
ImGui.PopItemWidth()
ImGui.SameLine(0, g.Style.ItemInnerSpacing.x)
ImGui.TextEx(label, ImGui.FindRenderedTextEnd(label))
ImGui.EndGroup()
ImGui.PopID()
return v_current_min, v_current_max, value_changed
end
function ImGui.DragFloatRange2(label, v_current_min, v_current_max, v_speed, v_min, v_max, format, format_max, flags)
return DragRange2(ImGuiDataType.Float, label, v_current_min, v_current_max, v_speed or 1.0, v_min or 0.0, v_max or 0.0, format or "%.3f", format_max, flags or 0)
end
function ImGui.DragIntRange2(label, v_current_min, v_current_max, v_speed, v_min, v_max, format, format_max, flags)
return DragRange2(ImGuiDataType.S32, label, v_current_min, v_current_max, v_speed or 1.0, v_min or 0, v_max or 0, format or "%d", format_max, flags or 0)
end
---------------------------------------------------------------- ----------------------------------------------------------------
-- [SECTION] SLIDERXXX -- [SECTION] SLIDERXXX
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -3651,6 +3800,18 @@ function ImGui.SliderInt(label, v, v_min, v_max, format, flags)
return ImGui.SliderScalar(label, ImGuiDataType.S32, v, v_min, v_max, format, flags) return ImGui.SliderScalar(label, ImGuiDataType.S32, v, v_min, v_max, format, flags)
end end
function ImGui.SliderInt2(label, v, v_min, v_max, format, flags)
return ImGui.SliderScalarN(label, ImGuiDataType.S32, v, 2, v_min, v_max, format or "%d", flags)
end
function ImGui.SliderInt3(label, v, v_min, v_max, format, flags)
return ImGui.SliderScalarN(label, ImGuiDataType.S32, v, 3, v_min, v_max, format or "%d", flags)
end
function ImGui.SliderInt4(label, v, v_min, v_max, format, flags)
return ImGui.SliderScalarN(label, ImGuiDataType.S32, v, 4, v_min, v_max, format or "%d", flags)
end
--- @param label string --- @param label string
--- @param size ImVec2 --- @param size ImVec2
--- @param data_type ImGuiDataType --- @param data_type ImGuiDataType
@@ -3764,6 +3925,11 @@ function ImGui.InputText(label, buf, buf_size, flags, callback, user_data)
return ImGui.InputTextEx(label, nil, buf, buf_size, ImVec2(0, 0), flags, callback, user_data) return ImGui.InputTextEx(label, nil, buf, buf_size, ImVec2(0, 0), flags, callback, user_data)
end end
--- @param size? ImVec2
function ImGui.InputTextMultiline(label, buf, buf_size, size, flags, callback, user_data)
return ImGui.InputTextEx(label, nil, buf, buf_size, size or ImVec2(0, 0), bit32.bor(flags or 0, ImGuiInputTextFlags.Multiline), callback, user_data)
end
ImStb = {} ImStb = {}
--- @module "imstb_textedit" --- @module "imstb_textedit"
@@ -5811,6 +5977,27 @@ function ImGui.ColorEdit3(label, col, flags)
return ImGui.ColorEdit4(label, col, bit32.bor(flags, ImGuiColorEditFlags.NoAlpha)) return ImGui.ColorEdit4(label, col, bit32.bor(flags, ImGuiColorEditFlags.NoAlpha))
end end
-- `col` mutated in place, returns value_changed
function ImGui.ColorPicker3(label, col, flags)
local col4 = { col[1], col[2], col[3], 1.0 }
if not ImGui.ColorPicker4(label, col4, bit32.bor(flags or 0, ImGuiColorEditFlags.NoAlpha)) then
return false
end
col[1] = col4[1]; col[2] = col4[2]; col[3] = col4[3]
return true
end
function ImGui.SetColorEditOptions(flags)
local g = GImGui
local def = ImGuiColorEditFlags.DefaultOptions_
for _, mask in ipairs({ ImGuiColorEditFlags.DisplayMask_, ImGuiColorEditFlags.DataTypeMask_, ImGuiColorEditFlags.PickerMask_, ImGuiColorEditFlags.InputMask_ }) do
if bit32.band(flags, mask) == 0 then
flags = bit32.bor(flags, bit32.band(def, mask))
end
end
g.IO.ConfigColorEditFlags = flags
end
-- Helper for ColorPicker4() -- Helper for ColorPicker4()
--- @param draw_list ImDrawList --- @param draw_list ImDrawList
--- @param pos ImVec2 --- @param pos ImVec2
+9 -3
View File
@@ -13,7 +13,7 @@ for dp, _, fn in os.walk(SRC):
if f.endswith(".lua"): if f.endswith(".lua"):
FILES[f] = open(os.path.join(dp, f)).read() FILES[f] = open(os.path.join(dp, f)).read()
ENTRY = ["imgui.lua", "imgui_impl_roblox.lua", "imgui_demo.lua"] # run order ENTRY = ["imgui.lua", "imgui_impl_roblox.lua"] + sorted(f for f in FILES if f.startswith("imgui_demo")) # run order
names = set() names = set()
for src in FILES.values(): for src in FILES.values():
@@ -27,8 +27,10 @@ out = ["--!native", "--!optimize 2", "-- GENERATED by tools/bundle.py from imgui
" if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end", " if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end",
" return r", " return r",
"end", ""] "end", ""]
linemap = [] # (first bundle line, file) for error -> source mapping (tools/test/run.py)
for name, src in sorted(FILES.items()): for name, src in sorted(FILES.items()):
out.append("__FILES[%r] = function()" % name) out.append("__FILES[%r] = function()" % name)
linemap.append((sum(x.count("\n") + 1 for x in out) + 1, name))
out.append(src.rstrip("\n")) out.append(src.rstrip("\n"))
out.append("end") out.append("end")
out.append("") out.append("")
@@ -40,6 +42,10 @@ for n in sorted(names):
out.append(" %s = %s," % (n, n)) out.append(" %s = %s," % (n, n))
out.append("}") out.append("}")
os.makedirs(os.path.join(ROOT, "build"), exist_ok=True) import sys
open(os.path.join(ROOT, "build", "ImGui.luau"), "w").write("\n".join(out) + "\n") OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "build")
os.makedirs(OUT, exist_ok=True)
open(os.path.join(OUT, "ImGui.luau"), "w").write("\n".join(out) + "\n")
import json
json.dump(linemap, open(os.path.join(OUT, "ImGui.linemap.json"), "w"))
print("wrote build/ImGui.luau (%d files, %d exports)" % (len(FILES), len(names))) print("wrote build/ImGui.luau (%d files, %d exports)" % (len(FILES), len(names)))
+54
View File
@@ -0,0 +1,54 @@
# Porting rules (Dear ImGui docking branch -> this Lua/Luau port)
Upstream reference: `tools/test/upstream/*.cpp|h` (ocornut/imgui `docking` branch, 1.93 WIP). Port semantics 1:1 from
there, but keep this port's existing data structures and conventions. Read the surrounding existing Lua code first.
## Runtime
- Target is **Luau (Roblox)** only. All files are concatenated into one ModuleScript by `tools/bundle.py`; every
file shares one global namespace. Globals defined by one file are visible to the others at call time.
- Each `imgui/*.lua` file is a function body in the bundle. New files must be loaded with `IM_INCLUDE"file.lua"`
from an existing file (files named `imgui_demo*.lua` are loaded automatically, after `imgui.lua`).
- No `goto`, no `::labels::`, no `setfenv/getfenv`, no LuaJIT `bit`/`ffi`. Use `bit32`. For `continue` use
`repeat ... until true` with `break`, and an explicit flag when the loop itself must break out of the wrapper.
- Luau has a limit of 200 locals per function and 255 upvalues; split huge functions (like demo sections) into
several local/global functions.
## Conventions (follow what the port already does)
- `ImVector`: 1-based `.Data`, `.Size`, methods `push_back/pop_back/resize/back/iter/erase/...` (imgui_h.lua).
Indices that C++ stores as 0-based *values* (e.g. column index, tab order) stay 0-based unless the surrounding
port code already shifted them; document `-- 0-based` / `-- 1-based` next to anything ambiguous.
- `ImVec2(x, y)` stores `[1],[2]` with `.x/.y` aliases (metatable). Arithmetic operators exist. Prefer
`ImVec2_Copy(dst, src)` / `ImVec2_CopyV(dst, x, y)` when C++ assigns into an existing struct field.
`ImVec4`, `ImRect` (`.Min/.Max`) likewise.
- Structs: constructor function `ImGuiFoo()` returning a table, methods on `MT.ImGuiFoo` with
`MT.ImGuiFoo.__index = MT.ImGuiFoo`. `local MT = ImGui.GetMetatables()`.
- Flags/enums: tables like `ImGuiTableFlags.Resizable`; combine with `bit32.bor`, test with `bit32.band(...) ~= 0`.
- Context: `local g = GImGui` (each file has its own local `GImGui` kept in sync; follow how the file you are in
does it). New fields on `ImGuiContext`/`ImGuiWindow`: add them to the constructor in imgui_internal.lua.
- Strings: Lua strings + 1-based byte positions; `text_end` is exclusive. Editable text buffers are `char[]`
(byte tables), like `ImGui.InputText(label, buf, buf_size)`.
- **Output parameters** (C++ `bool* p_open`, `float* v`, `int* current_item`...) become return values:
- bool-like widgets: `return pressed, new_v` (Checkbox, RadioButton, Selectable, MenuItem, CheckboxFlags)
- scalar widgets: `return new_v, changed` (Slider*/Drag*/Input* scalar, SliderAngle, Combo -> `new_idx, changed`)
- multi-component (`float v[3]`, ImVec4 colors, `char[]` buffers): table mutated in place, return `changed`
- `Begin(name, p_open)` style: `return open, visible` / follow existing `ImGui.Begin`.
When an existing similar function already exists, copy its convention exactly.
- `static` locals in C++ become file-level upvalue tables / fields (demo uses a `static` table per section).
- `IM_ASSERT(cond, msg)` exists. `IM_COL32(r,g,b,a)`, `ImMax/ImMin/ImClamp/ImLerp/ImFloor/ImTrunc` etc. exist.
- Keep function names identical to upstream (`ImGui.TableNextRow`, `ImGui.TabBarLayout`, ...). Internal helpers
that are `static` in C++ become `local function` in the file.
- Roblox can't do: OS clipboard (internal only), files/URLs (VFS `ImStd.ImFileLoadToMemory` only), multi-viewports,
gamepad. Port the code paths anyway where cheap; demo shows "(not supported on Roblox)" text instead of
widgets that cannot work.
## Testing
- `python3 tools/test/run.py` — runs main.client.luau 400 frames with scripted random input (mouse, clicks,
keys, typing). Errors print with `imgui/<file>.lua:<line>` mapped stack traces.
- `--open` forces every CollapsingHeader/TreeNode open (exercises the whole demo). `--big` makes the demo
window 900x1000. `--png out.png` writes a screenshot (Read the PNG to look at it). `--check` asserts the
incremental renderer is pixel identical to a full redraw. `--main file.luau` runs your own test LocalScript
(copy main.client.luau as a template: `local G = require(ReplicatedStorage:WaitForChild("ImGui"))`, all
library globals are fields of `G`).
- `python3 tools/test/missing.py [tables|widgets|imgui.cpp]` lists upstream functions not yet ported.
- Several people edit the repo concurrently. Keep every file syntactically valid after each edit. If a run fails
because of an error in a file you don't own, wait a minute and retry instead of editing it.
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""Lists upstream ImGui:: functions (tools/test/upstream/*.cpp, docking branch) that the Lua port doesn't define.
python3 tools/test/missing.py # summary per upstream file
python3 tools/test/missing.py tables # names missing from imgui_tables.cpp
"""
import os, re, sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(HERE))
port = set()
for f in os.listdir(os.path.join(ROOT, "imgui")):
if f.endswith(".lua"):
port.update(re.findall(r"function ImGui[.:](\w+)", open(os.path.join(ROOT, "imgui", f)).read()))
filt = sys.argv[1] if len(sys.argv) > 1 else None
for f in sorted(os.listdir(os.path.join(HERE, "upstream"))):
if not f.endswith(".cpp") or f == "imgui_demo.cpp": continue
if filt and filt not in f: continue
src = open(os.path.join(HERE, "upstream", f)).read()
names = sorted(set(re.findall(r"^[\w:<>*& ]+?\bImGui::(\w+)\(", src, re.M)))
miss = [n for n in names if n not in port]
print("%-20s %4d/%4d ported, missing %d" % (f, len(names) - len(miss), len(names), len(miss)))
if filt: print(" " + "\n ".join(miss))
+245
View File
@@ -0,0 +1,245 @@
-- Minimal Roblox mock for running the imgui Roblox backend + the real CanvasDraw module in the Luau CLI.
local realG = {} -- (no getfenv: it would mark the global env unsafe and deoptimize everything)
FAKE_TIME = 0
local SCREEN_W, SCREEN_H = 1920, 1080
local NFRAMES = tonumber(ARG_FRAMES) or 400
local FORCE = ARG_FORCE == true
local DUMP_FRAMES = { [60] = true, [200] = true, [NFRAMES] = true }
local function Signal()
local s = { conns = {} }
function s:Connect(fn)
local c = { fn = fn, Connected = true }
function c:Disconnect() c.Connected = false end
table.insert(s.conns, c)
return c
end
function s:Fire(...) for _, c in ipairs(s.conns) do if c.Connected then c.fn(...) end end end
return s
end
local V2 = {}
V2.__index = V2
local function v2(x, y) return setmetatable({ X = x or 0, Y = y or 0 }, V2) end
V2.__add = function(a, b) return v2(a.X + b.X, a.Y + b.Y) end
V2.__sub = function(a, b) return v2(a.X - b.X, a.Y - b.Y) end
V2.__mul = function(a, b) if type(b) == "number" then return v2(a.X * b, a.Y * b) end return v2(a.X * b.X, a.Y * b.Y) end
V2.__div = function(a, b) if type(b) == "number" then return v2(a.X / b, a.Y / b) end return v2(a.X / b.X, a.Y / b.Y) end
V2.__eq = function(a, b) return a.X == b.X and a.Y == b.Y end
local Vector2 = { new = v2, zero = v2(0, 0), one = v2(1, 1) }
-- Enums: KeyCode values as in Roblox; unknown KeyCode names error (catches typos in the backend)
local KEYCODES = {
Unknown = 0, Backspace = 8, Tab = 9, Return = 13, Escape = 27, Space = 32, Quote = 39, Comma = 44, Minus = 45, Period = 46, Slash = 47,
Semicolon = 59, Equals = 61, LeftBracket = 91, BackSlash = 92, RightBracket = 93, Backquote = 96, Delete = 127,
KeypadPeriod = 266, KeypadDivide = 267, KeypadMultiply = 268, KeypadMinus = 269, KeypadPlus = 270, KeypadEnter = 271, KeypadEquals = 272,
Up = 273, Down = 274, Right = 275, Left = 276, Insert = 277, Home = 278, End = 279, PageUp = 280, PageDown = 281,
CapsLock = 301, RightShift = 303, LeftShift = 304, RightControl = 305, LeftControl = 306, RightAlt = 307, LeftAlt = 308,
LeftSuper = 311, RightSuper = 312,
}
local digits = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" }
for i = 0, 9 do KEYCODES[digits[i + 1]] = 48 + i; KEYCODES["Keypad" .. digits[i + 1]] = 256 + i end
for i = 0, 25 do KEYCODES[string.char(65 + i)] = 97 + i end
for i = 1, 15 do KEYCODES["F" .. i] = 281 + i end
local Enum = setmetatable({}, { __index = function(t, enum_name)
local e = setmetatable({}, { __index = function(e, item)
local value = 0
if enum_name == "KeyCode" then value = KEYCODES[item]; assert(value, "invalid KeyCode " .. item) end
local it = { Name = item, Value = value, EnumType = enum_name }
rawset(e, item, it)
return it
end })
rawset(t, enum_name, e)
return e
end })
-- Instances
local InstMethods = {}
local InstMT = {}
InstMT.__index = function(t, k)
if k == "Parent" then return rawget(t, "_parent") end
local m = InstMethods[k]; if m then return m end
for _, c in ipairs(rawget(t, "_children")) do if c.Name == k then return c end end
return nil
end
InstMT.__newindex = function(t, k, v)
if k == "Parent" then
local old = rawget(t, "_parent")
if old then local ch = rawget(old, "_children"); for i, c in ipairs(ch) do if c == t then table.remove(ch, i) break end end end
rawset(t, "_parent", v)
if v then table.insert(rawget(v, "_children"), t) end
else
rawset(t, k, v)
end
end
local function newInst(class, name, parent)
local o = setmetatable({ ClassName = class, Name = name or class, _children = {} }, InstMT)
if class == "ScreenGui" or class == "Frame" or class == "ImageLabel" then o.AbsoluteSize = v2(SCREEN_W, SCREEN_H) end
o.Visible = true
if class == "BindableEvent" then local sig = Signal(); rawset(o, "Event", sig); rawset(o, "Fire", function(_, ...) sig:Fire(...) end) end
if parent then o.Parent = parent end
return o
end
function InstMethods:FindFirstChild(name, recursive)
for _, c in ipairs(rawget(self, "_children")) do if c.Name == name then return c end end
if recursive then for _, c in ipairs(rawget(self, "_children")) do local r = c:FindFirstChild(name, true); if r then return r end end end
return nil
end
function InstMethods:WaitForChild(name) return assert(self:FindFirstChild(name), "WaitForChild would hang: " .. name) end
function InstMethods:FindFirstChildOfClass(class) for _, c in ipairs(rawget(self, "_children")) do if c.ClassName == class then return c end end end
function InstMethods:GetChildren() return table.clone(rawget(self, "_children")) end
function InstMethods:IsA(class)
if class == "GuiObject" then return self.ClassName == "Frame" or self.ClassName == "ImageLabel" end
return self.ClassName == class
end
function InstMethods:Destroy() self.Parent = nil end
-- Services
local mouse = v2(0, 0)
local keys_down = {}
local UIS = {
InputBegan = Signal(), InputEnded = Signal(), InputChanged = Signal(), WindowFocused = Signal(), WindowFocusReleased = Signal(),
}
function UIS:GetMouseLocation() return mouse end
function UIS:IsKeyDown(kc) return keys_down[kc] == true end
local RunService = { Heartbeat = Signal(), Stepped = Signal(), PreAnimation = Signal(), RenderStepped = Signal() }
local last_pixels -- last buffer written to an EditableImage
local AssetService = {}
function AssetService:CreateEditableImage(opts)
local img = { Size = opts.Size, Pixels = buffer.create(opts.Size.X * opts.Size.Y * 4) }
UPLOADS = 0
function img:WritePixelsBuffer(origin, size, buf) buffer.copy(img.Pixels, 0, buf); _G_UPLOADS = (_G_UPLOADS or 0) + 1 end
function img:Destroy() end
return img
end
local ReplicatedStorage = newInst("ReplicatedStorage")
local PlayerGui = newInst("PlayerGui")
local LocalPlayer = newInst("Player"); PlayerGui.Parent = LocalPlayer
local services = {
UserInputService = UIS, RunService = RunService, AssetService = AssetService, ReplicatedStorage = ReplicatedStorage,
Players = { LocalPlayer = LocalPlayer }, HttpService = {}, GuiService = { GetGuiInset = function() return v2(0, 0), v2(0, 0) end },
}
local game = {}
function game:GetService(name) services[name] = services[name] or {}; return services[name] end
local fakeG = {} -- Roblox's shared _G
local cache = {}
local ROBLOX
local function makeEnv(inst) return setmetatable({ script = inst }, { __index = ROBLOX }) end
local GLOBAL_NAMES = { "game", "workspace", "Instance", "Enum", "Vector2", "Vector3", "UDim2", "Color3", "Content", "warn", "require", "script", "os" }
-- Load like Roblox does but WITHOUT setfenv (which would disable Luau optimizations): Roblox globals become chunk locals.
local function loadAsScript(src_name, inst)
local fn = SOURCE_FNS[src_name]
local vals = {}
for i, n in ipairs(GLOBAL_NAMES) do vals[i] = (n == "script") and inst or ROBLOX[n] end
return function() return fn(table.unpack(vals, 1, #GLOBAL_NAMES)) end
end
local function rbxrequire(inst)
assert(type(inst) == "table" and inst.ClassName == "ModuleScript", "require() expects a ModuleScript")
if cache[inst] then return cache[inst].v end
local r = table.pack(loadAsScript(rawget(inst, "_src"), inst)())
assert(r.n == 1, "Module code did not return exactly one value")
cache[inst] = { v = r[1] }
return r[1]
end
ROBLOX = setmetatable({
game = game, workspace = { CurrentCamera = { ViewportSize = v2(SCREEN_W, SCREEN_H) } }, require = rbxrequire, _G = fakeG,
Instance = { new = function(class) return newInst(class) end },
Enum = Enum, Vector2 = Vector2,
Vector3 = { new = function(x, y, z) return { X = x, Y = y, Z = z } end },
UDim2 = { new = function(...) return { ... } end, fromScale = function(...) return { ... } end, fromOffset = function(x, y) return { X = x, Y = y } end },
Color3 = { new = function(r, g, b) return { R = r or 0, G = g or 0, B = b or 0 } end },
Content = { fromObject = function(o) return { Object = o } end },
warn = function(...) print("WARN", ...) end,
os = { clock = function() return FAKE_TIME end, time = os.time },
}, { __index = realG })
local mod = newInst("ModuleScript", "ImGui", ReplicatedStorage); rawset(mod, "_src", "ImGui.luau")
-- Run main.lua as a LocalScript
local main = loadAsScript("main.client.luau", newInst("LocalScript", "main"))
main()
local G = cache[mod].v
local render_time = 0
local realDemo = G.ImGui.ShowDemoWindow
local realCH = G.ImGui.CollapsingHeader
G.ImGui.CollapsingHeader = function(...)
if ARG_OPEN then G.ImGui.SetNextItemOpen(true, G.ImGuiCond.Always) end
return realCH(...)
end
local realTN = G.ImGui.TreeNode
G.ImGui.TreeNode = function(...)
if ARG_OPEN then G.ImGui.SetNextItemOpen(true, G.ImGuiCond.Always) end
return realTN(...)
end
G.ImGui.ShowDemoWindow = function(...)
if ARG_BIG then G.ImGui.SetNextWindowSize(G.ImVec2(900, 1000), G.ImGuiCond.Always) end
return realDemo(...)
end
local realRender = G.ImGui_ImplRoblox.RenderDrawData
G.ImGui_ImplRoblox.RenderDrawData = function(dd)
if FORCE then G.ImGui.GetIO().BackendPlatformUserData.ForceRedraw = true end
local t0 = os.clock(); realRender(dd); render_time = render_time + os.clock() - t0
end
-- Drive frames with scripted (deterministic) input
local seed = 12345
local function rnd(n) seed = (seed * 1103515245 + 12345) % 2147483648; return seed % n end
local function input(t, kc, pos) return { UserInputType = Enum.UserInputType[t], KeyCode = Enum.KeyCode[kc or "Unknown"], Position = pos or { X = 0, Y = 0, Z = 0 } } end
local held = false
local typing = { "H", "E", "L", "L", "O", "Space", "One", "Two", "Minus", "Backspace", "Left", "Return" }
local hex = {}
for i = 0, 255 do hex[i] = string.format("%02x", i) end
local total = 0
for frame = 1, NFRAMES do
local r = rnd(100)
if r < 40 then
mouse = v2(math.clamp(mouse.X + rnd(81) - 40, 0, SCREEN_W - 1), math.clamp(mouse.Y + rnd(81) - 40, 0, SCREEN_H - 1))
elseif r < 55 then
held = not held
if held then UIS.InputBegan:Fire(input("MouseButton1"), false) else UIS.InputEnded:Fire(input("MouseButton1"), false) end
elseif r < 60 then
UIS.InputChanged:Fire(input("MouseWheel", nil, { X = 0, Y = 0, Z = rnd(3) - 1 }), false)
elseif r < 75 then
local k = typing[rnd(#typing) + 1]
local shift = rnd(4) == 0
if shift then keys_down[Enum.KeyCode.LeftShift] = true; UIS.InputBegan:Fire(input("Keyboard", "LeftShift"), false) end
UIS.InputBegan:Fire(input("Keyboard", k), false)
UIS.InputEnded:Fire(input("Keyboard", k), false)
if shift then keys_down[Enum.KeyCode.LeftShift] = nil; UIS.InputEnded:Fire(input("Keyboard", "LeftShift"), false) end
elseif r < 90 then
mouse = v2(rnd(700), rnd(500))
end
FAKE_TIME = frame / 60
local t0 = os.clock()
RunService.Stepped:Fire(frame / 60, 1 / 60)
local dt = os.clock() - t0
if frame > 10 then total = total + dt end
if frame == 10 then render_time = 0; _G_UPLOADS = 0 end
if DUMP_FRAMES[frame] and ARG_DUMP then
local W, H = SCREEN_W, SCREEN_H
local screen = buffer.create(W * H * 4)
local gui = PlayerGui:FindFirstChild("ImGui")
for _, label in ipairs(gui:GetChildren()) do
if label.Visible then
local img, px, py = label.ImageContent.Object, label.Position.X, label.Position.Y
local tw, th = img.Size.X, img.Size.Y
for y = 0, th - 1 do
if py + y < H then
local n = math.min(tw, W - px)
buffer.copy(screen, ((py + y) * W + px) * 4, img.Pixels, y * tw * 4, n * 4)
end
end
end
end
local parts = {}
for i = 0, W * H * 4 - 1 do parts[#parts + 1] = hex[buffer.readu8(screen, i)] end
print(string.format("IMG %d %d %d %s", frame, W, H, table.concat(parts)))
end
end
print(string.format("avg frame total %.2f ms, of which render %.2f ms, uploads/frame %.2f", total / (NFRAMES - 10) * 1000, render_time / (NFRAMES - 10) * 1000, _G_UPLOADS / (NFRAMES - 10)))
+36490
View File
File diff suppressed because it is too large Load Diff
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Headless test harness: runs build/ImGui.luau + a main script in the Luau CLI against a mocked Roblox
(roblox_mock.luau). Rebuilds the bundle first.
python3 tools/test/run.py # 400 frames of main.client.luau, scripted random input
python3 tools/test/run.py --open # force every CollapsingHeader/TreeNode open (exercises whole demo)
python3 tools/test/run.py --big --open --bench # timing, best of 5
python3 tools/test/run.py --check # incremental vs forced redraw must be pixel identical
python3 tools/test/run.py --png out.png # screenshot of the last frame
python3 tools/test/run.py --main my_test.luau # different LocalScript
"""
import argparse, os, shutil, subprocess, sys, zlib, struct, tempfile
TMP = tempfile.mkdtemp(prefix="imgui_test_") # per process: parallel runs must not share files
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(HERE))
LUAU = shutil.which("luau") or "/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin/luau"
G = "game, workspace, Instance, Enum, Vector2, Vector3, UDim2, Color3, Content, warn, require, script, os"
def build_run(args, flags):
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP], check=True, stdout=subprocess.DEVNULL)
out = ["--!native", "--!optimize 2", flags, "local SOURCE_FNS = {}"]
def add(key, path):
s = "\n".join(l for l in open(path).read().split("\n") if not l.startswith("--!"))
out.append("SOURCE_FNS[%r] = function(%s)\n%s\nend" % (key, G, s))
add("ImGui.luau", os.path.join(TMP, "ImGui.luau"))
add("main.client.luau", args.main)
out.append(open(os.path.join(HERE, "roblox_mock.luau")).read())
global OFFSET, LAST_LINE
LAST_LINE = open(os.path.join(TMP, "ImGui.luau")).read().count("\n")
OFFSET = 5 # lines before the ImGui function body in run.luau (flags/header lines + "function(...)" line)
path = os.path.join(TMP, "run.luau")
open(path, "w").write("\n".join(out))
return path
def run(args, extra=""):
flags = "ARG_FRAMES=%d; ARG_BIG=%s; ARG_OPEN=%s; %s" % (args.frames, str(args.big).lower(), str(args.open).lower(), extra)
try:
p = subprocess.run([LUAU, "-O2", "--codegen", build_run(args, flags)], capture_output=True, text=True, timeout=args.timeout)
except subprocess.TimeoutExpired as e:
return 124, "TIMEOUT (infinite loop?)\n" + remap((e.stdout or b"").decode(errors="replace"))
return p.returncode, remap(p.stdout + p.stderr)
def remap(text):
"""run.luau:N -> imgui/<file>.lua:M"""
import json, re
lm = json.load(open(os.path.join(TMP, "ImGui.linemap.json")))
def sub(m):
n = int(m.group(1)) - OFFSET # line inside build/ImGui.luau (minus its 2 stripped --! lines)
n += 2
best = None
if n > LAST_LINE: return "<harness>:%d" % n
for start, name in lm:
if start <= n: best = (start, name)
if not best: return m.group(0)
return "%s:%d" % (best[1], n - best[0] + 1)
return re.sub(r"\S*run\.luau:(\d+)", sub, text)
def images(out):
return [l.split() for l in out.split("\n") if l.startswith("IMG ")]
def write_png(img, path, crop=None):
_, fr, w, h, hx = img; w, h = int(w), int(h); px = bytes.fromhex(hx)
W, H = crop or (w, h)
raw = bytearray()
for y in range(H):
raw.append(0)
for x in range(W):
o = (y * w + x) * 4; r, g, b, a = px[o:o + 4]; k = a / 255
raw += bytes([int(r * k + 90 * (1 - k)), int(g * k + 90 * (1 - k)), int(b * k + 90 * (1 - k))])
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t + d) & 0xffffffff)
open(path, 'wb').write(b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', W, H, 8, 2, 0, 0, 0))
+ chunk(b'IDAT', zlib.compress(bytes(raw))) + chunk(b'IEND', b''))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--frames", type=int, default=400)
ap.add_argument("--main", default=os.path.join(ROOT, "main.client.luau"))
ap.add_argument("--big", action="store_true", help="demo window forced to 900x1000")
ap.add_argument("--open", action="store_true", help="force all CollapsingHeader/TreeNode open")
ap.add_argument("--bench", action="store_true")
ap.add_argument("--check", action="store_true")
ap.add_argument("--png")
ap.add_argument("--crop", default="1100x1080")
ap.add_argument("--timeout", type=int, default=120)
args = ap.parse_args()
if args.bench:
for mode in ["", "ARG_FORCE=true"]:
best = None
for _ in range(5):
code, out = run(args, mode)
if code: print(out[-3000:]); sys.exit(1)
line = [l for l in out.split("\n") if l.startswith("avg frame")][-1]
if best is None or float(line.split()[3]) < float(best.split()[3]): best = line
print("%-12s %s" % (mode or "incremental", best))
return
if args.check:
code, a = run(args, "ARG_DUMP=true")
code2, b = run(args, "ARG_DUMP=true; ARG_FORCE=true")
if code or code2: print((a if code else b)[-3000:]); sys.exit(1)
ok = [x[4] for x in images(a)] == [x[4] for x in images(b)]
print("PIXELS-IDENTICAL" if ok else "PIXELS-DIFFER"); sys.exit(0 if ok else 1)
code, out = run(args, "ARG_DUMP=true" if args.png else "")
print("\n".join(l if not l.startswith("IMG ") else l[:40] + "..." for l in out.split("\n"))[-4000:])
if args.png and images(out):
write_png(images(out)[-1], args.png, tuple(map(int, args.crop.split("x"))))
print("wrote", args.png)
sys.exit(code)
try:
main()
finally:
shutil.rmtree(TMP, ignore_errors=True)
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Static check: ImGui.X / ImGui:X / global ImFoo() calls in imgui/*.lua that nothing defines.
python3 tools/test/undefined.py
"""
import os, re, collections
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SRC = os.path.join(ROOT, "imgui")
files = {}
for dp, _, fn in os.walk(SRC):
for f in fn:
if f.endswith(".lua"):
files[os.path.relpath(os.path.join(dp, f), SRC)] = open(os.path.join(dp, f)).read()
def strip(s): # drop comments and strings (roughly) so we don't report text
s = re.sub(r"--\[(=*)\[.*?\]\1\]", "", s, flags=re.S)
s = re.sub(r"\[(=*)\[.*?\]\1\]", '""', s, flags=re.S)
s = re.sub(r'"(\\.|[^"\\\n])*"', '""', s)
s = re.sub(r"'(\\.|[^'\\\n])*'", "''", s)
return re.sub(r"--[^\n]*", "", s)
defined_imgui, defined_global, used = set(), set(), collections.defaultdict(list)
for name, src in files.items():
s = strip(src)
defined_imgui.update(re.findall(r"function\s+ImGui[.:](\w+)", s))
defined_imgui.update(re.findall(r"\bImGui\.(\w+)\s*=[^=]", s))
defined_global.update(re.findall(r"^\s*function\s+([A-Za-z_]\w*)\s*\(", s, re.M))
defined_global.update(re.findall(r"^\s*([A-Za-z_]\w*)\s*=[^=]", s, re.M))
defined_global.update(re.findall(r"^\s*local\s+function\s+([A-Za-z_]\w*)", s, re.M))
defined_global.update(re.findall(r"^\s*local\s+([A-Za-z_][\w, ]*)", s, re.M) and
[x.strip() for l in re.findall(r"^\s*local\s+([A-Za-z_][\w, ]*)", s, re.M) for x in l.split(",")])
for i, line in enumerate(s.split("\n"), 1):
for m in re.findall(r"\bImGui\.(\w+)\s*\(", line):
used[("ImGui." + m, m, True)].append("%s:%d" % (name, i))
for m in re.findall(r"(?<![.:\w])(Im[A-Z]\w*)\s*\(", line):
used[(m, m, False)].append("%s:%d" % (name, i))
missing = [(k, v) for k, v in used.items() if (k[1] not in defined_imgui if k[2] else k[1] not in defined_global)]
for (full, _, _), locs in sorted(missing):
print("%-45s %d uses, e.g. %s" % (full, len(locs), ", ".join(locs[:3])))
print("total undefined:", len(missing))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff