WIP: tabs, docking, dragdrop, settings, logging, harness
This commit is contained in:
@@ -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"]]
|
||||||
+10821
-920
File diff suppressed because it is too large
Load Diff
+1728
-53
File diff suppressed because it is too large
Load Diff
+753
-836
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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
+175
-23
@@ -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
|
||||||
@@ -1992,8 +2028,124 @@ ImGuiTreeNodeFlags.DrawLinesMask_ = bitOr(ImGuiTreeNodeFlags.DrawLinesNone, ImGu
|
|||||||
--- @enum ImGuiTableFlags
|
--- @enum ImGuiTableFlags
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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)))
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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))
|
||||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||||
@@ -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
Reference in New Issue
Block a user