Port debug tools (item picker, flash style color, debug locate, DebugDraw*, keyboard preview, debug break, DrawCmd mesh view), context hooks, SetContextName, AddInputCharactersUTF8, sanity checks, window resize ids, Register/UnregisterUserTexture, SetPixelDensity, TestShortcutRouting, DemoMarker; InputTextState selection API (fixes undo crash, callbacks get 0-based positions), ShortcutsForCancel (Esc/right-click reverts drags/sliders), ImParseFormatSanitizeForPrinting, ImDrawList CloneOutput/PrimQuadUV/AddImageQuad, ImDrawData DeIndexAllBuffers/ScaleClipRects, internal helpers (ImBitVector, ImGuiPackedDate, inline ImGui getters...); t_debugtools, t_helpers

This commit is contained in:
4DBug
2026-10-15 08:52:30 -05:00
parent 9be05aeb5c
commit e3840a938a
16 changed files with 7058 additions and 2073 deletions
+1 -1
View File
@@ -1 +1 @@
[[46, "imgui.lua"], [21466, "imgui_demo.lua"], [33318, "imgui_draw.lua"], [41521, "imgui_h.lua"], [44455, "imgui_impl_roblox.lua"], [46103, "imgui_impl_roblox_fonts.lua"], [52506, "imgui_internal.lua"], [56228, "imgui_tables.lua"], [61287, "imgui_widgets.lua"], [73263, "imstb_rectpack.lua"], [73927, "imstb_textedit.lua"], [75469, "imstb_truetype.lua"], [78257, "imstd_minstdio.lua"]]
[[46, "imgui.lua"], [22580, "imgui_demo.lua"], [34431, "imgui_draw.lua"], [42736, "imgui_h.lua"], [45681, "imgui_impl_roblox.lua"], [47329, "imgui_impl_roblox_fonts.lua"], [53732, "imgui_internal.lua"], [57611, "imgui_tables.lua"], [62711, "imgui_widgets.lua"], [74825, "imstb_rectpack.lua"], [75489, "imstb_textedit.lua"], [77031, "imstb_truetype.lua"], [79819, "imstd_minstdio.lua"]]
+1765 -173
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -6,6 +6,7 @@ export type ImVec4 = { x: number, y: number, z: number, w: number, [any]: any }
export type ImGuiAPI = {
AcceptDragDropPayload: (type: string, flags: number?) -> any,
ActivateItemByID: (...any) -> ...any,
AddContextHook: (...any) -> ...any,
AddDrawListToDrawDataEx: (...any) -> ...any,
AddSettingsHandler: (...any) -> ...any,
AddUpdateViewport: (...any) -> ...any,
@@ -72,6 +73,7 @@ export type ImGuiAPI = {
CalcTypematicRepeatAmount: (...any) -> ...any,
CalcWindowNextAutoFitSize: (...any) -> ...any,
CalcWrapWidthForPos: (...any) -> ...any,
CallContextHooks: (...any) -> ...any,
Checkbox: (label: string, v: boolean?) -> (boolean, boolean?),
CheckboxFlags: (...any) -> ...any,
CleanupIniSettings: (...any) -> ...any,
@@ -111,15 +113,29 @@ export type ImGuiAPI = {
DataTypeGetInfo: (...any) -> ...any,
DataTypeIsZero: (...any) -> ...any,
DebugAllocHook: (...any) -> ...any,
DebugBreakButton: (...any) -> ...any,
DebugBreakButtonTooltip: (...any) -> ...any,
DebugBreakClearData: (...any) -> ...any,
DebugDrawCursorPos: (...any) -> ...any,
DebugDrawItemRect: (...any) -> ...any,
DebugDrawLineExtents: (...any) -> ...any,
DebugFlashStyleColor: (idx: number) -> (),
DebugHookIdInfo: (...any) -> ...any,
DebugLocateItem: (...any) -> ...any,
DebugLocateItemOnHover: (...any) -> ...any,
DebugLocateItemResolveWithLastItem: (...any) -> ...any,
DebugLog: (fmt: string, ...any) -> (),
DebugLogV: (fmt: string, ...any) -> (),
DebugNodeColumns: (...any) -> ...any,
DebugNodeDockNode: (...any) -> ...any,
DebugNodeDrawCmdShowMeshAndBoundingBox: (...any) -> ...any,
DebugNodeDrawList: (...any) -> ...any,
DebugNodeFont: (...any) -> ...any,
DebugNodeFontGlyph: (...any) -> ...any,
DebugNodeFontGlyphsForSrcMask: (...any) -> ...any,
DebugNodeInputTextState: (...any) -> ...any,
DebugNodeMultiSelectState: (...any) -> ...any,
DebugNodePlatformMonitor: (...any) -> ...any,
DebugNodeStorage: (...any) -> ...any,
DebugNodeTabBar: (...any) -> ...any,
DebugNodeTable: (...any) -> ...any,
@@ -131,8 +147,13 @@ export type ImGuiAPI = {
DebugNodeWindowSettings: (...any) -> ...any,
DebugNodeWindowsList: (...any) -> ...any,
DebugNodeWindowsListByBeginStackParent: (...any) -> ...any,
DebugRenderKeyboardPreview: (...any) -> ...any,
DebugRenderViewportThumbnail: (...any) -> ...any,
DebugStartItemPicker: () -> (),
DebugTextEncoding: (str: string) -> (),
DebugTextUnformattedWithLocateItem: (...any) -> ...any,
DebugTextureIDToU64: (...any) -> ...any,
DemoMarker: (...any) -> ...any,
DestroyContext: (ctx: any) -> ...any,
DestroyPlatformWindow: (...any) -> ...any,
DestroyPlatformWindows: () -> (),
@@ -247,6 +268,8 @@ export type ImGuiAPI = {
EndTable: () -> (),
EndTooltip: () -> (),
ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any,
ErrorCheckEndFrameSanityChecks: (...any) -> ...any,
ErrorCheckNewFrameSanityChecks: (...any) -> ...any,
ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any,
ErrorLog: (...any) -> ...any,
ErrorRecoveryStoreState: (...any) -> ...any,
@@ -265,6 +288,7 @@ export type ImGuiAPI = {
FindRenderedTextEnd: (...any) -> ...any,
FindSettingsHandler: (...any) -> ...any,
FindViewportByID: (viewport_id: number) -> any,
FindViewportByPlatformHandle: (platform_handle: any) -> ...any,
FindWindowByID: (...any) -> ...any,
FindWindowByName: (...any) -> ...any,
FindWindowDisplayIndex: (...any) -> ...any,
@@ -279,6 +303,7 @@ export type ImGuiAPI = {
GcAwakeTransientWindowBuffers: (...any) -> ...any,
GcCompactTransientMiscBuffers: (...any) -> ...any,
GcCompactTransientWindowBuffers: (...any) -> ...any,
GetActiveID: (...any) -> ...any,
GetBackgroundDrawList: (viewport: any) -> ...any,
GetBoxSelectState: (...any) -> ...any,
GetClipboardText: () -> any,
@@ -293,6 +318,9 @@ export type ImGuiAPI = {
GetContentRegionAvail: () -> ImVec2,
GetContentRegionMax: (...any) -> ...any,
GetCurrentContext: () -> any,
GetCurrentFocusScope: (...any) -> ...any,
GetCurrentTabBar: (...any) -> ...any,
GetCurrentTable: (...any) -> ...any,
GetCurrentWindow: (...any) -> ...any,
GetCurrentWindowRead: (...any) -> ...any,
GetCursorPos: () -> ImVec2,
@@ -304,7 +332,9 @@ export type ImGuiAPI = {
GetDragDropPayload: () -> any,
GetDrawData: () -> any,
GetDrawListSharedData: () -> any,
GetFocusID: (...any) -> ...any,
GetFont: () -> any,
GetFontBaked: () -> any,
GetFontSize: () -> number,
GetFontTexUvWhitePixel: () -> ImVec2,
GetForegroundDrawList: (viewport: any) -> ...any,
@@ -322,6 +352,7 @@ export type ImGuiAPI = {
GetItemRectMax: () -> ImVec2,
GetItemRectMin: () -> ImVec2,
GetItemRectSize: () -> ImVec2,
GetItemStatusFlags: (...any) -> ...any,
GetKeyChordName: (...any) -> ...any,
GetKeyData: (...any) -> ...any,
GetKeyMagnitude2d: (...any) -> ...any,
@@ -340,6 +371,7 @@ export type ImGuiAPI = {
GetMousePosOnOpeningCurrentPopup: () -> ImVec2,
GetMultiSelectState: (...any) -> ...any,
GetNavTweakPressedAmount: (...any) -> ...any,
GetPixelDensity: (...any) -> ...any,
GetPlatformIO: () -> any,
GetPopupAllowedExtentRect: (...any) -> ...any,
GetRoundedFontSize: (...any) -> ...any,
@@ -369,10 +401,13 @@ export type ImGuiAPI = {
GetWindowContentRegionMax: (...any) -> ...any,
GetWindowContentRegionMin: (...any) -> ...any,
GetWindowDockID: () -> number,
GetWindowDockNode: (...any) -> ...any,
GetWindowDpiScale: () -> number,
GetWindowDrawList: () -> any,
GetWindowHeight: () -> number,
GetWindowPos: () -> ImVec2,
GetWindowResizeBorderID: (...any) -> ...any,
GetWindowResizeCornerID: (...any) -> ...any,
GetWindowScrollbarID: (...any) -> ...any,
GetWindowScrollbarRect: (...any) -> ...any,
GetWindowSize: () -> ImVec2,
@@ -414,6 +449,7 @@ export type ImGuiAPI = {
IsInNavFocusRoute: (...any) -> ...any,
IsItemActivated: () -> boolean,
IsItemActive: () -> boolean,
IsItemActiveAsInputText: (...any) -> ...any,
IsItemClicked: (mouse_button: number?) -> boolean,
IsItemDeactivated: () -> boolean,
IsItemDeactivatedAfterEdit: () -> boolean,
@@ -430,6 +466,7 @@ export type ImGuiAPI = {
IsKeyReleased: (...any) -> ...any,
IsKeyboardKey: (...any) -> ...any,
IsLRModKey: (...any) -> ...any,
IsLegacyKey: (...any) -> ...any,
IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean,
IsMouseClickedEx: (...any) -> ...any,
IsMouseDoubleClicked: (...any) -> ...any,
@@ -569,6 +606,8 @@ export type ImGuiAPI = {
RadioButton: (...any) -> ...any,
RadioButtonEx: (...any) -> ...any,
RegisterFontAtlas: (...any) -> ...any,
RegisterUserTexture: (...any) -> ...any,
RemoveContextHook: (...any) -> ...any,
RemoveSettingsHandler: (...any) -> ...any,
Render: () -> (),
RenderArrow: (...any) -> ...any,
@@ -604,6 +643,7 @@ export type ImGuiAPI = {
ScaleRatioFromValueT: (...any) -> ...any,
ScaleValueFromRatioT: (...any) -> ...any,
ScaleWindowsInViewport: (...any) -> ...any,
ScrollToBringRectIntoView: (...any) -> ...any,
ScrollToItem: (...any) -> ...any,
ScrollToRect: (...any) -> ...any,
ScrollToRectEx: (...any) -> ...any,
@@ -621,6 +661,7 @@ export type ImGuiAPI = {
SetColorEditOptions: (...any) -> ...any,
SetColumnOffset: (column_index: number, offset: number) -> (),
SetColumnWidth: (column_index: number, width: number) -> (),
SetContextName: (...any) -> ...any,
SetCurrentContext: (ctx: any) -> ...any,
SetCurrentFont: (...any) -> ...any,
SetCurrentViewport: (...any) -> ...any,
@@ -634,6 +675,7 @@ export type ImGuiAPI = {
SetItemDefaultFocus: () -> (),
SetItemKeyOwner: (...any) -> ...any,
SetItemTooltip: (fmt: string, ...any) -> (),
SetItemTooltipV: (fmt: string, ...any) -> (),
SetKeyOwner: (...any) -> ...any,
SetKeyOwnersForKeyChord: (...any) -> ...any,
SetKeyboardFocusHere: (offset: number?) -> (),
@@ -671,6 +713,7 @@ export type ImGuiAPI = {
SetNextWindowSize: (size: ImVec2, cond: number?) -> (),
SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any,
SetNextWindowViewport: (id: number) -> (),
SetPixelDensity: (...any) -> ...any,
SetScrollFromPosX: (...any) -> ...any,
SetScrollFromPosY: (...any) -> ...any,
SetScrollHereX: (center_x_ratio: number?) -> (),
@@ -681,6 +724,7 @@ export type ImGuiAPI = {
SetStateStorage: (tree: any) -> ...any,
SetTabItemClosed: (label: string) -> (),
SetTooltip: (fmt: string, ...any) -> (),
SetTooltipV: (fmt: string, ...any) -> (),
SetWindowClipRectBeforeSetChannel: (...any) -> ...any,
SetWindowCollapsed: (...any) -> ...any,
SetWindowDock: (...any) -> ...any,
@@ -688,6 +732,7 @@ export type ImGuiAPI = {
SetWindowFontScale: (...any) -> ...any,
SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any,
SetWindowHitTestHole: (...any) -> ...any,
SetWindowParentWindowForFocusRoute: (...any) -> ...any,
SetWindowPos: (...any) -> ...any,
SetWindowSize: (...any) -> ...any,
SetWindowViewport: (...any) -> ...any,
@@ -836,6 +881,7 @@ export type ImGuiAPI = {
TempInputScalar: (...any) -> ...any,
TempInputText: (...any) -> ...any,
TestKeyOwner: (...any) -> ...any,
TestShortcutRouting: (...any) -> ...any,
Text: (fmt: string, ...any) -> (),
TextAligned: (...any) -> ...any,
TextBufferToString: (buf: {number}) -> string,
@@ -867,9 +913,12 @@ export type ImGuiAPI = {
TypingSelectFindNextSingleCharMatch: (...any) -> ...any,
Unindent: (indent_w: number?) -> (),
UnregisterFontAtlas: (...any) -> ...any,
UnregisterUserTexture: (...any) -> ...any,
UpdateAliasKey: (...any) -> ...any,
UpdateCurrentFontSize: (...any) -> ...any,
UpdateDebugToolFlashStyleColor: (...any) -> ...any,
UpdateDebugToolItemPathQuery: (...any) -> ...any,
UpdateDebugToolItemPicker: (...any) -> ...any,
UpdateFontsEndFrame: (...any) -> ...any,
UpdateFontsNewFrame: (...any) -> ...any,
UpdateHoveredWindowAndCaptureFlags: (...any) -> ...any,
+1 -1
View File
@@ -1 +1 @@
[[183, "imgui.lua"], [21603, "imgui_demo.lua"], [33455, "imgui_draw.lua"], [41658, "imgui_h.lua"], [44592, "imgui_impl_love.lua"], [45132, "imgui_internal.lua"], [48854, "imgui_tables.lua"], [53913, "imgui_widgets.lua"], [65889, "imstb_rectpack.lua"], [66553, "imstb_textedit.lua"], [68095, "imstb_truetype.lua"], [70883, "imstd_minstdio.lua"]]
[[183, "imgui.lua"], [22717, "imgui_demo.lua"], [34568, "imgui_draw.lua"], [42873, "imgui_h.lua"], [45818, "imgui_impl_love.lua"], [46358, "imgui_internal.lua"], [50237, "imgui_tables.lua"], [55337, "imgui_widgets.lua"], [67451, "imstb_rectpack.lua"], [68115, "imstb_textedit.lua"], [69657, "imstb_truetype.lua"], [72445, "imstd_minstdio.lua"]]
+1765 -173
View File
File diff suppressed because it is too large Load Diff
+1520 -1553
View File
File diff suppressed because it is too large Load Diff
+1194 -80
View File
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -214,7 +214,7 @@ function ShowDockingDisabledMessage()
end
-- Helper to wire demo markers located in code to an interactive browser (e.g. https://pthom.github.io/imgui_explorer)
function IMGUI_DEMO_MARKER(section) end -- [Port] no ImGui.DemoMarker() hook in this port: no-op
function IMGUI_DEMO_MARKER(section) ImGui.DemoMarker("imgui_demo.lua", 0, section) end -- [Port] no __LINE__ in Lua: line is 0
-- Sneakily forward declare functions which aren't worth putting in public API yet
-- [Port] ImGui.ShowFontAtlas(atlas) and ImGui.TreeNodeSetOpen(storage_id, is_open) are already public here.
@@ -698,8 +698,7 @@ function DemoWindowMenuBar(demo_data)
_, demo_data.ShowDebugLog = ImGui.MenuItem("Debug Log", nil, demo_data.ShowDebugLog, has_debug_tools)
_, demo_data.ShowIDStackTool = ImGui.MenuItem("ID Stack Tool", nil, demo_data.ShowIDStackTool, has_debug_tools)
local is_debugger_present = io.ConfigDebugIsDebuggerPresent
local has_item_picker = ImGui.DebugStartItemPicker ~= nil -- [Port] ImGui.DebugStartItemPicker() is not ported yet: menu item disabled until it is
if ImGui.MenuItem("Item Picker", nil, false, has_debug_tools and has_item_picker) then -- and is_debugger_present
if ImGui.MenuItem("Item Picker", nil, false, has_debug_tools) then -- and is_debugger_present
ImGui.DebugStartItemPicker()
end
if not is_debugger_present then
@@ -8904,7 +8903,7 @@ function ImGui.ShowStyleEditor(ref)
end
ImGui.PushID(i)
if ImGui.Button("?") then
if ImGui.DebugFlashStyleColor then ImGui.DebugFlashStyleColor(i) end -- [Port] DebugFlashStyleColor() is not in the library yet
ImGui.DebugFlashStyleColor(i)
end
ImGui.SetItemTooltip("Flash given color to identify places where it is used.")
ImGui.SameLine()
+102
View File
@@ -4155,6 +4155,42 @@ function MT.ImDrawData:AddDrawList(draw_list)
ImGui.AddDrawListToDrawDataEx(self, self.CmdLists, draw_list)
end
-- For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering!
-- [Luau] IdxBuffer values are 1-based vertex indices
function MT.ImDrawData:DeIndexAllBuffers()
local new_vtx_buffer = ImDrawVtxBuffer()
self.TotalVtxCount = 0; self.TotalIdxCount = 0
for i = 1, self.CmdLists.Size do
repeat
local draw_list = self.CmdLists.Data[i]
if draw_list.IdxBuffer.Size == 0 then
break -- continue
end
new_vtx_buffer:resize(draw_list.IdxBuffer.Size)
local src, dst, idx = draw_list.VtxBuffer.Buf, new_vtx_buffer.Buf, draw_list.IdxBuffer.Data
for j = 1, draw_list.IdxBuffer.Size do
buffer.copy(dst, (j - 1) * VTX_STRIDE, src, (idx[j] - 1) * VTX_STRIDE, VTX_STRIDE)
end
draw_list.VtxBuffer, new_vtx_buffer = new_vtx_buffer, draw_list.VtxBuffer -- swap()
draw_list.IdxBuffer:resize(0)
self.TotalVtxCount = self.TotalVtxCount + draw_list.VtxBuffer.Size
until true
end
end
-- Helper to scale the ClipRect field of each ImDrawCmd.
-- Use if your final output buffer is at a different scale than draw_data->DisplaySize,
-- or if there is a difference between your window resolution and framebuffer resolution.
function MT.ImDrawData:ScaleClipRects(fb_scale)
for i = 1, self.CmdLists.Size do
local draw_list = self.CmdLists.Data[i]
for j = 1, draw_list.CmdBuffer.Size do
local cmd = draw_list.CmdBuffer.Data[j]
cmd.ClipRect = ImVec4(cmd.ClipRect.x * fb_scale.x, cmd.ClipRect.y * fb_scale.y, cmd.ClipRect.z * fb_scale.x, cmd.ClipRect.w * fb_scale.y)
end
end
end
local IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN = 4
local IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX = 512
@@ -4262,6 +4298,29 @@ function MT.ImDrawList:_ClearFreeMemory()
self._Splitter:ClearFreeMemory()
end
-- Note: For multi-threaded rendering, consider using `imgui_threaded_rendering` from https://github.com/ocornut/imgui_club
-- [Luau] C++ copies the vectors by value: copy the ImDrawCmd structs and the vertex buffer bytes
--- @return ImDrawList
function MT.ImDrawList:CloneOutput()
local dst = ImDrawList(nil)
for i = 1, self.CmdBuffer.Size do
local src_cmd = self.CmdBuffer.Data[i]
local cmd = ImDrawCmd()
for k, v in pairs(src_cmd) do cmd[k] = v end
cmd.ClipRect = ImVec4(src_cmd.ClipRect.x, src_cmd.ClipRect.y, src_cmd.ClipRect.z, src_cmd.ClipRect.w)
if src_cmd.TexRef then cmd.TexRef = ImTextureRef(src_cmd.TexRef._TexID); cmd.TexRef._TexData = src_cmd.TexRef._TexData end
dst.CmdBuffer:push_back(cmd)
end
if self.IdxBuffer.Size > 0 then
dst.IdxBuffer:resize(self.IdxBuffer.Size)
table.move(self.IdxBuffer.Data, 1, self.IdxBuffer.Size, 1, dst.IdxBuffer.Data)
end
dst.VtxBuffer:resize(self.VtxBuffer.Size)
buffer.copy(dst.VtxBuffer.Buf, 0, self.VtxBuffer.Buf, 0, self.VtxBuffer.Size * VTX_STRIDE)
dst.Flags = self.Flags
return dst
end
function MT.ImDrawList:AddDrawCmd()
local draw_cmd = ImDrawCmd()
@@ -4819,6 +4878,22 @@ function MT.ImDrawList:PrimRectUV(a, c, uv_a, uv_c, col)
self._IdxWritePtr = idx_write_ptr + 6
end
function MT.ImDrawList:PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col)
local idx = self._VtxCurrentIdx
local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf
local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = idx; idx_data[idx_write_ptr + 1] = idx + 1; idx_data[idx_write_ptr + 2] = idx + 2
idx_data[idx_write_ptr + 3] = idx; idx_data[idx_write_ptr + 4] = idx + 2; idx_data[idx_write_ptr + 5] = idx + 3
local vtx_write_ptr = self._VtxWritePtr
WV(vtx_data, vtx_write_ptr + 0, a.x, a.y, uv_a.x, uv_a.y, col)
WV(vtx_data, vtx_write_ptr + 1, b.x, b.y, uv_b.x, uv_b.y, col)
WV(vtx_data, vtx_write_ptr + 2, c.x, c.y, uv_c.x, uv_c.y, col)
WV(vtx_data, vtx_write_ptr + 3, d.x, d.y, uv_d.x, uv_d.y, col)
self._VtxWritePtr = vtx_write_ptr + 4
self._VtxCurrentIdx = idx + 4
self._IdxWritePtr = idx_write_ptr + 6
end
--- @param thickness float
--- @return float
-- [Luau] local function (also MT.ImDrawList._CalculateCenterBiasedOffset)
@@ -6808,6 +6883,30 @@ function MT.ImDrawList:AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col)
end
end
function MT.ImDrawList:AddImageQuad(tex_ref, p1, p2, p3, p4, uv1, uv2, uv3, uv4, col)
if uv1 == nil then uv1 = ImVec2(0, 0) end
if uv2 == nil then uv2 = ImVec2(1, 0) end
if uv3 == nil then uv3 = ImVec2(1, 1) end
if uv4 == nil then uv4 = ImVec2(0, 1) end
if col == nil then col = IM_COL32_WHITE end
if bit32.band(col, IM_COL32_A_MASK) == 0 then
return
end
local push_texture_id = tex_ref ~= self._CmdHeader.TexRef
if push_texture_id then
self:PushTexture(tex_ref)
end
self:PrimReserve(6, 4)
self:PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col)
if push_texture_id then
self:PopTexture()
end
end
--- @param tex_ref ImTextureRef
--- @param p_min ImVec2
--- @param p_max ImVec2
@@ -6901,6 +7000,9 @@ function MT.ImDrawList:PopClipRect()
self:_OnChangedClipRect()
end
function MT.ImDrawList:GetClipRectMin() local cr = self._ClipRectStack:back(); return ImVec2(cr.x, cr.y) end
function MT.ImDrawList:GetClipRectMax() local cr = self._ClipRectStack:back(); return ImVec2(cr.z, cr.w) end
--- @param center ImVec2
--- @param radius float
--- @param a_min_sample int
+19 -8
View File
@@ -499,8 +499,8 @@ end
--- @field UserCallbackData any
--- @field UserCallbackDataSize int
--- @field UserCallbackDataOffset int
local IM_DRAWCMD = {}
IM_DRAWCMD.__index = IM_DRAWCMD
MT.ImDrawCmd = {}
MT.ImDrawCmd.__index = MT.ImDrawCmd
--- @return ImDrawCmd
--- @nodiscard
@@ -515,11 +515,11 @@ function ImDrawCmd()
UserCallbackData = nil, -- 4-8 -- Callback user data (when UserCallback != nil). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored.
UserCallbackDataSize = 0, -- 4 -- Size of callback user data when using storage, otherwise 0.
UserCallbackDataOffset = 0 -- 4 -- [Internal] Offset of callback user data when using storage, otherwise -1.
}, IM_DRAWCMD)
}, MT.ImDrawCmd)
end
--- @return ImTextureID
function IM_DRAWCMD:GetTexID()
function MT.ImDrawCmd:GetTexID()
local tex_id = (self.TexRef._TexData) and self.TexRef._TexData.TexID or self.TexRef._TexID
if self.TexRef._TexData ~= nil then
IM_ASSERT(tex_id ~= ImTextureID_Invalid, "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!")
@@ -775,11 +775,14 @@ end
--- @param x int
--- @param y int
function MT.ImTextureData:GetPixels() IM_ASSERT(self.Pixels ~= nil); return self.Pixels end
function MT.ImTextureData:GetPixelsAt(x, y)
return self.Pixels, (x + y * self.Width) * self.BytesPerPixel + 1
end
function MT.ImTextureData:GetSizeInBytes() return self.Width * self.Height * self.BytesPerPixel end
function MT.ImTextureData:GetPitch() return self.Width * self.BytesPerPixel end
function MT.ImTextureData:GetTexRef() local tex_ref = ImTextureRef(); tex_ref._TexData = self; tex_ref._TexID = ImTextureID_Invalid; return tex_ref end
function MT.ImTextureData:GetTexID() return self.TexID end
--- @param tex_id ImTextureID
@@ -799,8 +802,8 @@ function MT.ImTextureData:SetStatus(status) self.Status = status if (status == I
--- @class ImTextureRef
--- @field _TexData? ImTextureData
--- @field _TexID ImTextureID
local IM_TEXTURE_REF = {}
IM_TEXTURE_REF.__index = IM_TEXTURE_REF
MT.ImTextureRef = {}
MT.ImTextureRef.__index = MT.ImTextureRef
--- @return ImTextureRef
--- @nodiscard
@@ -809,7 +812,14 @@ function ImTextureRef(tex_id)
-- Members (either are set, never both!)
_TexData = nil, -- A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded.
_TexID = tex_id or ImTextureID_Invalid -- _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui.Image() calls.
}, IM_TEXTURE_REF)
}, MT.ImTextureRef)
end
-- This is provided for consistency (but we don't actually use this)
--- @return ImTextureID
function MT.ImTextureRef:GetTexID()
IM_ASSERT(not (self._TexData ~= nil and self._TexID ~= ImTextureID_Invalid))
if self._TexData then return self._TexData.TexID else return self._TexID end
end
-------------------------------------------------------------------------------
@@ -2543,7 +2553,8 @@ function MT.ImGuiInputTextCallbackData:InsertChars(pos, new_text)
self.SelectionStart = self.CursorPos; self.SelectionEnd = self.CursorPos
end
function MT.ImGuiInputTextCallbackData:SelectAll() self.SelectionStart = 0; self.SelectionEnd = self.BufTextLen end
function MT.ImGuiInputTextCallbackData:SelectAll() self.SelectionStart = 0; self.CursorPos = self.BufTextLen; self.SelectionEnd = self.BufTextLen end
function MT.ImGuiInputTextCallbackData:SetSelection(s, e) IM_ASSERT(s >= 0 and s <= self.BufTextLen); IM_ASSERT(e >= 0 and e <= self.BufTextLen); self.SelectionStart = s; self.CursorPos = e; self.SelectionEnd = e end
function MT.ImGuiInputTextCallbackData:ClearSelection() self.SelectionStart = self.BufTextLen; self.SelectionEnd = self.BufTextLen end
function MT.ImGuiInputTextCallbackData:HasSelection() return self.SelectionStart ~= self.SelectionEnd end
+183 -26
View File
@@ -706,6 +706,27 @@ function ImCharIsBlankA(c) return c == 32 or c == 9 end
--- @param c char
function ImCharIsBlankW(c) return c == 32 or c == 9 or c == 0x3000 end
--- @param c char
function ImCharIsXdigitA(c) return (c >= 48 and c <= 57) or (c >= 65 and c <= 70) or (c >= 97 and c <= 102) end
--- @param lhs ImVec2
--- @param fail_value float
function ImInvLength(lhs, fail_value) local d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if d > 0.0 then return ImRsqrt(d) end; return fail_value end
function ImRoundPositive64(f) return _Trunc(f + 0.5) end -- Positive values only.
function ImRoundSigned64(f) if f >= 0.0 then return _Trunc(f + 0.5) else return _Trunc(f - 0.5) end end
-- [Luau] C '%' truncates toward zero; math.fmod matches it
function ImModPositive(a, b) return math.fmod(a + b, b) end
function ImIsFloatAboveGuaranteedIntegerPrecision(f) return f <= -16777216 or f >= 16777216 end
-- Helper: ImVec2i (2D vector, integer) / ImVec2ih (2D vector, half-size integer, for long-term packed storage)
-- [Luau] both are plain ImVec2 holding whole numbers
function ImVec2i(x, y) return ImVec2(x or 0, y or 0) end
--- @overload fun(rhs: ImVec2): ImVec2
function ImVec2ih(x, y)
if type(x) == "table" then return ImVec2(_Trunc(x.x), _Trunc(x.y)) end
return ImVec2(x or 0, y or 0)
end
--- @enum ImGuiNavMoveFlags
ImGuiNavMoveFlags =
{
@@ -1004,6 +1025,28 @@ function ImBitArraySetBit(arr, n)
arr[idx] = bitOr(arr[idx], mask)
end
-- [Luau] bit arrays are ImU32[] tables with 1-based word indices; bit indices 'n' are 1-based like the functions above
--- @param bitcount int
function ImBitArrayGetStorageSizeInBytes(bitcount) return bitLShift(bitRShift(bitcount + 31, 5), 2) end
--- @param arr ImU32[]
--- @param bitcount int
function ImBitArrayClearAllBits(arr, bitcount) for i = 1, bitRShift(bitcount + 31, 5) do arr[i] = 0 end end
function ImBitArrayTestBit(arr, n) local mask = bitLShift(1, bitAnd(n - 1, 31)); return bitAnd(arr[bitRShift(n - 1, 5) + 1], mask) ~= 0 end
function ImBitArrayClearBit(arr, n) local mask = bitLShift(1, bitAnd(n - 1, 31)); local idx = bitRShift(n - 1, 5) + 1; arr[idx] = bitAnd(arr[idx], bitNot(mask)) end
-- Works on range [n..n2)
function ImBitArraySetBitRange(arr, n, n2)
n = n - 1; n2 = n2 - 1 -- [Luau] to 0-based
n2 = n2 - 1
while n <= n2 do
local a_mod = bitAnd(n, 31)
local b_mod = ((n2 > bitOr(n, 31)) and 31 or bitAnd(n2, 31)) + 1
local mask = bitAnd(2 ^ b_mod - 1, bitNot(2 ^ a_mod - 1))
local idx = bitRShift(n, 5) + 1
arr[idx] = bitOr(arr[idx], mask)
n = bitAnd(n + 32, bitNot(31))
end
end
-- Bit array for named keys
--- @alias ImBitArrayForNamedKeys ImBitArray
@@ -1030,11 +1073,30 @@ function ImBitArray(BITCOUNT, OFFSET)
--- @param n int # 1-based
this.SetBit = function(self, n) n = n + OFFSET; IM_ASSERT(n >= 1 and n <= BITCOUNT); ImBitArraySetBit(self.Data, n); end
--- @param n int # 1-based
this.ClearBit = function(self, n) n = n + OFFSET; IM_ASSERT(n >= 1 and n <= BITCOUNT); ImBitArrayClearBit(self.Data, n); end
--- @param n int # 1-based, range [n..n2)
this.SetBitRange = function(self, n, n2) n = n + OFFSET; n2 = n2 + OFFSET; IM_ASSERT(n >= 1 and n <= BITCOUNT and n2 > n and n2 <= BITCOUNT + 1); ImBitArraySetBitRange(self.Data, n, n2); end
this:ClearAllBits()
return this
end
-- Helper: ImBitVector
-- Store 1-bit per value.
--- @class ImBitVector
--- @field Storage ImVector<ImU32>
MT.ImBitVector = {}
MT.ImBitVector.__index = MT.ImBitVector
function ImBitVector() return setmetatable({ Storage = ImVector() }, MT.ImBitVector) end
function MT.ImBitVector:Create(sz) self.Storage:resize(bitRShift(sz + 31, 5)); for i = 1, self.Storage.Size do self.Storage.Data[i] = 0 end end
function MT.ImBitVector:Clear() self.Storage:clear() end
--- @param n int # 1-based
function MT.ImBitVector:TestBit(n) IM_ASSERT(n <= bitLShift(self.Storage.Size, 5)); return IM_BITARRAY_TESTBIT(self.Storage.Data, n) end
function MT.ImBitVector:SetBit(n) IM_ASSERT(n <= bitLShift(self.Storage.Size, 5)); ImBitArraySetBit(self.Storage.Data, n) end
function MT.ImBitVector:ClearBit(n) IM_ASSERT(n <= bitLShift(self.Storage.Size, 5)); ImBitArrayClearBit(self.Storage.Data, n) end
-- Stateful path API, add points then finish with PathFillConvex() or PathStroke()
-- - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing.
-- so e.g. 'PathArcTo(center, radius, PI * -0.5, PI)' is ok, whereas 'PathArcTo(center, radius, PI, PI * -0.5)' won't have correct anti-aliasing when followed by PathFillConvex().
@@ -1266,9 +1328,13 @@ end
--- @param key string
--- @return ImGuiStyleVarInfo
--- @nodiscard
MT.ImGuiStyleVarInfo = {}
MT.ImGuiStyleVarInfo.__index = MT.ImGuiStyleVarInfo
function ImGuiStyleVarInfo(count, data_type, key)
return { Count = count, DataType = data_type, Key = key }
return setmetatable({ Count = count, DataType = data_type, Key = key }, MT.ImGuiStyleVarInfo)
end
-- [Luau] no pointers: returns the field value (number, or the live ImVec2 table) of 'parent' (e.g. ImGuiStyle)
function MT.ImGuiStyleVarInfo:GetVarPtr(parent) return parent[self.Key] end
-- Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable.
--- @class ImGuiStyleMod
@@ -1383,11 +1449,11 @@ end
--- @field RefVal any
--- @field StorageId ImGuiID
--- @field ColorMarker ImU32
local IMGUI_NEXT_ITEM_DATA = {}
IMGUI_NEXT_ITEM_DATA.__index = IMGUI_NEXT_ITEM_DATA
MT.ImGuiNextItemData = {}
MT.ImGuiNextItemData.__index = MT.ImGuiNextItemData
-- Also cleared manually by ItemAdd()!
function IMGUI_NEXT_ITEM_DATA:ClearFlags()
function MT.ImGuiNextItemData:ClearFlags()
self.HasFlags = ImGuiNextItemDataFlags.None
self.ItemFlagsSet = ImGuiItemFlags.None
end
@@ -1410,7 +1476,7 @@ function ImGuiNextItemData()
RefVal = 0, -- Not exposed yet, for ImGuiInputTextFlags.ParseEmptyAsRefVal
StorageId = 0, -- Set by SetNextItemStorageID()
ColorMarker = 0 -- Set by SetNextItemColorMarker(). Not exposed yet, supported by DragScalar,SliderScalar and for ImGuiSliderFlags.ColorMarkers.
}, IMGUI_NEXT_ITEM_DATA)
}, MT.ImGuiNextItemData)
end
-- Storage for SetNexWindow** functions
@@ -1433,10 +1499,10 @@ end
--- @field BgAlphaVal number?
--- @field MenuBarOffsetMinVal ImVec2?
--- @field RefreshFlagsVal int?
local IMGUI_NEXT_WINDOW_DATA = {}
IMGUI_NEXT_WINDOW_DATA.__index = IMGUI_NEXT_WINDOW_DATA
MT.ImGuiNextWindowData = {}
MT.ImGuiNextWindowData.__index = MT.ImGuiNextWindowData
function IMGUI_NEXT_WINDOW_DATA:ClearFlags()
function MT.ImGuiNextWindowData:ClearFlags()
self.HasFlags = ImGuiNextWindowDataFlags.None
end
@@ -1475,7 +1541,7 @@ function ImGuiNextWindowData()
RefreshFlagsVal = nil,
CacheKeySet = false, -- [Port] SetNextWindowCached()
CacheKeyVal = nil
}, IMGUI_NEXT_WINDOW_DATA)
}, MT.ImGuiNextWindowData)
end
--- @enum ImGuiWindowBgClickFlags
@@ -1738,21 +1804,29 @@ local function ImGuiInputTextState()
SelectedAllMouseLock = false, -- after a double-click to select all, we ignore further mouse drags to update selection
EditedBefore = false, -- edited since activated
EditedThisFrame = false, -- edited this frame
WantReloadUserBuf = false, -- force a reload of user buf so it may be modified externally. may be automatic in future version.
LastMoveDirectionLR = ImGuiDir.Left, -- ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s
ReloadSelectionStart = 0,
ReloadSelectionEnd = 0,
}
return setmetatable(this, MT.ImGuiInputTextState)
end
function MT.ImGuiInputTextState:ClearText() self.TextLen = 0; self.TextA.Data[1] = 0; self:CursorClamp() end
function MT.ImGuiInputTextState:ClearFreeMemory() self.TextA:clear(); self.TextToRevertTo:clear() end
function MT.ImGuiInputTextState:GetText() if self.TextA.Data then return self.TextA.Data else return { 0 } end end -- [Luau] byte table (char[])
-- Internal temporary state for deactivating InputText() instances.
-- Store as part of ImGuiDeactivatedItemData?
--- @class ImGuiInputTextDeactivatedState
--- @field ID ImGuiID # widget id owning the text state (which just got deactivated)
--- @field ElapseFrame int
--- @field TextA ImVector<char> # text buffer
local IMGUI_INPUT_TEXT_DEACTIVATED_STATE = {}
IMGUI_INPUT_TEXT_DEACTIVATED_STATE.__index = IMGUI_INPUT_TEXT_DEACTIVATED_STATE
MT.ImGuiInputTextDeactivatedState = {}
MT.ImGuiInputTextDeactivatedState.__index = MT.ImGuiInputTextDeactivatedState
function IMGUI_INPUT_TEXT_DEACTIVATED_STATE:ClearFreeMemory()
function MT.ImGuiInputTextDeactivatedState:ClearFreeMemory()
self.ID = 0
self.ElapseFrame = 0
self.TextA:clear()
@@ -1765,7 +1839,7 @@ local function ImGuiInputTextDeactivatedState()
ID = 0, -- widget id owning the text state (which just got deactivated)
ElapseFrame = 0,
TextA = ImVector() -- text buffer
}, IMGUI_INPUT_TEXT_DEACTIVATED_STATE)
}, MT.ImGuiInputTextDeactivatedState)
end
--- @alias ImGuiKeyRoutingIndex ImS16
@@ -1798,10 +1872,10 @@ end
--- @field Index ImGuiKeyRoutingIndex[] # Index of first entry in Entries[]
--- @field Entries ImVector<ImGuiKeyRoutingData>
--- @field EntriesNext ImVector<ImGuiKeyRoutingData> # Double-buffer to avoid reallocation (could use a shared buffer)
local _ImGuiKeyRoutingTable = {}
_ImGuiKeyRoutingTable.__index = _ImGuiKeyRoutingTable
MT.ImGuiKeyRoutingTable = {}
MT.ImGuiKeyRoutingTable.__index = MT.ImGuiKeyRoutingTable
function _ImGuiKeyRoutingTable:Clear()
function MT.ImGuiKeyRoutingTable:Clear()
for n = 1, ImGuiKey.NamedKey_COUNT do self.Index[n] = -1 end
self.Entries:clear()
self.EntriesNext:clear()
@@ -1813,7 +1887,7 @@ local function ImGuiKeyRoutingTable()
Index = {}, -- Index of first entry in Entries[]
Entries = ImVector(),
EntriesNext = ImVector() -- Double-buffer to avoid reallocation (could use a shared buffer)
}, _ImGuiKeyRoutingTable)
}, MT.ImGuiKeyRoutingTable)
this:Clear()
@@ -1899,10 +1973,10 @@ end
--- @field DistBox float # Move -- Best candidate box distance to current NavId
--- @field DistCenter float # Move -- Best candidate center distance to current NavId
--- @field DistAxial float # Move -- Best candidate axial distance to current NavId
local IMGUI_NAV_ITEM_DATA = {}
IMGUI_NAV_ITEM_DATA.__index = IMGUI_NAV_ITEM_DATA
MT.ImGuiNavItemData = {}
MT.ImGuiNavItemData.__index = MT.ImGuiNavItemData
function IMGUI_NAV_ITEM_DATA:Clear()
function MT.ImGuiNavItemData:Clear()
self.Window = nil
self.ID = 0
self.FocusScopeId = 0
@@ -1915,7 +1989,7 @@ end
--- @return ImGuiNavItemData
local function ImGuiNavItemData()
local this = setmetatable({ }, IMGUI_NAV_ITEM_DATA)
local this = setmetatable({ }, MT.ImGuiNavItemData)
this.RectRel = ImRect()
this:Clear()
return this
@@ -2488,7 +2562,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
DockContext = ImGuiDockContext(),
DockNodeWindowMenuHandler = nil,
DebugHoveredDockNode = nil, -- Hovered dock node.
CurrentTabBarStack = ImVector(), -- holds ImGuiTabBar references directly (no ImGuiPtrOrIndex needed in Lua)
CurrentTabBarStack = ImVector(), -- ImVector<ImGuiPtrOrIndex>
ShrinkWidthBuffer = ImVector(), -- ImGuiShrinkWidthItem
-- Multi-Select state
@@ -2652,10 +2726,10 @@ end
--- @field WantApply bool # Set when loaded from .ini data
--- @field WantDelete bool # Set to invalidate/delete the settings entry
--- @field Name string # Window name
local IMGUI_WINDOW_SETTINGS = {}
IMGUI_WINDOW_SETTINGS.__index = IMGUI_WINDOW_SETTINGS
MT.ImGuiWindowSettings = {}
MT.ImGuiWindowSettings.__index = MT.ImGuiWindowSettings
function IMGUI_WINDOW_SETTINGS:GetName()
function MT.ImGuiWindowSettings:GetName()
return self.Name
end
@@ -2675,7 +2749,7 @@ function ImGuiWindowSettings()
DockOrder = -1, -- Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible.
ClassId = 0, -- ID of window class if specified
LastUsedDate = 0,
}, IMGUI_WINDOW_SETTINGS)
}, MT.ImGuiWindowSettings)
end
-- Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper.
@@ -3391,6 +3465,8 @@ function ImGuiInputEvent()
end
-- [Internal] Key ranges
ImGuiKey_LegacyNativeKey_BEGIN = 0
ImGuiKey_LegacyNativeKey_END = 512
ImGuiKey_Keyboard_BEGIN = ImGuiKey.NamedKey_BEGIN
ImGuiKey_Keyboard_END = ImGuiKey.GamepadStart
ImGuiKey_Gamepad_BEGIN = ImGuiKey.GamepadStart
@@ -3715,5 +3791,86 @@ end
--- @nodiscard
function ImGuiFocusScopeData() return { ID = nil, WindowID = nil } end
-------------------------------------------------------------------------------
-- [Luau] Inline struct helpers / constructors from imgui.h + imgui_internal.h
-------------------------------------------------------------------------------
-- Helper for running a block of code not more than once a frame
-- [Luau] no operator bool(): call the object, e.g. 'if once() then ... end'
MT.ImGuiOnceUponAFrame = {}
MT.ImGuiOnceUponAFrame.__index = MT.ImGuiOnceUponAFrame
MT.ImGuiOnceUponAFrame.__call = function(self) local current_frame = ImGui.GetFrameCount(); if self.RefFrame == current_frame then return false end; self.RefFrame = current_frame; return true end
function ImGuiOnceUponAFrame() return setmetatable({ RefFrame = -1 }, MT.ImGuiOnceUponAFrame) end
-- [Internal] ImGuiTextFilter item
-- [Luau] stores the item text as a Lua string (upstream: Begin pointer + Len)
--- @param buf string
--- @param b int # 1-based
--- @param e int # exclusive
function ImGuiTextFilterItem(buf, b, e) IM_ASSERT(e > b); return { Text = string.sub(buf, b, e - 1), CountInclude = 0, MatchIncludesMask = 0 } end
-- A date in YYYYMMDD format packed into 16-bits
--- @class ImGuiPackedDate
MT.ImGuiPackedDate = {}
MT.ImGuiPackedDate.__index = MT.ImGuiPackedDate
--- @param yyyymmdd? int
function ImGuiPackedDate(yyyymmdd)
local this = setmetatable({ Year = 0, Month = 0, Day = 0 }, MT.ImGuiPackedDate)
if yyyymmdd ~= nil then -- Pack
this.Year = bitAnd(math.floor(yyyymmdd / 10000) - 2000, 0x7F) -- Year since 2000 (7 bits)
this.Month = bitAnd(math.floor(yyyymmdd / 100) % 100, 0x0F) -- Month (1-12) (4 bits)
this.Day = bitAnd(yyyymmdd % 100, 0x1F) -- Day (1-31) (5 bits)
end
return this
end
function MT.ImGuiPackedDate:IsValid() return self.Year ~= 0 and self.Month ~= 0 and self.Day ~= 0 end
function MT.ImGuiPackedDate:Unpack() if self.Year ~= 0 and self.Month ~= 0 and self.Day ~= 0 then return ((self.Year + 2000) * 10000) + (self.Month * 100) + self.Day end; return 0 end -- Unpack
function MT.ImGuiPackedDate:SubtractMonths(m) while m > 0 do if self.Month == 1 then self.Year = bitAnd(self.Year - 1, 0x7F) end; self.Month = (self.Month == 1) and 12 or self.Month - 1; m = m - 1 end end -- FIXME-OPT: Stupid but enough for what we do with it.
-- Pointer or index, e.g. g.CurrentTabBarStack[]
-- [Luau] overload by argument type: number = index, otherwise pointer
function ImGuiPtrOrIndex(ptr_or_index)
if type(ptr_or_index) == "number" then return { Ptr = nil, Index = ptr_or_index } end
return { Ptr = ptr_or_index, Index = -1 }
end
-- Note that Max is exclusive, so perhaps should be using a Begin/End convention.
--- @class ImGuiListClipperRange
MT.ImGuiListClipperRange = {}
function MT.ImGuiListClipperRange.FromIndices(min, max) return { Min = min, Max = max, PosToIndexConvert = false, PosToIndexOffsetMin = 0, PosToIndexOffsetMax = 0 } end
function MT.ImGuiListClipperRange.FromPositions(y1, y2, off_min, off_max) return { Min = _Trunc(y1), Max = _Trunc(y2), PosToIndexConvert = true, PosToIndexOffsetMin = off_min, PosToIndexOffsetMax = off_max } end
-- Temporary clipper data, buffers shared/reused between instances
--- @class ImGuiListClipperData
MT.ImGuiListClipperData = {}
MT.ImGuiListClipperData.__index = MT.ImGuiListClipperData
function ImGuiListClipperData() return setmetatable({ ListClipper = nil, LossynessOffset = 0.0, StepNo = 0, ItemsFrozen = 0, Ranges = ImVector() }, MT.ImGuiListClipperData) end
function MT.ImGuiListClipperData:Reset(clipper) self.ListClipper = clipper; self.StepNo = 0; self.ItemsFrozen = 0; self.Ranges:resize(0) end
function ImGuiStackLevelInfo() return { ID = 0, QueryFrameCount = 0, QuerySuccess = false, DataType = -1, DescOffset = -1 } end
function ImGuiDebugItemPathQuery() return { MainID = 0, Active = false, Complete = false, Step = 0, Results = ImVector(), ResultsDescBuf = ImGuiTextBuffer(), ResultPathBuf = ImGuiTextBuffer() } end
function ImGuiIDStackTool() return { OptHexEncodeNonAsciiChars = true, OptCopyToClipboardOnCtrlC = false, LastActiveFrame = -1, CopyToClipboardLastTime = -FLT_MAX } end
function ImGuiContextHook() return { HookId = 0, Type = 0, Owner = 0, Callback = nil, UserData = nil } end
-------------------------------------------------------------------------------
-- [Luau] ImGui:: inline helpers from imgui_internal.h
-------------------------------------------------------------------------------
function ImGui.GetCurrentTable() local g = GImGui; return g.CurrentTable end
--- @param window ImGuiWindow
--- @param parent_window ImGuiWindow?
function ImGui.SetWindowParentWindowForFocusRoute(window, parent_window) window.ParentWindowForFocusRoute = parent_window end -- You may also use SetNextWindowClass()'s FocusRouteParentWindowId field.
function ImGui.GetPixelDensity() return GImGui.FontRasterizerDensity end -- [Luau] g.FontRasterizerDensity == upstream g.CurrentPixelDensity
function ImGui.ScrollToBringRectIntoView(window, rect) ImGui.ScrollToRect(window, rect, ImGuiScrollFlags.KeepVisibleEdgeY) end
function ImGui.GetItemStatusFlags() local g = GImGui; return g.LastItemData.StatusFlags end
function ImGui.GetActiveID() local g = GImGui; return g.ActiveId end
function ImGui.GetFocusID() local g = GImGui; return g.NavId end
function ImGui.IsLegacyKey(key) return key >= ImGuiKey_LegacyNativeKey_BEGIN and key < ImGuiKey_LegacyNativeKey_END end
function ImGui.GetWindowDockNode() local g = GImGui; return g.CurrentWindow.DockNode end
function ImGui.GetCurrentFocusScope() local g = GImGui; return g.CurrentFocusScopeId end -- Focus scope we are outputting into, set by PushFocusScope()
function ImGui.GetCurrentTabBar() local g = GImGui; return g.CurrentTabBar end
-- This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field.
function ImGui.IsItemActiveAsInputText() local g = GImGui; return g.ActiveId ~= 0 and g.ActiveId == g.LastItemData.ID and g.InputTextState.ID == g.LastItemData.ID end
return true -- [Roblox] ModuleScripts must return exactly one value
+53 -12
View File
@@ -331,9 +331,13 @@ end
-- Storage for a table .ini settings
--- @class ImGuiTableSettings
MT.ImGuiTableSettings = {}
MT.ImGuiTableSettings.__index = MT.ImGuiTableSettings
function ImGuiTableSettings()
return { ID = 0, SaveFlags = 0, RefScale = 0.0, ColumnsCount = 0, ColumnsCountMax = 0, LastUsedDate = 0, WantApply = false, Columns = {} }
return setmetatable({ ID = 0, SaveFlags = 0, RefScale = 0.0, ColumnsCount = 0, ColumnsCountMax = 0, LastUsedDate = 0, WantApply = false, Columns = {} }, MT.ImGuiTableSettings)
end
-- [Luau] column settings are stored in a 0-based .Columns table (upstream: allocated right after the struct)
function MT.ImGuiTableSettings:GetColumnSettings() return self.Columns end
-- Storage data for a single column for legacy Columns() api
--- @class ImGuiOldColumnData
@@ -4394,6 +4398,17 @@ function ImGui.TableLoadSettingsForColumn(column, column_settings, load_flags)
column.SortDirection = column_settings.SortDirection
end
-- [Luau] boolean "less than" for table.sort; takes (table, lhs column idx, rhs column idx) instead of ImGuiTableFixDisplayOrderColumnData
local function TableFixDisplayOrderComparer(table_, lhs_idx, rhs_idx)
-- Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after.
local lhs_order, rhs_order = table_.Columns[lhs_idx].DisplayOrder, table_.Columns[rhs_idx].DisplayOrder
if lhs_order < 0 then lhs_order = lhs_order + 0x10000 end
if rhs_order < 0 then rhs_order = rhs_order + 0x10000 end
local order_delta = lhs_order - rhs_order
if order_delta ~= 0 then return order_delta < 0 end
return lhs_idx < rhs_idx
end
-- Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2)
function ImGui.TableFixDisplayOrder(table_)
if band(table_.Flags, ImGuiTableFlags.Reorderable) == 0 then
@@ -4408,17 +4423,7 @@ function ImGui.TableFixDisplayOrder(table_)
fdo_columns[n + 1] = n
end
-- Sort by DisplayOrder and then Index
-- Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after.
local function key(idx)
local o = table_.Columns[idx].DisplayOrder
if o < 0 then o = o + 0x10000 end
return o
end
table.sort(fdo_columns, function(lhs_idx, rhs_idx)
local a, b = key(lhs_idx), key(rhs_idx)
if a ~= b then return a < b end
return lhs_idx < rhs_idx
end)
table.sort(fdo_columns, function(lhs_idx, rhs_idx) return TableFixDisplayOrderComparer(table_, lhs_idx, rhs_idx) end)
for n = 0, table_.ColumnsCount - 1 do
table_.Columns[fdo_columns[n + 1]].DisplayOrder = n
end
@@ -4427,6 +4432,27 @@ function ImGui.TableFixDisplayOrder(table_)
end
end
local function TableSettingsHandler_Cleanup(ctx, handler, args)
local g = ctx
for i = 0, g.Tables:GetMapSize() - 1 do
local table_ = g.Tables:GetByIndex(i)
if table_ then
table_.SettingsOffset = -1
end
end
-- [Luau] settings.LastUsedDate is stored unpacked (YYYYMMDD int, 0 = none); g.SessionDate is an ImGuiPackedDate when set
for _, settings in g.SettingsTables:iter() do
local is_valid = ImGuiPackedDate(settings.LastUsedDate):IsValid()
if args._DiscardOlderThanDate ~= 0 and settings.LastUsedDate < args._DiscardOlderThanDate then
settings.ID = 0
elseif args.DiscardWhenMissingDate and not is_valid then
settings.ID = 0
elseif args.SetCurrentSessionDateToAll or (args.SetCurrentSessionDateWhenMissingDate and not is_valid) then
settings.LastUsedDate = g.SessionDate and g.SessionDate:Unpack() or 0
end
end
end
local function ForEachTable(g, fn)
for i = 0, g.Tables:GetMapSize() - 1 do
local t = g.Tables:GetByIndex(i)
@@ -4552,6 +4578,7 @@ function ImGui.TableSettingsAddSettingsHandler()
ini_handler.ReadLineFn = TableSettingsHandler_ReadLine
ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll
ini_handler.WriteAllFn = TableSettingsHandler_WriteAll
ini_handler.CleanupFn = TableSettingsHandler_Cleanup
ImGui.AddSettingsHandler(ini_handler)
end
@@ -4615,6 +4642,20 @@ function ImGui.TableGcCompactSettings()
ForEachTable(g, function(t) t.SettingsOffset = -1 end)
end
-------------------------------------------------------------------------------
-- [SECTION] Tables: Debugging
-------------------------------------------------------------------------------
-- [Luau] global (upstream: static) so ImGui.DebugNodeTable() in imgui.lua can use it
function DebugNodeTableGetSizingPolicyDesc(sizing_policy)
sizing_policy = band(sizing_policy, ImGuiTableFlags.SizingMask_)
if sizing_policy == ImGuiTableFlags.SizingFixedFit then return "FixedFit" end
if sizing_policy == ImGuiTableFlags.SizingFixedSame then return "FixedSame" end
if sizing_policy == ImGuiTableFlags.SizingStretchProp then return "StretchProp" end
if sizing_policy == ImGuiTableFlags.SizingStretchSame then return "StretchSame" end
return "N/A"
end
-------------------------------------------------------------------------------
-- [SECTION] Columns, BeginColumns, EndColumns, etc.
-- (This is a legacy API, prefer using BeginTable/EndTable!)
+180 -42
View File
@@ -2598,12 +2598,14 @@ function ImGui.RoundScalarWithFormatT(format, data_type, v)
return v -- Don't apply if the value is not visible in the format string
end
-- TODO: Sanitize format
-- Sanitize format
local fmt_sanitized = ImParseFormatSanitizeForPrinting(string.sub(format, fmt_start), nil, 32)
-- Format value with our rounding, and read back
local str = ImFormatString(format, v)
local v_str = ImFormatString(fmt_sanitized, v)
-- [Luau] leading spaces: ImAtof() skips them
v = ImAtof(str) or v
v = ImAtof(v_str) or v
return v
end
@@ -2689,6 +2691,30 @@ local function ImParseFormatTrimDecorations(fmt)
return string.sub(fmt, fmt_start, fmt_end - 1)
end
-- Sanitize format
-- - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
-- - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
-- [Luau] fmt_out is a byte table (may be nil); returns the sanitized format as a string
--- @param fmt_in string
--- @param fmt_out char[]?
--- @param fmt_out_size size_t
function ImParseFormatSanitizeForPrinting(fmt_in, fmt_out, fmt_out_size)
local fmt_end = ImParseFormatFindEnd(fmt_in, 1)
-- IM_UNUSED(fmt_out_size)
IM_ASSERT(fmt_end < fmt_out_size) -- Format is too long, let us know if this happens to you!
fmt_out = fmt_out or {}
local n = 0
for i = 1, fmt_end - 1 do
local c = string.byte(fmt_in, i)
if c ~= 39 and c ~= 36 and c ~= 95 then -- Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
n = n + 1
fmt_out[n] = c
end
end
fmt_out[n + 1] = 0 -- Zero-terminate
return string.char(unpack(fmt_out, 1, n))
end
-- - For scanning we need to remove all width and precision fields and flags "%+3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d"
--- @param fmt_in string
--- @param fmt_out char[]
@@ -3290,6 +3316,18 @@ function ImGui.DragBehaviorT(TYPE, SIGNEDTYPE, data_type, v, v_speed, v_min, v_m
return v, true
end
local function ShortcutsForCancel(id)
local g = GImGui
local is_cancel_with_keyboard = ImGui.Shortcut(ImGuiKey.Escape, ImGuiInputFlags.None, id)
-- [Luau] ImGuiKey_NavGamepadCancel macro inlined (io.ConfigNavSwapGamepadButtons is not ported: always FaceRight)
local is_cancel_with_gamepad = bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) ~= 0 and bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.HasGamepad) ~= 0 and ImGui.Shortcut(ImGuiKey.GamepadFaceRight, ImGuiInputFlags.None, id)
local is_cancel_with_mouse = ImGui.IsMouseReleased(ImGuiMouseButton.Right, id)
if is_cancel_with_mouse then
ImGui.SetKeyOwner(ImGuiKey.MouseRight, id)
end
return is_cancel_with_keyboard or is_cancel_with_gamepad or is_cancel_with_mouse
end
--- @param id ImGuiID
--- @param data_type ImGuiDataType
--- @param v number
@@ -3311,6 +3349,13 @@ function ImGui.DragBehavior(id, data_type, v, v_speed, min, max, format, flags)
elseif (g.ActiveIdSource == ImGuiInputSource.Keyboard or g.ActiveIdSource == ImGuiInputSource.Gamepad) and g.NavActivatePressedId == id and not g.ActiveIdIsJustActivated then
ImGui.ClearActiveID()
end
if ShortcutsForCancel(id) and g.ActiveId == id then
-- Canceling action reverts to initial value
local value_changed = (v ~= g.ActiveIdValueOnActivation)
v = g.ActiveIdValueOnActivation
ImGui.ClearActiveID()
return v, value_changed
end
end
if g.ActiveId ~= id then
return v, false
@@ -3869,9 +3914,7 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
local set_new_value = false
local clicked_t = 0.0
if g.ActiveIdSource == ImGuiInputSource.Mouse then
if not g.IO.MouseDown[0] then
ImGui.ClearActiveID()
else
do
local mouse_abs_pos = g.IO.MousePos[axis_k]
if g.ActiveIdIsJustActivated then
local grab_t = ImGui.ScaleRatioFromValueT(data_type, v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize)
@@ -3891,11 +3934,6 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
set_new_value = true
end
elseif g.ActiveIdSource == ImGuiInputSource.Keyboard or g.ActiveIdSource == ImGuiInputSource.Gamepad then
if g.ActiveIdIsJustActivated then
g.SliderCurrentAccum = 0.0 -- Reset any stored nav delta upon activation
g.SliderCurrentAccumDirty = false
end
local input_delta = (axis == ImGuiAxis.X) and ImGui.GetNavTweakPressedAmount(axis) or -ImGui.GetNavTweakPressedAmount(axis)
if input_delta ~= 0.0 then
local tweak_slow = ImGui.IsKeyDown((g.NavInputSource == ImGuiInputSource.Gamepad) and ImGuiKey.NavGamepadTweakSlow or ImGuiKey.NavKeyboardTweakSlow)
@@ -3922,9 +3960,7 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
end
local delta = g.SliderCurrentAccum
if g.NavActivatePressedId == id and not g.ActiveIdIsJustActivated then
ImGui.ClearActiveID()
elseif g.SliderCurrentAccumDirty then
if g.SliderCurrentAccumDirty then
clicked_t = ImGui.ScaleRatioFromValueT(data_type, v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize)
if (clicked_t >= 1.0 and delta > 0.0) or (clicked_t <= 0.0 and delta < 0.0) then -- This is to avoid applying the saturation when already past the limits
@@ -4016,6 +4052,30 @@ function ImGui.SliderBehavior(bb, id, data_type, v, min, max, format, flags, out
IM_ASSERT((flags == 1 or bit32.band(flags, ImGuiSliderFlags.InvalidMask_) == 0), "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.")
IM_ASSERT(bit32.band(flags, ImGuiSliderFlags.WrapAround) == 0) -- Not supported by SliderXXX(), only by DragXXX()
-- Things we can do easily outside the SliderBehaviorT<> template, saves code generation.
local g = GImGui
if g.ActiveId == id then
if g.ActiveIdSource == ImGuiInputSource.Mouse and not g.IO.MouseDown[0] then
ImGui.ClearActiveID()
end
if g.ActiveIdSource == ImGuiInputSource.Keyboard or g.ActiveIdSource == ImGuiInputSource.Gamepad then
if g.ActiveIdIsJustActivated then
g.SliderCurrentAccum = 0.0 -- Reset any stored nav delta upon activation
g.SliderCurrentAccumDirty = false
end
if g.NavActivatePressedId == id and not g.ActiveIdIsJustActivated then
ImGui.ClearActiveID()
end
end
if ShortcutsForCancel(id) and g.ActiveId == id then
-- Canceling action reverts to initial value
local value_changed = (v ~= g.ActiveIdValueOnActivation)
v = g.ActiveIdValueOnActivation
ImGui.ClearActiveID()
return v, value_changed
end
end
if data_type == ImGuiDataType.S8 then
local v32 = (ImS32)((ImS8)(v)); local r
v32, r = ImGui.SliderBehaviorT(ImS32, ImS32, bb, id, ImGuiDataType.S32, v32, min, max, format, flags, out_grab_bb)
@@ -4602,7 +4662,7 @@ end
--- @param obj ImGuiInputTextState
--- @param idx int
local function STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx)
function ImStb.TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx)
local len = obj.TextLen
idx = ImStb.TEXTEDIT_GETNEXTCHARINDEX(obj, idx)
while idx <= len and not ImStb.is_word_boundary_from_left(obj, idx) do
@@ -4613,7 +4673,7 @@ end
--- @param obj ImGuiInputTextState
--- @param idx int
local function STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx)
function ImStb.TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx)
idx = ImStb.TEXTEDIT_GETNEXTCHARINDEX(obj, idx)
local len = obj.TextLen
while idx <= len and not ImStb.is_word_boundary_from_right(obj, idx) do
@@ -4627,9 +4687,9 @@ end
function ImStb.TEXTEDIT_MOVEWORDRIGHT(obj, idx)
local g = obj.Ctx
if g.IO.ConfigMacOSXBehaviors then
return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx)
return ImStb.TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx)
else
return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx)
return ImStb.TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx)
end
end
@@ -4830,6 +4890,13 @@ function MT.ImGuiInputTextState:CursorClamp()
end
function MT.ImGuiInputTextState:HasSelection() return self.Stb.select_start ~= self.Stb.select_end end
function MT.ImGuiInputTextState:ClearSelection() self.Stb.select_start = self.Stb.cursor; self.Stb.select_end = self.Stb.cursor end
-- [Luau] positions are 1-based byte indices (upstream: 0-based)
function MT.ImGuiInputTextState:GetCursorPos() return self.Stb.cursor end
function MT.ImGuiInputTextState:GetSelectionStart() return self.Stb.select_start end
function MT.ImGuiInputTextState:GetSelectionEnd() return self.Stb.select_end end
function MT.ImGuiInputTextState:SetSelection(start, _end) self.Stb.select_start = start; self.Stb.cursor = _end; self.Stb.select_end = _end end
function MT.ImGuiInputTextState:GetPreferredOffsetX() if self.Stb.has_preferred_x then return self.Stb.preferred_x else return -1 end end
function MT.ImGuiInputTextState:SelectAll()
self.Stb.select_start = 1
@@ -4838,6 +4905,10 @@ function MT.ImGuiInputTextState:SelectAll()
self.Stb.has_preferred_x = false
end
function MT.ImGuiInputTextState:ReloadUserBufAndSelectAll() self.WantReloadUserBuf = true; self.ReloadSelectionStart = 1; self.ReloadSelectionEnd = INT_MAX end
function MT.ImGuiInputTextState:ReloadUserBufAndKeepSelection() self.WantReloadUserBuf = true; self.ReloadSelectionStart = self.Stb.select_start; self.ReloadSelectionEnd = self.Stb.select_end end
function MT.ImGuiInputTextState:ReloadUserBufAndMoveToEnd() self.WantReloadUserBuf = true; self.ReloadSelectionStart = INT_MAX; self.ReloadSelectionEnd = INT_MAX end
end
function ImGui.PushPasswordFont()
@@ -4989,9 +5060,9 @@ local function InputTextFilterCharacter(ctx, state, char, callback, user_data, i
callback_data.EventFlag = ImGuiInputTextFlags.CallbackCharFilter
callback_data.EventChar = c
callback_data.EventActivated = (state ~= nil and g.ActiveId == state.ID and g.ActiveIdIsJustActivated)
callback_data.CursorPos = state.Stb.cursor
callback_data.SelectionStart = state.Stb.select_start
callback_data.SelectionEnd = state.Stb.select_end
callback_data.CursorPos = state.Stb.cursor - 1 -- [Luau] callback positions are 0-based
callback_data.SelectionStart = state.Stb.select_start - 1
callback_data.SelectionEnd = state.Stb.select_end - 1
callback_data.UserData = user_data
if callback(callback_data) ~= 0 then
return char, false
@@ -5573,7 +5644,7 @@ function ImGui.InputTextEx(label, hint, buf, buf_size, size_arg, flags, callback
if not stbte.HAS_SELECTION(state.Stb) then
stbte.prep_selection_at_cursor(state.Stb)
end
state.Stb.cursor = STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state.Stb.cursor)
state.Stb.cursor = ImStb.TEXTEDIT_MOVEWORDRIGHT_MAC(state, state.Stb.cursor)
state.Stb.select_end = state.Stb.cursor
stbte.clamp(state, state.Stb)
else
@@ -5902,9 +5973,9 @@ function ImGui.InputTextEx(label, hint, buf, buf_size, size_arg, flags, callback
callback_data.BufTextLen = state.TextLen
callback_data.BufSize = state.BufCapacity
callback_data.BufDirty = false
callback_data.CursorPos = state.Stb.cursor
callback_data.SelectionStart = state.Stb.select_start
callback_data.SelectionEnd = state.Stb.select_end
callback_data.CursorPos = state.Stb.cursor - 1 -- [Luau] callback positions are 0-based, stb ones 1-based
callback_data.SelectionStart = state.Stb.select_start - 1
callback_data.SelectionEnd = state.Stb.select_end - 1
-- Call user code
callback(callback_data)
@@ -5914,12 +5985,12 @@ function ImGui.InputTextEx(label, hint, buf, buf_size, size_arg, flags, callback
IM_ASSERT(callback_data.Buf == callback_buf) -- Invalid to modify those fields
IM_ASSERT(callback_data.BufSize == state.BufCapacity)
IM_ASSERT(callback_data.Flags == flags)
if callback_data.BufDirty or callback_data.CursorPos ~= state.Stb.cursor then
if callback_data.BufDirty or callback_data.CursorPos ~= state.Stb.cursor - 1 then
state.CursorFollow = true
end
state.Stb.cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen)
state.Stb.select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen)
state.Stb.select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen)
state.Stb.cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen) + 1
state.Stb.select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen) + 1
state.Stb.select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen) + 1
if callback_data.BufDirty then
-- Callback may update buffer and thus set buf_dirty even in read-only mode.
IM_ASSERT(callback_data.BufTextLen == ImStd.ImStrlen(callback_data.Buf)) -- You need to maintain BufTextLen if you change the text!
@@ -6283,6 +6354,47 @@ function ImGui.InputTextWithHint(label, hint, buf, buf_size, flags, callback, us
return ImGui.InputTextEx(label, hint, buf, buf_size, ImVec2(0, 0), flags, callback, user_data)
end
-- [Luau] stb positions/undo indices are 1-based in this port (displayed as-is)
--- @param state ImGuiInputTextState
function ImGui.DebugNodeInputTextState(state)
local g = GImGui
local stb_state = state.Stb
local undo_state = stb_state.undostate
ImGui.Text("ID: 0x%08X, ActiveID: 0x%08X", state.ID, g.ActiveId)
if ImGui.DebugLocateItemOnHover then ImGui.DebugLocateItemOnHover(state.ID) end -- [Luau] not ported yet
local wrap_dir = ""
if bit32.band(state.Flags, ImGuiInputTextFlags.WordWrap) ~= 0 then
if state.LastMoveDirectionLR == ImGuiDir.Left then wrap_dir = " (L)" else wrap_dir = " (R)" end
end
ImGui.Text("TextLen: %d, Cursor: %d%s, Selection: %d..%d", state.TextLen, stb_state.cursor, wrap_dir, stb_state.select_start, stb_state.select_end)
ImGui.Text("BufCapacity: %d, LineCount: %d", state.BufCapacity, state.LineCount)
ImGui.Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state.TextA.Size, state.TextA.Capacity)
ImGui.Text("has_preferred_x: %d (%.2f)", stb_state.has_preferred_x and 1 or 0, stb_state.preferred_x)
ImGui.Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state.undo_point, undo_state.redo_point, undo_state.undo_char_point, undo_state.redo_char_point)
if ImGui.BeginChild("undopoints", ImVec2(0.0, ImGui.GetTextLineHeight() * 10), bit32.bor(ImGuiChildFlags.Borders, ImGuiChildFlags.ResizeY)) then -- Visualize undo state
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImVec2(0, 0))
for n = 1, IMSTB_TEXTEDIT_UNDOSTATECOUNT do
local undo_rec = undo_state.undo_rec[n]
local undo_rec_type = (n < undo_state.undo_point) and "u" or ((n >= undo_state.redo_point) and "r" or " ")
if undo_rec_type == " " then
ImGui.BeginDisabled()
end
local buf_preview_len = (undo_rec_type ~= " " and undo_rec.char_storage ~= -1) and undo_rec.insert_length or 0
local buf_preview_str = ""
if buf_preview_len > 0 then
buf_preview_str = string.char(unpack(undo_state.undo_char, undo_rec.char_storage, undo_rec.char_storage + buf_preview_len - 1))
end
ImGui.Text("%s [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"",
undo_rec_type, n, undo_rec.where, undo_rec.insert_length, undo_rec.delete_length, undo_rec.char_storage, buf_preview_str)
if undo_rec_type == " " then
ImGui.EndDisabled()
end
end
ImGui.PopStyleVar()
end
ImGui.EndChild()
end
-------------------------------------------------------------------------------
-- [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
-------------------------------------------------------------------------------
@@ -10648,14 +10760,37 @@ local function TabItemGetSectionIdx(tab)
return 1
end
-- [Luau] comparers return a boolean "less than" for table.sort (upstream: ImQsort int comparers)
local function TabItemComparerBySection(a, b)
if a == nil or b == nil then return false end
local a_section = TabItemGetSectionIdx(a)
local b_section = TabItemGetSectionIdx(b)
if a_section ~= b_section then
return a_section < b_section
end
return a.IndexDuringLayout < b.IndexDuringLayout
end
local function TabItemComparerByBeginOrder(a, b)
return a.BeginOrder < b.BeginOrder
end
local function GetTabBarFromTabBarRef(ref)
local g = GImGui
if ref.Ptr then return ref.Ptr else return g.TabBars:GetByIndex(ref.Index) end
end
local function GetTabBarRefFromTabBar(tab_bar)
local g = GImGui
if g.TabBars:Contains(tab_bar) then
return ImGuiPtrOrIndex(g.TabBars:GetIndex(tab_bar))
end
return ImGuiPtrOrIndex(tab_bar)
end
-- Stable sorts (C++ ImQsort is not stable, but the comparers are total orders in practice)
local function SortTabsBySection(tab_bar)
table.sort(tab_bar.Tabs.Data, function(a, b)
if a == nil or b == nil then return false end
local sa, sb = TabItemGetSectionIdx(a), TabItemGetSectionIdx(b)
if sa ~= sb then return sa < sb end
return a.IndexDuringLayout < b.IndexDuringLayout
end)
table.sort(tab_bar.Tabs.Data, TabItemComparerBySection)
end
local function SortTabsByBeginOrder(tab_bar)
@@ -10663,7 +10798,7 @@ local function SortTabsByBeginOrder(tab_bar)
if not data then return end
local arr = {}
for i = 1, tab_bar.Tabs.Size do arr[i] = data[i] end
table.sort(arr, function(a, b) return a.BeginOrder < b.BeginOrder end)
table.sort(arr, TabItemComparerByBeginOrder)
for i = 1, tab_bar.Tabs.Size do data[i] = arr[i] end
end
@@ -10677,6 +10812,12 @@ end
-- Spacing: Horizontal spacing at the end of the section.
local function ImGuiTabBarSection() return { TabCount = 0, Width = 0.0, WidthAfterShrinkMinWidth = 0.0, Spacing = 0.0 } end
-- [Luau] boolean "less than" for table.sort. Width compared exactly: upstream's (int) truncation isn't a strict weak order, which table.sort rejects
local function ShrinkWidthItemComparer(a, b)
if a.Width ~= b.Width then return a.Width > b.Width end
return a.Index > b.Index -- b->Index - a->Index
end
-- Shrink excess width from a set of item, by removing width from the larger items first.
-- Set items Width to -1.0f to disable shrinking this item.
-- items: 1-based array of ImGuiShrinkWidthItem, `first` = 1-based index of first item, `count` items
@@ -10690,10 +10831,7 @@ function ImGui.ShrinkWidths(items, first, count, width_excess, width_min)
-- Sort largest first, smallest last.
local slice = {}
for n = 1, count do slice[n] = items[first + n - 1] end
table.sort(slice, function(a, b)
if a.Width ~= b.Width then return a.Width > b.Width end
return a.Index > b.Index -- ShrinkWidthItemComparer: b->Index - a->Index (Width compared exactly: upstream's (int) truncation isn't a strict weak order, which table.sort rejects)
end)
table.sort(slice, ShrinkWidthItemComparer)
for n = 1, count do items[first + n - 1] = slice[n] end
local it = slice
local count_same_width = 1
@@ -10804,7 +10942,7 @@ function ImGui.BeginTabBarEx(tab_bar, tab_bar_bb, flags)
end
-- Add to stack
g.CurrentTabBarStack:push_back(tab_bar)
g.CurrentTabBarStack:push_back(GetTabBarRefFromTabBar(tab_bar))
g.CurrentTabBar = tab_bar
tab_bar.Window = window
@@ -10886,7 +11024,7 @@ function ImGui.EndTabBar()
end
g.CurrentTabBarStack:pop_back()
if g.CurrentTabBarStack.Size == 0 then g.CurrentTabBar = nil else g.CurrentTabBar = g.CurrentTabBarStack.Data[g.CurrentTabBarStack.Size] end
if g.CurrentTabBarStack.Size == 0 then g.CurrentTabBar = nil else g.CurrentTabBar = GetTabBarFromTabBarRef(g.CurrentTabBarStack:back()) end
end
-- This is called only once a frame before by the first call to ItemTab()
+2
View File
@@ -13,6 +13,8 @@ CHECKS=(
"readme-min|README OK|MINIFY=1 $R --main tools/test/scripts/t_readme.luau --frames 30"
"cached|CACHED OK|$R --main tools/test/scripts/t_cached_window.luau --frames 130"
"fixes|FIXES OK|$R --main tools/test/scripts/t_fixes.luau --frames 55"
"debugtools|DEBUGTOOLS OK|$R --main tools/test/scripts/t_debugtools.luau --frames 65"
"helpers|HELPERS OK|$R --main tools/test/scripts/t_helpers.luau --frames 35"
"multiselect|MULTISELECT OK|$R --main tools/test/scripts/t_multiselect.luau --frames 380"
"nodemo|NODEMO OK|NO_DEMO=1 $R --main tools/test/scripts/t_nodemo.luau --frames 25"
"widgets|OK|$R --main tools/test/scripts/t_widgets.luau --frames 80"
+79
View File
@@ -0,0 +1,79 @@
-- Debug tools + misc imgui.cpp ports: item picker, DebugFlashStyleColor, DebugLocateItem, context hooks, AddInputCharactersUTF8, ImBezierCubicClosestPoint
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
local ctx = ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- ImBezierCubicClosestPoint: straight-line cubic (0,0)-(30,0)-(60,0)-(90,0): closest point to (45,10) is (45,0)
local p1, p2, p3, p4 = ImVec2(0, 0), ImVec2(30, 0), ImVec2(60, 0), ImVec2(90, 0)
local cp = G.ImBezierCubicClosestPoint(p1, p2, p3, p4, ImVec2(45, 10), 20)
check(math.abs(cp.x - 45) < 1e-3 and math.abs(cp.y) < 1e-6, ("ImBezierCubicClosestPoint %g,%g"):format(cp.x, cp.y))
-- Curved: result must lie on the curve (distance to a dense sampling of the curve ~0) and beat the endpoints
local q1, q2, q3, q4 = ImVec2(0, 0), ImVec2(0, 100), ImVec2(100, 100), ImVec2(100, 0)
local target = ImVec2(50, 120)
for _, r in { G.ImBezierCubicClosestPoint(q1, q2, q3, q4, target, 64), G.ImBezierCubicClosestPointCasteljau(q1, q2, q3, q4, target, 0.25) } do
check(math.abs(r.x - 50) < 0.5 and math.abs(r.y - 75) < 0.5, ("closest point on curve %g,%g (expected ~50,75)"):format(r.x, r.y))
end
-- AddInputCharactersUTF8 queues one Text event per codepoint
io:AddInputCharactersUTF8("a\u{e9}\u{20ac}\u{1F600}")
local chars = {}
for _, e in ctx.InputEventsQueue.Data do if e.Type == G.ImGuiInputEventType.Text then table.insert(chars, e.Text.Char) end end
-- (this build has IM_UNICODE_CODEPOINT_MAX = 0xFFFF like upstream's default ImWchar16: U+1F600 becomes IM_UNICODE_CODEPOINT_INVALID)
check(#chars == 4 and chars[1] == 0x61 and chars[2] == 0xE9 and chars[3] == 0x20AC and chars[4] == ((G.IM_UNICODE_CODEPOINT_MAX >= 0x1F600) and 0x1F600 or 0xFFFD), "AddInputCharactersUTF8 " .. table.concat(chars, ","))
-- Context hooks
local log = {}
local function hook(t, name)
local h = G.ImGuiContextHook(); h.Type = t; h.Callback = function(c, hk) table.insert(log, name) end
return ImGui.AddContextHook(ctx, h)
end
local T = G.ImGuiContextHookType
hook(T.NewFramePre, "nfpre"); hook(T.NewFramePost, "nfpost"); hook(T.RenderPre, "rpre"); local rpost = hook(T.RenderPost, "rpost")
-- Style color flash
local col = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
local orig = { col.x, col.y, col.z, col.w }
local f, rect, picked, broke, button_id = 0, nil, nil, 0, 0
ctx.DebugBreakCallback = function() broke += 1 end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
if rect and f == 5 then io:AddMousePosEvent((rect[1].x + rect[2].x) / 2, (rect[1].y + rect[2].y) / 2) end
if f == 6 then ImGui.DebugStartItemPicker() end
if f == 8 then io:AddMouseButtonEvent(0, true) elseif f == 9 then io:AddMouseButtonEvent(0, false) end
ImGui.NewFrame()
if f == 1 then check(table.concat(log, " ") == "nfpre nfpost", "hooks after NewFrame: " .. table.concat(log, " ")) end
if f == 8 then picked = ctx.DebugItemPickerBreakId end
if f == 3 then ImGui.DebugFlashStyleColor(G.ImGuiCol.Button) end
ImGui.SetNextWindowPos(ImVec2(10, 10), G.ImGuiCond.Always)
ImGui.Begin("DebugTools")
ImGui.Button("Target")
if f == 2 then local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); rect = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
local target_id = ctx.LastItemData.ID
button_id = target_id
if f == 12 then ImGui.DebugLocateItem(target_id) end
if f == 13 then check(ctx.DebugLocateId == 0, "DebugLocateItem resolved by ItemAdd()") end
ImGui.DebugDrawItemRect(); ImGui.DebugDrawCursorPos(); ImGui.DebugDrawLineExtents()
ImGui.DebugTextUnformattedWithLocateItem(("id 0x%08X here"):format(target_id))
ImGui.End()
ImGui.SetNextWindowPos(ImVec2(400, 10), G.ImGuiCond.Always); ImGui.ShowMetricsWindow()
ImGui.SetNextWindowPos(ImVec2(400, 300), G.ImGuiCond.Always); ImGui.ShowDebugLogWindow()
if f == 4 then
local c = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
check(c.w == 1.0 and ctx.DebugFlashStyleColorIdx == G.ImGuiCol.Button, "flash active")
end
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 1 then check(table.concat(log, " ") == "nfpre nfpost rpre rpost", "hooks after Render: " .. table.concat(log, " ")); ImGui.RemoveContextHook(ctx, rpost) end
if f == 2 then check(table.concat(log, " ") == "nfpre nfpost rpre rpost nfpre nfpost rpre", "RemoveContextHook: " .. table.concat(log, " ")) end
if f == 60 then
local c = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
check(c.x == orig[1] and c.y == orig[2] and c.z == orig[3] and c.w == orig[4] and ctx.DebugFlashStyleColorIdx == nil, "flash restored color exactly")
check(picked ~= nil and picked ~= 0 and picked == button_id, ("item picker set DebugItemPickerBreakId (%s vs %s)"):format(tostring(picked), tostring(button_id)))
check(not ctx.DebugItemPickerActive and broke >= 1, ("item picker break hook called (%d)"):format(broke))
if #failed == 0 then print("DEBUGTOOLS OK") end
end
end)
+142
View File
@@ -0,0 +1,142 @@
-- Small upstream helpers: ImGuiInputTextState selection API + ReloadUserBuf*, ImGuiInputTextCallbackData:SetSelection, ImDrawList AddImageQuad/PrimQuadUV/
-- CloneOutput, ImDrawData DeIndexAllBuffers/ScaleClipRects, ImBitVector/ImBitArray*, ImGuiPackedDate, ImParseFormatSanitizeForPrinting, drag cancel (ShortcutsForCancel)
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- Pure helpers
check(G.ImParseFormatSanitizeForPrinting("%.3f%%", nil, 32) == "%.3f", "sanitize %.3f%%")
check(G.ImParseFormatSanitizeForPrinting("%'_5.2f units", nil, 32) == "%5.2f", "sanitize stb flags: " .. G.ImParseFormatSanitizeForPrinting("%'_5.2f units", nil, 32))
local d = G.ImGuiPackedDate(20260315)
check(d:IsValid() and d:Unpack() == 20260315, "PackedDate round trip " .. d:Unpack())
d:SubtractMonths(3)
check(d:Unpack() == 20251215, "PackedDate SubtractMonths " .. d:Unpack())
check(not G.ImGuiPackedDate():IsValid() and G.ImGuiPackedDate():Unpack() == 0, "PackedDate empty")
local bv = G.ImBitVector()
bv:Create(70)
check(bv.Storage.Size == 3, "ImBitVector size")
bv:SetBit(1); bv:SetBit(70); bv:SetBit(33)
check(bv:TestBit(1) and bv:TestBit(70) and bv:TestBit(33) and not bv:TestBit(2) and not bv:TestBit(69), "ImBitVector SetBit/TestBit")
bv:ClearBit(33)
check(not bv:TestBit(33) and bv:TestBit(70), "ImBitVector ClearBit")
local arr = {}
G.ImBitArrayClearAllBits(arr, 96)
G.ImBitArraySetBitRange(arr, 3, 70) -- [3..70)
local ok_range = true
for n = 1, 96 do if G.ImBitArrayTestBit(arr, n) ~= (n >= 3 and n < 70) then ok_range = false end end
check(ok_range, "ImBitArraySetBitRange")
check(G.ImBitArrayGetStorageSizeInBytes(70) == 12, "ImBitArrayGetStorageSizeInBytes")
check(G.ImModPositive(-1, 5) == 4 and G.ImRoundSigned64(-1.5) == -2 and G.ImCharIsXdigitA(string.byte("F")), "math helpers")
local buf = ImGui.NewTextBuffer("hello world", 64)
local cbuf = ImGui.NewTextBuffer("abcdef", 64)
local cb_calls = 0
local function cb(data)
cb_calls += 1
if cb_calls == 1 then data:SetSelection(1, 3) end
return 0
end
local df, rect_df = 10.0, nil
local f = 0
local function center(r) return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
local function vtx(dl, i) -- 1-based vertex
local b, o = dl.VtxBuffer.Buf, (i - 1) * G.VTX_STRIDE
return buffer.readf64(b, o), buffer.readf64(b, o + 8), buffer.readf64(b, o + 16), buffer.readf64(b, o + 24), buffer.readu32(b, o + 32)
end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
-- drag + Escape cancels and reverts (ShortcutsForCancel)
if f == 20 then io:AddMousePosEvent(center(rect_df)) end
if f == 21 then io:AddMouseButtonEvent(0, true) end
if f == 22 then local x, y = center(rect_df); io:AddMousePosEvent(x + 40, y) end
if f == 24 then io:AddKeyEvent(G.ImGuiKey.Escape, true) end
if f == 25 then io:AddKeyEvent(G.ImGuiKey.Escape, false) end
if f == 26 then io:AddMouseButtonEvent(0, false) end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(400, 300))
ImGui.Begin("H")
if f == 2 then ImGui.SetKeyboardFocusHere() end
ImGui.InputText("##t", buf, 64)
local id = ImGui.GetItemID()
local st = ImGui.GetInputTextState(id)
if f == 5 then
check(st ~= nil, "InputText active")
if st then
st:SetSelection(3, 6)
check(st:GetSelectionStart() == 3 and st:GetSelectionEnd() == 6 and st:GetCursorPos() == 6 and st:HasSelection(), "SetSelection/Get*")
st:ClearSelection()
check(st:GetSelectionStart() == 6 and st:GetSelectionEnd() == 6 and not st:HasSelection(), "ClearSelection")
check(st:GetPreferredOffsetX() == -1, "GetPreferredOffsetX")
-- modify the user buffer externally then reload
local s = "bye"; for i = 1, #s do buf[i] = string.byte(s, i) end; buf[#s + 1] = 0
st:ReloadUserBufAndSelectAll()
end
end
if f == 7 and st then
check(G.ImGui._ByteArrayToString(st:GetText(), 1, st.TextLen + 1) == "bye", "ReloadUserBufAndSelectAll text")
check(st:GetSelectionStart() == 1 and st:GetSelectionEnd() == 4, ("ReloadUserBufAndSelectAll sel %d..%d"):format(st:GetSelectionStart(), st:GetSelectionEnd()))
st:SetSelection(2, 3); st:ReloadUserBufAndKeepSelection()
end
if f == 9 and st then
check(st:GetSelectionStart() == 2 and st:GetSelectionEnd() == 3, "ReloadUserBufAndKeepSelection")
st:ReloadUserBufAndMoveToEnd()
end
if f == 11 and st then
check(st:GetCursorPos() == 4 and not st:HasSelection(), "ReloadUserBufAndMoveToEnd " .. st:GetCursorPos())
end
-- callback SetSelection (0-based callback positions -> 1-based stb)
if f == 12 then ImGui.SetKeyboardFocusHere() end
ImGui.InputText("##c", cbuf, 64, G.ImGuiInputTextFlags.CallbackAlways, cb)
if f == 15 then
local cst = ImGui.GetInputTextState(ImGui.GetItemID())
check(cst ~= nil and cb_calls > 0 and cst:GetSelectionStart() == 2 and cst:GetSelectionEnd() == 4, "callback SetSelection " .. (cst and (cst:GetSelectionStart() .. ".." .. cst:GetSelectionEnd()) or "nil"))
end
df = ImGui.DragFloat("##df", df, 1.0)
local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); rect_df = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) }
if f == 23 then check(df ~= 10.0, "drag changed value " .. df) end
if f == 25 then check(df == 10.0 and ImGui.GetActiveID() == 0, "Escape reverts drag " .. df) end
if f == 30 then
local dl = ImGui.GetWindowDrawList()
local v0, i0 = dl.VtxBuffer.Size, dl.IdxBuffer.Size
local tex = G.ImTextureRef(1234)
dl:AddImageQuad(tex, ImVec2(10, 10), ImVec2(50, 12), ImVec2(48, 60), ImVec2(8, 58), ImVec2(0, 0), ImVec2(1, 0), ImVec2(1, 1), ImVec2(0, 1), 0xFF00FF00)
check(dl.VtxBuffer.Size == v0 + 4 and dl.IdxBuffer.Size == i0 + 6, "AddImageQuad counts")
local x, y, u, v, col = vtx(dl, v0 + 3)
check(x == 48 and y == 60 and u == 1 and v == 1 and col == 0xFF00FF00, ("PrimQuadUV vertex %s %s %s %s %x"):format(x, y, u, v, col))
local idx = dl.IdxBuffer.Data
check(idx[i0 + 1] == v0 + 1 and idx[i0 + 5] == v0 + 3 and idx[i0 + 6] == v0 + 4, "PrimQuadUV indices")
local cmin, cmax = dl:GetClipRectMin(), dl:GetClipRectMax()
check(cmax.x > cmin.x and cmax.y > cmin.y, "GetClipRectMin/Max")
local clone = dl:CloneOutput()
check(clone.VtxBuffer.Size == dl.VtxBuffer.Size and clone.IdxBuffer.Size == dl.IdxBuffer.Size and clone.CmdBuffer.Size == dl.CmdBuffer.Size
and clone.VtxBuffer.Buf ~= dl.VtxBuffer.Buf and clone.CmdBuffer.Data[1] ~= dl.CmdBuffer.Data[1], "CloneOutput copies buffers")
local cx = vtx(clone, v0 + 3)
check(cx == 48, "CloneOutput vertex data")
local dd = G.ImDrawData()
dd.CmdLists:push_back(clone)
local n_idx = clone.IdxBuffer.Size
local ex, ey = vtx(clone, clone.IdxBuffer.Data[n_idx])
dd:DeIndexAllBuffers()
check(clone.IdxBuffer.Size == 0 and clone.VtxBuffer.Size == n_idx and dd.TotalVtxCount == n_idx and dd.TotalIdxCount == 0, "DeIndexAllBuffers counts")
local lx, ly = vtx(clone, n_idx)
check(lx == ex and ly == ey, "DeIndexAllBuffers vertex data")
local cr = clone.CmdBuffer.Data[1].ClipRect
local x1, y1, z1, w1 = cr.x, cr.y, cr.z, cr.w
dd:ScaleClipRects(ImVec2(2, 3))
local cr2 = clone.CmdBuffer.Data[1].ClipRect
check(cr2.x == x1 * 2 and cr2.y == y1 * 3 and cr2.z == z1 * 2 and cr2.w == w1 * 3 and dl.CmdBuffer.Data[1].ClipRect.z == z1, "ScaleClipRects")
end
ImGui.End()
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 32 and #failed == 0 then print("HELPERS OK") end
end)