docking: runs end to end (dock/undock by drag, builder, ini round trip), test scripts

This commit is contained in:
4DBug
2026-10-08 05:43:00 -05:00
parent fee9a84902
commit 2381a232ee
17 changed files with 492 additions and 107 deletions
+1 -1
View File
@@ -1 +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"]] [[13, "imgui.lua"], [12966, "imgui_demo.lua"], [13859, "imgui_demo_2.lua"], [16747, "imgui_docking.lua"], [19875, "imgui_dragdrop.lua"], [20167, "imgui_draw.lua"], [25162, "imgui_h.lua"], [27316, "imgui_impl_roblox.lua"], [28072, "imgui_impl_roblox_fonts.lua"], [28850, "imgui_internal.lua"], [31999, "imgui_tables.lua"], [32046, "imgui_tabs.lua"], [33213, "imgui_widgets.lua"], [41303, "imstb_rectpack.lua"], [41749, "imstb_textedit.lua"], [42990, "imstb_truetype.lua"], [45458, "imstd_minstdio.lua"]]
+74 -18
View File
@@ -1010,8 +1010,8 @@ local function ApplyWindowSettings(window, settings)
window.AutoFitFramesX = 0; window.AutoFitFramesY = 0 window.AutoFitFramesX = 0; window.AutoFitFramesY = 0
end end
window.Collapsed = settings.Collapsed window.Collapsed = settings.Collapsed
-- window.DockId = settings.DockId window.DockId = settings.DockId or 0
-- window.DockOrder = settings.DockOrder window.DockOrder = settings.DockOrder or -1
SetWindowConditionAllowFlags(window, ImGuiCond.FirstUseEver, false) SetWindowConditionAllowFlags(window, ImGuiCond.FirstUseEver, false)
end end
@@ -1042,7 +1042,7 @@ local function InitOrLoadWindowSettings(window, settings)
window.SetWindowPosAllowFlags = bit32.bor(ImGuiCond.Always, ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing) window.SetWindowPosAllowFlags = bit32.bor(ImGuiCond.Always, ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing)
window.SetWindowSizeAllowFlags = window.SetWindowPosAllowFlags window.SetWindowSizeAllowFlags = window.SetWindowPosAllowFlags
window.SetWindowCollapsedAllowFlags = window.SetWindowPosAllowFlags window.SetWindowCollapsedAllowFlags = window.SetWindowPosAllowFlags
-- window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags
ApplyWindowSettings(window, settings) ApplyWindowSettings(window, settings)
if settings ~= nil then if settings ~= nil then
-- TODO: settings.LastUsedDate = g.SessionDate -- TODO: settings.LastUsedDate = g.SessionDate
@@ -5117,7 +5117,7 @@ local function RenderWindowOuterBorders(window)
RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0, window.WindowBorderSize)) RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0, window.WindowBorderSize))
end end
if g.Style.FrameBorderSize > 0 and (bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0) then if g.Style.FrameBorderSize > 0 and (bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0) and not window.DockIsActive then
local y = window.Pos.y + window.TitleBarHeight - 1 local y = window.Pos.y + window.TitleBarHeight - 1
window.DrawList:AddLineH(window.Pos.x + border_size * 0.5, window.Pos.x + window.Size.x - border_size * 0.5, y, border_col, g.Style.FrameBorderSize) window.DrawList:AddLineH(window.Pos.x + border_size * 0.5, window.Pos.x + window.Size.x - border_size * 0.5, y, border_col, g.Style.FrameBorderSize)
end end
@@ -5162,11 +5162,19 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
if bit32.band(bg_col, IM_COL32_A_MASK) ~= 0 then if bit32.band(bg_col, IM_COL32_A_MASK) ~= 0 then
local bg_rect = ImRect(window.Pos + ImVec2(0, window.TitleBarHeight), window.Pos + window.Size) local bg_rect = ImRect(window.Pos + ImVec2(0, window.TitleBarHeight), window.Pos + window.Size)
local bg_rounding_flags = (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and ImDrawFlags.RoundCornersAll or ImDrawFlags.RoundCornersBottom local bg_rounding_flags
local bg_draw_list = window.DrawList if window.DockIsActive then
bg_rounding_flags = ImGui.CalcRoundingFlagsForRectInRect(bg_rect, window.DockNode.HostWindow:Rect(), 0.0)
else
bg_rounding_flags = (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and ImDrawFlags.RoundCornersAll or ImDrawFlags.RoundCornersBottom
end
local bg_draw_list = window.DockIsActive and window.DockNode.HostWindow.DrawList or window.DrawList
if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(0) end
bg_draw_list:AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags) bg_draw_list:AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags)
if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(1) end
end end
end end
if window.DockIsActive then window.DockNode.IsBgDrawnThisFrame = true end
-- Title bar -- Title bar
if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and not window.DockIsActive then if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and not window.DockIsActive then
@@ -5184,6 +5192,25 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
end end
end end
-- Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
local node = window.DockNode
if window.DockIsActive and node:IsHiddenTabBar() and not node:IsNoTabBar() then
local unhide_sz_draw = ImTrunc(g.FontSize * 0.70)
local unhide_sz_hit = ImTrunc(g.FontSize * 0.55)
local p = node.Pos
local r = ImRect(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit))
local unhide_id = window:GetID("#UNHIDE")
ImGui.KeepAliveID(unhide_id)
local pressed, hovered, held = ImGui.ButtonBehavior(r, unhide_id, ImGuiButtonFlags.FlattenChildren)
if pressed then
node.WantHiddenTabBarToggle = true
elseif held and ImGui.IsMouseDragging(0) then
ImGui.StartMouseMovingWindowOrNode(window, node, true)
end
local col = ImGui.GetColorU32(((held and hovered) or (node.IsFocused and not hovered)) and ImGuiCol.ButtonActive or (hovered and ImGuiCol.ButtonHovered or ImGuiCol.Button))
window.DrawList:AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0), p + ImVec2(0.0, unhide_sz_draw), col)
end
if window.ScrollbarX then if window.ScrollbarX then
ImGui.Scrollbar(ImGuiAxis.X) ImGui.Scrollbar(ImGuiAxis.X)
end end
@@ -5212,13 +5239,14 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
end end
end end
if handle_borders_and_resize_grips then if handle_borders_and_resize_grips and not window.DockNodeAsHost then
RenderWindowOuterBorders(window) RenderWindowOuterBorders(window)
end end
end end
window.DC.NavLayerCurrent = ImGuiNavLayer.Main window.DC.NavLayerCurrent = ImGuiNavLayer.Main
end end
ImGui.RenderWindowOuterBorders = RenderWindowOuterBorders
--- @param window ImGuiWindow --- @param window ImGuiWindow
--- @param title_bar_rect ImRect --- @param title_bar_rect ImRect
@@ -6068,6 +6096,11 @@ end
--- @param popup_flags ImGuiPopupFlags --- @param popup_flags ImGuiPopupFlags
function ImGui.IsPopupOpen(id, popup_flags) function ImGui.IsPopupOpen(id, popup_flags)
local g = GImGui local g = GImGui
if popup_flags == nil then popup_flags = 0 end
if type(id) == "string" then
-- str_id overload: with AnyPopupId the id must be 0, otherwise it is hashed with the current window's ID stack
id = (bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0) and 0 or g.CurrentWindow:GetID(id)
end
if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0 then if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0 then
-- Return true if any popup is open at the current BeginPopup() level of the popup stack -- Return true if any popup is open at the current BeginPopup() level of the popup stack
@@ -7986,7 +8019,9 @@ local function AddWindowToDrawData(window, layer)
local g = GImGui local g = GImGui
local viewport = window.Viewport local viewport = window.Viewport
g.IO.MetricsRenderWindows = g.IO.MetricsRenderWindows + 1 g.IO.MetricsRenderWindows = g.IO.MetricsRenderWindows + 1
-- TODO: splitter if window.DrawList._Splitter._Count > 1 then
window.DrawList:ChannelsMerge() -- Merge if user forgot to merge back. Also required for DockNodeHost windows.
end
ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[layer], window.DrawList) ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[layer], window.DrawList)
for _, child in window.DC.ChildWindows:iter() do for _, child in window.DC.ChildWindows:iter() do
if (ImGui.IsWindowActiveAndVisible(child)) then -- Clipped children may have been marked not active if (ImGui.IsWindowActiveAndVisible(child)) then -- Clipped children may have been marked not active
@@ -17305,6 +17340,7 @@ local function FixLargeWindowsWhenUndocking(size, ref_viewport)
end end
function ImGui.DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_ref) function ImGui.DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_ref)
if clear_persistent_docking_ref == nil then clear_persistent_docking_ref = true end -- C++ default argument
if window.DockNode then if window.DockNode then
ImGui.DockNodeRemoveWindow(window.DockNode, window, clear_persistent_docking_ref and 0 or window.DockId) ImGui.DockNodeRemoveWindow(window.DockNode, window, clear_persistent_docking_ref and 0 or window.DockId)
else else
@@ -18291,7 +18327,7 @@ function ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, dir,
local g = ImGui.GetCurrentContext() local g = ImGui.GetCurrentContext()
local dock_spacing = g.Style.ItemInnerSpacing.x local dock_spacing = g.Style.ItemInnerSpacing.x
local axis = (dir == ImGuiDir.Left or dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y local axis = (dir == ImGuiDir.Left or dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y
local a, o = axis + 1, bit32.bxor(axis, 1) + 1 local a, o = axis, 3 - axis -- the port's ImGuiAxis is 1-based (X=1,Y=2): it is directly the ImVec2 index
pos_new[o] = pos_old[o] pos_new[o] = pos_old[o]
size_new[o] = size_old[o] size_new[o] = size_old[o]
@@ -18435,7 +18471,7 @@ function ImGui.DockNodePreviewDockSetup(host_window, host_node, payload_window,
local size_new, size_old = ImVec2(0, 0), ImVec2(data.FutureNode.Size.x, data.FutureNode.Size.y) local size_new, size_old = ImVec2(0, 0), ImVec2(data.FutureNode.Size.x, data.FutureNode.Size.y)
ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window.Size) ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window.Size)
local split_ratio = ImSaturate(size_new[split_axis + 1] / data.FutureNode.Size[split_axis + 1]) local split_ratio = ImSaturate(size_new[split_axis] / data.FutureNode.Size[split_axis])
data.FutureNode.Pos = pos_new data.FutureNode.Pos = pos_new
data.FutureNode.Size = size_new data.FutureNode.Size = size_new
data.SplitRatio = (split_dir == ImGuiDir.Right or split_dir == ImGuiDir.Down) and (1.0 - split_ratio) or split_ratio data.SplitRatio = (split_dir == ImGuiDir.Right or split_dir == ImGuiDir.Down) and (1.0 - split_ratio) or split_ratio
@@ -18523,7 +18559,7 @@ function ImGui.DockNodePreviewDockRender(host_window, host_node, root_payload, d
local cc = draw_r_in:GetCenter() local cc = draw_r_in:GetCenter()
local center = ImVec2(math.floor(cc.x), math.floor(cc.y)) local center = ImVec2(math.floor(cc.x), math.floor(cc.y))
dl:AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding) dl:AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding)
dl:AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding, 0, thickness) dl:AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding, thickness)
if dir == ImGuiDir.Left or dir == ImGuiDir.Right then if dir == ImGuiDir.Left or dir == ImGuiDir.Right then
dl:AddLineV(center.x, draw_r_in.Min.y, draw_r_in.Max.y, overlay_col_lines, thickness) dl:AddLineV(center.x, draw_r_in.Min.y, draw_r_in.Max.y, overlay_col_lines, thickness)
end end
@@ -18562,7 +18598,7 @@ function ImGui.DockNodeTreeSplit(ctx, parent_node, split_axis, split_inheritor_c
parent_node.AuthorityForPos = ImGuiDataAuthority.DockNode parent_node.AuthorityForPos = ImGuiDataAuthority.DockNode
parent_node.AuthorityForSize = ImGuiDataAuthority.DockNode parent_node.AuthorityForSize = ImGuiDataAuthority.DockNode
local a = split_axis + 1 local a = split_axis
local size_avail = parent_node.Size[a] - g.Style.DockingSeparatorSize local size_avail = parent_node.Size[a] - g.Style.DockingSeparatorSize
size_avail = ImMax(size_avail, g.Style.WindowMinSize[a] * 2.0) size_avail = ImMax(size_avail, g.Style.WindowMinSize[a] * 2.0)
IM_ASSERT(size_avail > 0.0) IM_ASSERT(size_avail > 0.0)
@@ -18648,7 +18684,7 @@ function ImGui.DockNodeTreeUpdatePosSize(node, pos, size, only_write_to_single_n
if child_0_is_or_will_be_visible and child_1_is_or_will_be_visible then if child_0_is_or_will_be_visible and child_1_is_or_will_be_visible then
local spacing = g.Style.DockingSeparatorSize local spacing = g.Style.DockingSeparatorSize
local a = node.SplitAxis + 1 local a = node.SplitAxis
local size_avail = ImMax(size[a] - spacing, 0.0) local size_avail = ImMax(size[a] - spacing, 0.0)
local size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[a] * 2.0) * 0.5) local size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[a] * 2.0) * 0.5)
@@ -18717,7 +18753,7 @@ function ImGui.DockNodeTreeUpdateSplitter(node)
if child_0.IsVisible and child_1.IsVisible then if child_0.IsVisible and child_1.IsVisible then
local axis = node.SplitAxis local axis = node.SplitAxis
IM_ASSERT(axis ~= ImGuiAxis.None) IM_ASSERT(axis ~= ImGuiAxis.None)
local a, o = axis + 1, bit32.bxor(axis, 1) + 1 local a, o = axis, 3 - axis -- the port's ImGuiAxis is 1-based (X=1,Y=2): it is directly the ImVec2 index
local bb = ImRect(ImVec2(child_0.Pos.x, child_0.Pos.y), ImVec2(child_1.Pos.x, child_1.Pos.y)) local bb = ImRect(ImVec2(child_0.Pos.x, child_0.Pos.y), ImVec2(child_1.Pos.x, child_1.Pos.y))
bb.Min[a] = bb.Min[a] + child_0.Size[a] bb.Min[a] = bb.Min[a] + child_0.Size[a]
bb.Max[o] = bb.Max[o] + child_1.Size[o] bb.Max[o] = bb.Max[o] + child_1.Size[o]
@@ -19808,7 +19844,7 @@ function ImGui.SplitterBehavior(bb, id, axis, size1, size2, min_size1, min_size2
local bb_render = ImRect(bb.Min, bb.Max) local bb_render = ImRect(bb.Min, bb.Max)
if held then if held then
local d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min local d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min
local mouse_delta = d[axis + 1] local mouse_delta = d[axis]
local size_1_maximum_delta = ImMax(0.0, size1 - min_size1) local size_1_maximum_delta = ImMax(0.0, size1 - min_size1)
local size_2_maximum_delta = ImMax(0.0, size2 - min_size2) local size_2_maximum_delta = ImMax(0.0, size2 - min_size2)
@@ -20104,7 +20140,7 @@ end
function ImGui.RenderDragDropTargetRectEx(draw_list, bb, rounding) function ImGui.RenderDragDropTargetRectEx(draw_list, bb, rounding)
local g = ImGui.GetCurrentContext() local g = ImGui.GetCurrentContext()
draw_list:AddRectFilled(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTargetBg), rounding, 0) 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) draw_list:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTarget), rounding, g.Style.DragDropTargetBorderSize)
end end
--- @return ImGuiPayload? --- @return ImGuiPayload?
@@ -23622,7 +23658,8 @@ function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx)
draw_list._IdxWritePtr = draw_list.IdxBuffer.Size + 1 draw_list._IdxWritePtr = draw_list.IdxBuffer.Size + 1
-- If current command is used with different settings we need to add a new command -- 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] local curr_cmd = nil
if draw_list.CmdBuffer.Size > 0 then curr_cmd = draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size] end
if curr_cmd == nil then if curr_cmd == nil then
draw_list:AddDrawCmd() draw_list:AddDrawCmd()
elseif curr_cmd.ElemCount == 0 then elseif curr_cmd.ElemCount == 0 then
@@ -25099,6 +25136,25 @@ function ImGui.RenderColorRectWithAlphaCheckerboard(draw_list, p_min, p_max, col
end end
function ImGui.RenderRectFilledWithHole(draw_list, outer, inner, col, rounding)
local fill_L = inner.Min.x > outer.Min.x
local fill_R = inner.Max.x < outer.Max.x
local fill_U = inner.Min.y > outer.Min.y
local fill_D = inner.Max.y < outer.Max.y
local D = ImDrawFlags
local bor = bit32.bor
local function f(c, v) return c and 0 or v end
if fill_L then draw_list:AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, bor(D.RoundCornersNone, f(fill_U, D.RoundCornersTopLeft), f(fill_D, D.RoundCornersBottomLeft))) end
if fill_R then draw_list:AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, bor(D.RoundCornersNone, f(fill_U, D.RoundCornersTopRight), f(fill_D, D.RoundCornersBottomRight))) end
if fill_U then draw_list:AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, bor(D.RoundCornersNone, f(fill_L, D.RoundCornersTopLeft), f(fill_R, D.RoundCornersTopRight))) end
if fill_D then draw_list:AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, bor(D.RoundCornersNone, f(fill_L, D.RoundCornersBottomLeft), f(fill_R, D.RoundCornersBottomRight))) end
if fill_L and fill_U then draw_list:AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, D.RoundCornersTopLeft) end
if fill_R and fill_U then draw_list:AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, D.RoundCornersTopRight) end
if fill_L and fill_D then draw_list:AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, D.RoundCornersBottomLeft) end
if fill_R and fill_D then draw_list:AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, D.RoundCornersBottomRight) end
end
return true -- [Roblox] ModuleScripts must return exactly one value return true -- [Roblox] ModuleScripts must return exactly one value
end end
@@ -32207,7 +32263,7 @@ function ImGui.EndTabBar()
end end
g.CurrentTabBarStack:pop_back() g.CurrentTabBarStack:pop_back()
g.CurrentTabBar = (g.CurrentTabBarStack.Size == 0) and nil or g.CurrentTabBarStack.Data[g.CurrentTabBarStack.Size] if g.CurrentTabBarStack.Size == 0 then g.CurrentTabBar = nil else g.CurrentTabBar = g.CurrentTabBarStack.Data[g.CurrentTabBarStack.Size] end
end end
function ImGui.TabBarLayout(tab_bar) function ImGui.TabBarLayout(tab_bar)
+43 -8
View File
@@ -998,8 +998,8 @@ local function ApplyWindowSettings(window, settings)
window.AutoFitFramesX = 0; window.AutoFitFramesY = 0 window.AutoFitFramesX = 0; window.AutoFitFramesY = 0
end end
window.Collapsed = settings.Collapsed window.Collapsed = settings.Collapsed
-- window.DockId = settings.DockId window.DockId = settings.DockId or 0
-- window.DockOrder = settings.DockOrder window.DockOrder = settings.DockOrder or -1
SetWindowConditionAllowFlags(window, ImGuiCond.FirstUseEver, false) SetWindowConditionAllowFlags(window, ImGuiCond.FirstUseEver, false)
end end
@@ -1030,7 +1030,7 @@ local function InitOrLoadWindowSettings(window, settings)
window.SetWindowPosAllowFlags = bit32.bor(ImGuiCond.Always, ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing) window.SetWindowPosAllowFlags = bit32.bor(ImGuiCond.Always, ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing)
window.SetWindowSizeAllowFlags = window.SetWindowPosAllowFlags window.SetWindowSizeAllowFlags = window.SetWindowPosAllowFlags
window.SetWindowCollapsedAllowFlags = window.SetWindowPosAllowFlags window.SetWindowCollapsedAllowFlags = window.SetWindowPosAllowFlags
-- window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags
ApplyWindowSettings(window, settings) ApplyWindowSettings(window, settings)
if settings ~= nil then if settings ~= nil then
-- TODO: settings.LastUsedDate = g.SessionDate -- TODO: settings.LastUsedDate = g.SessionDate
@@ -5105,7 +5105,7 @@ local function RenderWindowOuterBorders(window)
RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0, window.WindowBorderSize)) RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0, window.WindowBorderSize))
end end
if g.Style.FrameBorderSize > 0 and (bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0) then if g.Style.FrameBorderSize > 0 and (bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0) and not window.DockIsActive then
local y = window.Pos.y + window.TitleBarHeight - 1 local y = window.Pos.y + window.TitleBarHeight - 1
window.DrawList:AddLineH(window.Pos.x + border_size * 0.5, window.Pos.x + window.Size.x - border_size * 0.5, y, border_col, g.Style.FrameBorderSize) window.DrawList:AddLineH(window.Pos.x + border_size * 0.5, window.Pos.x + window.Size.x - border_size * 0.5, y, border_col, g.Style.FrameBorderSize)
end end
@@ -5150,11 +5150,19 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
if bit32.band(bg_col, IM_COL32_A_MASK) ~= 0 then if bit32.band(bg_col, IM_COL32_A_MASK) ~= 0 then
local bg_rect = ImRect(window.Pos + ImVec2(0, window.TitleBarHeight), window.Pos + window.Size) local bg_rect = ImRect(window.Pos + ImVec2(0, window.TitleBarHeight), window.Pos + window.Size)
local bg_rounding_flags = (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and ImDrawFlags.RoundCornersAll or ImDrawFlags.RoundCornersBottom local bg_rounding_flags
local bg_draw_list = window.DrawList if window.DockIsActive then
bg_rounding_flags = ImGui.CalcRoundingFlagsForRectInRect(bg_rect, window.DockNode.HostWindow:Rect(), 0.0)
else
bg_rounding_flags = (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and ImDrawFlags.RoundCornersAll or ImDrawFlags.RoundCornersBottom
end
local bg_draw_list = window.DockIsActive and window.DockNode.HostWindow.DrawList or window.DrawList
if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(0) end
bg_draw_list:AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags) bg_draw_list:AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags)
if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(1) end
end end
end end
if window.DockIsActive then window.DockNode.IsBgDrawnThisFrame = true end
-- Title bar -- Title bar
if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and not window.DockIsActive then if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and not window.DockIsActive then
@@ -5172,6 +5180,25 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
end end
end end
-- Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
local node = window.DockNode
if window.DockIsActive and node:IsHiddenTabBar() and not node:IsNoTabBar() then
local unhide_sz_draw = ImTrunc(g.FontSize * 0.70)
local unhide_sz_hit = ImTrunc(g.FontSize * 0.55)
local p = node.Pos
local r = ImRect(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit))
local unhide_id = window:GetID("#UNHIDE")
ImGui.KeepAliveID(unhide_id)
local pressed, hovered, held = ImGui.ButtonBehavior(r, unhide_id, ImGuiButtonFlags.FlattenChildren)
if pressed then
node.WantHiddenTabBarToggle = true
elseif held and ImGui.IsMouseDragging(0) then
ImGui.StartMouseMovingWindowOrNode(window, node, true)
end
local col = ImGui.GetColorU32(((held and hovered) or (node.IsFocused and not hovered)) and ImGuiCol.ButtonActive or (hovered and ImGuiCol.ButtonHovered or ImGuiCol.Button))
window.DrawList:AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0), p + ImVec2(0.0, unhide_sz_draw), col)
end
if window.ScrollbarX then if window.ScrollbarX then
ImGui.Scrollbar(ImGuiAxis.X) ImGui.Scrollbar(ImGuiAxis.X)
end end
@@ -5200,13 +5227,14 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
end end
end end
if handle_borders_and_resize_grips then if handle_borders_and_resize_grips and not window.DockNodeAsHost then
RenderWindowOuterBorders(window) RenderWindowOuterBorders(window)
end end
end end
window.DC.NavLayerCurrent = ImGuiNavLayer.Main window.DC.NavLayerCurrent = ImGuiNavLayer.Main
end end
ImGui.RenderWindowOuterBorders = RenderWindowOuterBorders
--- @param window ImGuiWindow --- @param window ImGuiWindow
--- @param title_bar_rect ImRect --- @param title_bar_rect ImRect
@@ -6056,6 +6084,11 @@ end
--- @param popup_flags ImGuiPopupFlags --- @param popup_flags ImGuiPopupFlags
function ImGui.IsPopupOpen(id, popup_flags) function ImGui.IsPopupOpen(id, popup_flags)
local g = GImGui local g = GImGui
if popup_flags == nil then popup_flags = 0 end
if type(id) == "string" then
-- str_id overload: with AnyPopupId the id must be 0, otherwise it is hashed with the current window's ID stack
id = (bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0) and 0 or g.CurrentWindow:GetID(id)
end
if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0 then if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0 then
-- Return true if any popup is open at the current BeginPopup() level of the popup stack -- Return true if any popup is open at the current BeginPopup() level of the popup stack
@@ -7974,7 +8007,9 @@ local function AddWindowToDrawData(window, layer)
local g = GImGui local g = GImGui
local viewport = window.Viewport local viewport = window.Viewport
g.IO.MetricsRenderWindows = g.IO.MetricsRenderWindows + 1 g.IO.MetricsRenderWindows = g.IO.MetricsRenderWindows + 1
-- TODO: splitter if window.DrawList._Splitter._Count > 1 then
window.DrawList:ChannelsMerge() -- Merge if user forgot to merge back. Also required for DockNodeHost windows.
end
ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[layer], window.DrawList) ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[layer], window.DrawList)
for _, child in window.DC.ChildWindows:iter() do for _, child in window.DC.ChildWindows:iter() do
if (ImGui.IsWindowActiveAndVisible(child)) then -- Clipped children may have been marked not active if (ImGui.IsWindowActiveAndVisible(child)) then -- Clipped children may have been marked not active
+8 -7
View File
@@ -594,6 +594,7 @@ local function FixLargeWindowsWhenUndocking(size, ref_viewport)
end end
function ImGui.DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_ref) function ImGui.DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_ref)
if clear_persistent_docking_ref == nil then clear_persistent_docking_ref = true end -- C++ default argument
if window.DockNode then if window.DockNode then
ImGui.DockNodeRemoveWindow(window.DockNode, window, clear_persistent_docking_ref and 0 or window.DockId) ImGui.DockNodeRemoveWindow(window.DockNode, window, clear_persistent_docking_ref and 0 or window.DockId)
else else
@@ -1580,7 +1581,7 @@ function ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, dir,
local g = ImGui.GetCurrentContext() local g = ImGui.GetCurrentContext()
local dock_spacing = g.Style.ItemInnerSpacing.x local dock_spacing = g.Style.ItemInnerSpacing.x
local axis = (dir == ImGuiDir.Left or dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y local axis = (dir == ImGuiDir.Left or dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y
local a, o = axis + 1, bit32.bxor(axis, 1) + 1 local a, o = axis, 3 - axis -- the port's ImGuiAxis is 1-based (X=1,Y=2): it is directly the ImVec2 index
pos_new[o] = pos_old[o] pos_new[o] = pos_old[o]
size_new[o] = size_old[o] size_new[o] = size_old[o]
@@ -1724,7 +1725,7 @@ function ImGui.DockNodePreviewDockSetup(host_window, host_node, payload_window,
local size_new, size_old = ImVec2(0, 0), ImVec2(data.FutureNode.Size.x, data.FutureNode.Size.y) local size_new, size_old = ImVec2(0, 0), ImVec2(data.FutureNode.Size.x, data.FutureNode.Size.y)
ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window.Size) ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window.Size)
local split_ratio = ImSaturate(size_new[split_axis + 1] / data.FutureNode.Size[split_axis + 1]) local split_ratio = ImSaturate(size_new[split_axis] / data.FutureNode.Size[split_axis])
data.FutureNode.Pos = pos_new data.FutureNode.Pos = pos_new
data.FutureNode.Size = size_new data.FutureNode.Size = size_new
data.SplitRatio = (split_dir == ImGuiDir.Right or split_dir == ImGuiDir.Down) and (1.0 - split_ratio) or split_ratio data.SplitRatio = (split_dir == ImGuiDir.Right or split_dir == ImGuiDir.Down) and (1.0 - split_ratio) or split_ratio
@@ -1812,7 +1813,7 @@ function ImGui.DockNodePreviewDockRender(host_window, host_node, root_payload, d
local cc = draw_r_in:GetCenter() local cc = draw_r_in:GetCenter()
local center = ImVec2(math.floor(cc.x), math.floor(cc.y)) local center = ImVec2(math.floor(cc.x), math.floor(cc.y))
dl:AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding) dl:AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding)
dl:AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding, 0, thickness) dl:AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding, thickness)
if dir == ImGuiDir.Left or dir == ImGuiDir.Right then if dir == ImGuiDir.Left or dir == ImGuiDir.Right then
dl:AddLineV(center.x, draw_r_in.Min.y, draw_r_in.Max.y, overlay_col_lines, thickness) dl:AddLineV(center.x, draw_r_in.Min.y, draw_r_in.Max.y, overlay_col_lines, thickness)
end end
@@ -1851,7 +1852,7 @@ function ImGui.DockNodeTreeSplit(ctx, parent_node, split_axis, split_inheritor_c
parent_node.AuthorityForPos = ImGuiDataAuthority.DockNode parent_node.AuthorityForPos = ImGuiDataAuthority.DockNode
parent_node.AuthorityForSize = ImGuiDataAuthority.DockNode parent_node.AuthorityForSize = ImGuiDataAuthority.DockNode
local a = split_axis + 1 local a = split_axis
local size_avail = parent_node.Size[a] - g.Style.DockingSeparatorSize local size_avail = parent_node.Size[a] - g.Style.DockingSeparatorSize
size_avail = ImMax(size_avail, g.Style.WindowMinSize[a] * 2.0) size_avail = ImMax(size_avail, g.Style.WindowMinSize[a] * 2.0)
IM_ASSERT(size_avail > 0.0) IM_ASSERT(size_avail > 0.0)
@@ -1937,7 +1938,7 @@ function ImGui.DockNodeTreeUpdatePosSize(node, pos, size, only_write_to_single_n
if child_0_is_or_will_be_visible and child_1_is_or_will_be_visible then if child_0_is_or_will_be_visible and child_1_is_or_will_be_visible then
local spacing = g.Style.DockingSeparatorSize local spacing = g.Style.DockingSeparatorSize
local a = node.SplitAxis + 1 local a = node.SplitAxis
local size_avail = ImMax(size[a] - spacing, 0.0) local size_avail = ImMax(size[a] - spacing, 0.0)
local size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[a] * 2.0) * 0.5) local size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[a] * 2.0) * 0.5)
@@ -2006,7 +2007,7 @@ function ImGui.DockNodeTreeUpdateSplitter(node)
if child_0.IsVisible and child_1.IsVisible then if child_0.IsVisible and child_1.IsVisible then
local axis = node.SplitAxis local axis = node.SplitAxis
IM_ASSERT(axis ~= ImGuiAxis.None) IM_ASSERT(axis ~= ImGuiAxis.None)
local a, o = axis + 1, bit32.bxor(axis, 1) + 1 local a, o = axis, 3 - axis -- the port's ImGuiAxis is 1-based (X=1,Y=2): it is directly the ImVec2 index
local bb = ImRect(ImVec2(child_0.Pos.x, child_0.Pos.y), ImVec2(child_1.Pos.x, child_1.Pos.y)) local bb = ImRect(ImVec2(child_0.Pos.x, child_0.Pos.y), ImVec2(child_1.Pos.x, child_1.Pos.y))
bb.Min[a] = bb.Min[a] + child_0.Size[a] bb.Min[a] = bb.Min[a] + child_0.Size[a]
bb.Max[o] = bb.Max[o] + child_1.Size[o] bb.Max[o] = bb.Max[o] + child_1.Size[o]
@@ -3097,7 +3098,7 @@ function ImGui.SplitterBehavior(bb, id, axis, size1, size2, min_size1, min_size2
local bb_render = ImRect(bb.Min, bb.Max) local bb_render = ImRect(bb.Min, bb.Max)
if held then if held then
local d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min local d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min
local mouse_delta = d[axis + 1] local mouse_delta = d[axis]
local size_1_maximum_delta = ImMax(0.0, size1 - min_size1) local size_1_maximum_delta = ImMax(0.0, size1 - min_size1)
local size_2_maximum_delta = ImMax(0.0, size2 - min_size2) local size_2_maximum_delta = ImMax(0.0, size2 - min_size2)
+1 -1
View File
@@ -266,7 +266,7 @@ end
function ImGui.RenderDragDropTargetRectEx(draw_list, bb, rounding) function ImGui.RenderDragDropTargetRectEx(draw_list, bb, rounding)
local g = ImGui.GetCurrentContext() local g = ImGui.GetCurrentContext()
draw_list:AddRectFilled(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTargetBg), rounding, 0) 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) draw_list:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTarget), rounding, g.Style.DragDropTargetBorderSize)
end end
--- @return ImGuiPayload? --- @return ImGuiPayload?
+21 -1
View File
@@ -3492,7 +3492,8 @@ function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx)
draw_list._IdxWritePtr = draw_list.IdxBuffer.Size + 1 draw_list._IdxWritePtr = draw_list.IdxBuffer.Size + 1
-- If current command is used with different settings we need to add a new command -- 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] local curr_cmd = nil
if draw_list.CmdBuffer.Size > 0 then curr_cmd = draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size] end
if curr_cmd == nil then if curr_cmd == nil then
draw_list:AddDrawCmd() draw_list:AddDrawCmd()
elseif curr_cmd.ElemCount == 0 then elseif curr_cmd.ElemCount == 0 then
@@ -4969,4 +4970,23 @@ function ImGui.RenderColorRectWithAlphaCheckerboard(draw_list, p_min, p_max, col
end end
function ImGui.RenderRectFilledWithHole(draw_list, outer, inner, col, rounding)
local fill_L = inner.Min.x > outer.Min.x
local fill_R = inner.Max.x < outer.Max.x
local fill_U = inner.Min.y > outer.Min.y
local fill_D = inner.Max.y < outer.Max.y
local D = ImDrawFlags
local bor = bit32.bor
local function f(c, v) return c and 0 or v end
if fill_L then draw_list:AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, bor(D.RoundCornersNone, f(fill_U, D.RoundCornersTopLeft), f(fill_D, D.RoundCornersBottomLeft))) end
if fill_R then draw_list:AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, bor(D.RoundCornersNone, f(fill_U, D.RoundCornersTopRight), f(fill_D, D.RoundCornersBottomRight))) end
if fill_U then draw_list:AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, bor(D.RoundCornersNone, f(fill_L, D.RoundCornersTopLeft), f(fill_R, D.RoundCornersTopRight))) end
if fill_D then draw_list:AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, bor(D.RoundCornersNone, f(fill_L, D.RoundCornersBottomLeft), f(fill_R, D.RoundCornersBottomRight))) end
if fill_L and fill_U then draw_list:AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, D.RoundCornersTopLeft) end
if fill_R and fill_U then draw_list:AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, D.RoundCornersTopRight) end
if fill_L and fill_D then draw_list:AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, D.RoundCornersBottomLeft) end
if fill_R and fill_D then draw_list:AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, D.RoundCornersBottomRight) end
end
return true -- [Roblox] ModuleScripts must return exactly one value return true -- [Roblox] ModuleScripts must return exactly one value
+1 -1
View File
@@ -218,7 +218,7 @@ function ImGui.EndTabBar()
end end
g.CurrentTabBarStack:pop_back() g.CurrentTabBarStack:pop_back()
g.CurrentTabBar = (g.CurrentTabBarStack.Size == 0) and nil or g.CurrentTabBarStack.Data[g.CurrentTabBarStack.Size] if g.CurrentTabBarStack.Size == 0 then g.CurrentTabBar = nil else g.CurrentTabBar = g.CurrentTabBarStack.Data[g.CurrentTabBarStack.Size] end
end end
function ImGui.TabBarLayout(tab_bar) function ImGui.TabBarLayout(tab_bar)
+4 -1
View File
@@ -17,7 +17,7 @@ local counter = 0
ImGui.CreateContext() ImGui.CreateContext()
local io = ImGui.GetIO() local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) io.ConfigFlags = bit32.bor(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard, ImGuiConfigFlags.DockingEnable)
local style = ImGui.GetStyle() local style = ImGui.GetStyle()
style:ScaleAllSizes(main_scale) style:ScaleAllSizes(main_scale)
@@ -29,6 +29,9 @@ RunService.Stepped:Connect(function()
ImGui_ImplRoblox.NewFrame() ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame() ImGui.NewFrame()
-- Dockspace covering the whole screen; the centre stays transparent so the game shows through
ImGui.DockSpaceOverViewport(0, nil, G.ImGuiDockNodeFlags.PassthruCentralNode)
if show_demo_window then if show_demo_window then
ImGui.ShowDemoWindow() ImGui.ShowDemoWindow()
end end
+5 -1
View File
@@ -45,10 +45,14 @@ there, but keep this port's existing data structures and conventions. Read the s
- `python3 tools/test/run.py` — runs main.client.luau 400 frames with scripted random input (mouse, clicks, - `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. 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 - `--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 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 (copy main.client.luau as a template: `local G = require(ReplicatedStorage:WaitForChild("ImGui"))`, all
library globals are fields of `G`). library globals are fields of `G`).
- `python3 tools/test/missing.py [tables|widgets|imgui.cpp]` lists upstream functions not yet ported. - `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 - 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. because of an error in a file you don't own, wait a minute and retry instead of editing it.
- **ImGuiAxis is 1-based in this port** (`None=0, X=1, Y=2`; upstream `None=-1, X=0, Y=1`). Use `v[axis]` to index an ImVec2 and `3 - axis` for the other axis. `ImGuiDir` keeps upstream values (`None=-1, Left=0, Right=1, Up=2, Down=3`).
- Never write `cond and nil or x` in Lua: it always evaluates `x`. Use an `if`.
- Port signature quirk: `AddRect(min, max, col, rounding, thickness, flags)` (thickness BEFORE flags; upstream has flags first). Check the port's signature before porting any draw call that takes both.\n
- C++ default arguments (e.g. `bool x = true`) must be reproduced with `if x == nil then x = true end`; a missing Lua argument is nil/falsy.
+1 -21
View File
@@ -195,7 +195,7 @@ for i = 0, 255 do hex[i] = string.format("%02x", i) end
local total = 0 local total = 0
for frame = 1, NFRAMES do for frame = 1, NFRAMES do
local r = rnd(100) local r = ARG_QUIET and 100 or rnd(100)
if r < 40 then 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)) 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 elseif r < 55 then
@@ -221,25 +221,5 @@ for frame = 1, NFRAMES do
if frame > 10 then total = total + dt end if frame > 10 then total = total + dt end
if frame == 10 then render_time = 0; _G_UPLOADS = 0 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 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))) 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)))
+5 -47
View File
@@ -4,12 +4,9 @@
python3 tools/test/run.py # 400 frames of main.client.luau, scripted random input 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 --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 python3 tools/test/run.py --main my_test.luau # different LocalScript
""" """
import argparse, os, shutil, subprocess, sys, zlib, struct, tempfile import argparse, os, shutil, subprocess, sys, tempfile
TMP = tempfile.mkdtemp(prefix="imgui_test_") # per process: parallel runs must not share files TMP = tempfile.mkdtemp(prefix="imgui_test_") # per process: parallel runs must not share files
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
@@ -34,7 +31,7 @@ def build_run(args, flags):
return path return path
def run(args, extra=""): 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) flags = "ARG_FRAMES=%d; ARG_BIG=%s; ARG_OPEN=%s; ARG_QUIET=%s; %s" % (args.frames, str(args.big).lower(), str(args.open).lower(), str(args.quiet).lower(), extra)
try: try:
p = subprocess.run([LUAU, "-O2", "--codegen", build_run(args, flags)], capture_output=True, text=True, timeout=args.timeout) p = subprocess.run([LUAU, "-O2", "--codegen", build_run(args, flags)], capture_output=True, text=True, timeout=args.timeout)
except subprocess.TimeoutExpired as e: except subprocess.TimeoutExpired as e:
@@ -56,57 +53,18 @@ def remap(text):
return "%s:%d" % (best[1], n - best[0] + 1) return "%s:%d" % (best[1], n - best[0] + 1)
return re.sub(r"\S*run\.luau:(\d+)", sub, text) 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(): def main():
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument("--frames", type=int, default=400) ap.add_argument("--frames", type=int, default=400)
ap.add_argument("--main", default=os.path.join(ROOT, "main.client.luau")) 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("--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("--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) ap.add_argument("--timeout", type=int, default=120)
ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)")
args = ap.parse_args() args = ap.parse_args()
if args.bench: code, out = run(args, "")
for mode in ["", "ARG_FORCE=true"]: print(out[-4000:])
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) sys.exit(code)
try: try:
+47
View File
@@ -0,0 +1,47 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local frame = 0
local dockspace_id
local winA, winB, winC
RunService.Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
dockspace_id = ImGui.DockSpaceOverViewport(0, nil, 0)
if frame == 2 then
ImGui.DockBuilderRemoveNode(dockspace_id)
ImGui.DockBuilderAddNode(dockspace_id, bit32.bor(G.ImGuiDockNodeFlags.DockSpace))
ImGui.DockBuilderSetNodeSize(dockspace_id, ImGui.GetMainViewport().Size)
local left, right = ImGui.DockBuilderSplitNode(dockspace_id, G.ImGuiDir.Left, 0.3)
ImGui.DockBuilderDockWindow("A", left)
ImGui.DockBuilderDockWindow("B", right)
ImGui.DockBuilderDockWindow("C", right)
ImGui.DockBuilderFinish(dockspace_id)
print("split", left, right)
end
for _, n in ipairs({"A", "B", "C"}) do
if ImGui.Begin(n) then ImGui.Text("window " .. n) end
ImGui.End()
end
if frame == 30 then
local g = ImGui.GetCurrentContext()
local a, b, c = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B"), ImGui.FindWindowByName("C")
print("A node", a.DockNode and a.DockNode.ID, "B node", b.DockNode and b.DockNode.ID, "C node", c.DockNode and c.DockNode.ID)
assert(a.DockNode ~= nil and b.DockNode ~= nil, "windows must be docked")
assert(a.DockNode ~= b.DockNode, "A and B in different nodes")
assert(b.DockNode == c.DockNode, "B and C share a node")
assert(b.DockNode.TabBar ~= nil and b.DockNode.TabBar.Tabs.Size == 2, "B/C tab bar with 2 tabs")
local ini = ImGui.SaveIniSettingsToMemory()
print(ini)
assert(ini:find("%[Docking%]%[Data%]"), "docking ini section")
print("DOCK OK")
end
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
end)
+69
View File
@@ -0,0 +1,69 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local frame = 0
-- script: {frame, x, y, down}
local function lerp(a, b, t) return a + (b - a) * t end
local function script(f)
if f >= 50 then
-- tab of B inside the docked node: press on the tab, drag far downward to undock
if f < 52 then return 150, 108, false end
if f == 52 then return 150, 108, true end
if f <= 66 then local t = (f - 52) / 14 return lerp(150, 700, t), lerp(108, 600, t), true end
if f <= 70 then return 700, 600, true end
return 700, 600, false
end
if f < 6 then return 600, 108, false end
if f == 6 then return 600, 108, true end
if f <= 20 then local t = (f - 6) / 14 return lerp(600, 250, t), lerp(108, 190, t), true end
if f <= 26 then return 250, 190, true end
if f == 27 then return 250, 190, false end
return 250, 190, false
end
local last_down = false
RunService.Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
local x, y, down = script(frame)
io:AddMousePosEvent(x, y)
if down ~= last_down then io:AddMouseButtonEvent(0, down); last_down = down end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(100, 100), G.ImGuiCond.FirstUseEver)
ImGui.SetNextWindowSize(ImVec2(300, 200), G.ImGuiCond.FirstUseEver)
if ImGui.Begin("A") then ImGui.Text("window A") end
ImGui.End()
ImGui.SetNextWindowPos(ImVec2(500, 100), G.ImGuiCond.FirstUseEver)
ImGui.SetNextWindowSize(ImVec2(300, 200), G.ImGuiCond.FirstUseEver)
if ImGui.Begin("B") then ImGui.Text("window B") end
ImGui.End()
if frame == 28 or frame == 40 then
local a, b = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B")
local g = ImGui.GetCurrentContext()
print("frame", frame, "movingwindow", g.MovingWindow and g.MovingWindow.Name, "dragdrop", g.DragDropActive, "A.node", a.DockNode and a.DockNode.ID, "B.node", b.DockNode and b.DockNode.ID, "B.pos", b.Pos.x, b.Pos.y)
end
if frame == 45 then
local a, b = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B")
assert(a.DockNode ~= nil and a.DockNode == b.DockNode, "B must be docked into A's node")
assert(a.DockNode.Windows.Size == 2, "node has 2 windows")
print("DRAG DOCK OK")
end
if frame >= 60 and frame <= 90 and frame % 3 == 0 then local g = ImGui.GetCurrentContext(); local b = ImGui.FindWindowByName("B"); local tb = b.DockNode and b.DockNode.TabBar; print("f", frame, "Bnode", b.DockNode and b.DockNode.ID, "Bpos", b.Pos.x, b.Pos.y, "active", g.ActiveId, "hovered", g.HoveredId, "hovwin", g.HoveredWindow and g.HoveredWindow.Name, "tabid", b.TabId, "moving", g.MovingWindow and g.MovingWindow.Name, "dd", g.DragDropActive, "mouse", g.IO.MousePos.x, g.IO.MousePos.y, "down", g.IO.MouseDown[0], "drag", ImGui.IsMouseDragging(0)) end
if frame == 52 then
local b = ImGui.FindWindowByName("B")
local tb = b.DockNode.TabBar
for i = 1, tb.Tabs.Size do local t = tb.Tabs.Data[i]; print("tab", i, ImGui.TabBarGetTabName(tb, t), "offset", t.Offset, "width", t.Width, "bar", tb.BarRect.Min.x, tb.BarRect.Min.y, tb.BarRect.Max.x, tb.BarRect.Max.y) end
end
if frame == 90 then
local a, b = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B")
print("final", "A.node", a.DockNode and a.DockNode.ID, "B.node", b.DockNode and b.DockNode.ID, "B.pos", b.Pos.x, b.Pos.y)
assert(b.DockNode == nil or b.DockNode ~= a.DockNode, "B must be undocked from A")
print("UNDOCK OK")
end
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
end)
+80
View File
@@ -0,0 +1,80 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local frame = 0
local seed = 7
local function rnd(n) seed = (seed * 1103515245 + 12345) % 2147483648; return seed % n end
local names = {"A", "B", "C", "D", "E"}
local drag -- {fx,fy,tx,ty,start}
local last_down = false
local docks, undocks = 0, 0
local function check_tree()
local g = ImGui.GetCurrentContext()
for id, node in pairs(g.DockContext.Nodes) do
assert(node.ID == id)
if node.ParentNode then
assert(node.ParentNode.ChildNodes[0] == node or node.ParentNode.ChildNodes[1] == node, "parent link")
end
for _, w in node.Windows:iter() do assert(w.DockNode == node or w.DockNode == nil, "window->node link") end
if node:IsSplitNode() then assert(node.ChildNodes[1] ~= nil and node.Windows.Size == 0, "split node has two children and no windows") end
end
for _, n in ipairs(names) do
local w = ImGui.FindWindowByName(n)
if w and w.DockNode then assert(w.DockNode.Windows:find_index(w) ~= nil, "node contains window") end
end
end
RunService.Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
-- scripted drag
local x, y, down = (drag and drag.x or 5), (drag and drag.y or 5), false
if not drag and frame % 45 == 0 then
local w = ImGui.FindWindowByName(names[rnd(#names) + 1])
if w and w.Active then
local sx, sy
if w.DockNode and w.DockNode.TabBar and w.DockNode.TabBar.Tabs.Size > 0 and rnd(2) == 0 then
local tb = w.DockNode.TabBar; local t = tb.Tabs.Data[rnd(tb.Tabs.Size) + 1]
sx, sy = tb.BarRect.Min.x + t.Offset + 4, tb.BarRect.Min.y + 8
else sx, sy = w.Pos.x + 40, w.Pos.y + 8 end
local mode = rnd(3)
local tx, ty
if mode == 0 then local o = ImGui.FindWindowByName(names[rnd(#names) + 1]); tx, ty = o.Pos.x + o.Size.x * (0.1 + rnd(80) / 100), o.Pos.y + o.Size.y * (0.1 + rnd(80) / 100)
else tx, ty = 50 + rnd(1800), 50 + rnd(980) end
drag = { sx = sx, sy = sy, tx = tx, ty = ty, start = frame, x = sx, y = sy }
x, y = sx, sy
end
end
if drag then
local f = frame - drag.start
local t = math.min(f / 14, 1)
drag.x = drag.sx + (drag.tx - drag.sx) * t; drag.y = drag.sy + (drag.ty - drag.sy) * t
x, y = drag.x, drag.y
down = f >= 1 and f <= 24
if f > 26 then drag = nil end
end
io:AddMousePosEvent(x, y)
if down ~= last_down then io:AddMouseButtonEvent(0, down); last_down = down end
ImGui.NewFrame()
ImGui.DockSpaceOverViewport(0, nil, G.ImGuiDockNodeFlags.PassthruCentralNode)
for i, n in ipairs(names) do
ImGui.SetNextWindowPos(ImVec2(60 + i * 90, 60 + i * 50), G.ImGuiCond.FirstUseEver)
ImGui.SetNextWindowSize(ImVec2(260, 180), G.ImGuiCond.FirstUseEver)
if ImGui.Begin(n) then ImGui.Text("window " .. n) end
ImGui.End()
end
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame % 50 == 0 then check_tree() end
if frame % 1000 == 0 then
local nodes, docked = 0, 0
for _ in pairs(ImGui.GetCurrentContext().DockContext.Nodes) do nodes += 1 end
for _, n in ipairs(names) do local w = ImGui.FindWindowByName(n); if w and w.DockNode then docked += 1 end end
print("frame", frame, "nodes", nodes, "docked windows", docked)
local ini = ImGui.SaveIniSettingsToMemory()
end
end)
+61
View File
@@ -0,0 +1,61 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
local function setup()
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
end
setup()
local frame, phase, ini, base = 0, 1, nil, 0
local function windows()
for _, n in ipairs({"A", "B", "C"}) do
if ImGui.Begin(n) then ImGui.Text("window " .. n) end
ImGui.End()
end
end
RunService.Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
local id = ImGui.DockSpaceOverViewport(0, nil, 0)
local f = frame - base
if phase == 1 then
if f == 2 then
ImGui.DockBuilderRemoveNode(id)
ImGui.DockBuilderAddNode(id, G.ImGuiDockNodeFlags.DockSpace)
ImGui.DockBuilderSetNodeSize(id, ImGui.GetMainViewport().Size)
local l, r = ImGui.DockBuilderSplitNode(id, G.ImGuiDir.Left, 0.25)
local rt, rb = ImGui.DockBuilderSplitNode(r, G.ImGuiDir.Up, 0.6)
ImGui.DockBuilderDockWindow("A", l); ImGui.DockBuilderDockWindow("B", rt); ImGui.DockBuilderDockWindow("C", rb)
ImGui.DockBuilderFinish(id)
end
windows()
if f == 20 then
ini = ImGui.SaveIniSettingsToMemory()
print(ini:match("%[Docking%].*"))
end
else
windows()
if f == 20 then
local a, b, c = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B"), ImGui.FindWindowByName("C")
print("reloaded nodes", a.DockNode and a.DockNode.ID, b.DockNode and b.DockNode.ID, c.DockNode and c.DockNode.ID, "sizes", a.Size.x, b.Size.x, b.Size.y, c.Size.y)
assert(a.DockNode and b.DockNode and c.DockNode, "all docked after reload")
assert(a.DockNode ~= b.DockNode and b.DockNode ~= c.DockNode, "3 distinct nodes")
local ini2 = ImGui.SaveIniSettingsToMemory()
assert(ini2:match("%[Docking%].*") == ini:match("%[Docking%].*"), "docking ini identical after round trip")
print("INI ROUNDTRIP OK")
end
end
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if phase == 1 and f == 20 then
G.ImGui_ImplRoblox.Shutdown()
ImGui.DestroyContext()
setup()
ImGui.LoadIniSettingsFromMemory(ini)
phase, base = 2, frame
end
end)
+41
View File
@@ -0,0 +1,41 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
G.ImGui_ImplRoblox.Init()
local frame = 0
local logged = nil
RunService.Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(20, 20), G.ImGuiCond.Always)
ImGui.SetNextWindowSize(ImVec2(400, 300), G.ImGuiCond.Always)
ImGui.Begin("Popups")
if frame == 3 then ImGui.OpenPopup("Delete?") end
if frame == 5 then ImGui.LogToClipboard() end
ImGui.Text("Some text to log")
if frame == 5 then ImGui.LogFinish(); logged = ImGui.GetClipboardText() end
ImGui.BeginChild("child", ImVec2(0, 100), G.ImGuiChildFlags.Borders)
for i = 1, 20 do ImGui.Text("child line " .. i) end
ImGui.EndChild()
if ImGui.BeginPopupContextWindow() then ImGui.Text("ctx"); ImGui.EndPopup() end
if ImGui.BeginPopupModal("Delete?", nil, G.ImGuiWindowFlags.AlwaysAutoResize) then
ImGui.Text("All those beautiful files will be deleted.\nThis operation cannot be undone!")
ImGui.Separator()
if ImGui.Button("OK", ImVec2(120, 0)) then ImGui.CloseCurrentPopup() end
ImGui.EndPopup()
end
ImGui.End()
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame == 6 then
print("LOG:", logged)
local ini = ImGui.SaveIniSettingsToMemory()
print("INI:", ini)
ImGui.LoadIniSettingsFromMemory(ini)
assert(ImGui.SaveIniSettingsToMemory() == ini, "ini round trip")
print("INI round trip ok")
end
end)
+30
View File
@@ -0,0 +1,30 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
G.ImGui_ImplRoblox.Init()
local opened = {true, true, true, true, true, true, true, true}
local frame = 0
RunService.Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(0, 0), G.ImGuiCond.Always)
ImGui.SetNextWindowSize(ImVec2(300, 300), G.ImGuiCond.Always)
ImGui.Begin("Tabs")
local flags = bit32.bor(G.ImGuiTabBarFlags.Reorderable, G.ImGuiTabBarFlags.TabListPopupButton, G.ImGuiTabBarFlags.FittingPolicyScroll, G.ImGuiTabBarFlags.AutoSelectNewTabs)
if ImGui.BeginTabBar("bar", flags) then
if ImGui.TabItemButton("+", G.ImGuiTabItemFlags.Trailing) then end
for i = 1, #opened do
local vis
vis, opened[i] = ImGui.BeginTabItem("Tab number " .. i, opened[i], (i == 3) and G.ImGuiTabItemFlags.UnsavedDocument or 0)
if vis then ImGui.Text("Contents of tab " .. i); ImGui.EndTabItem() end
end
ImGui.EndTabBar()
end
if frame == 50 then local tb = ImGui.GetCurrentContext().TabBars:GetByIndex(0); print("tabs", tb.Tabs.Size, "selected", tb.SelectedTabId ~= 0, "scrollbtn", tb.ScrollButtonEnabled) end
ImGui.End()
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
end)