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
end
window.Collapsed = settings.Collapsed
-- window.DockId = settings.DockId
-- window.DockOrder = settings.DockOrder
window.DockId = settings.DockId or 0
window.DockOrder = settings.DockOrder or -1
SetWindowConditionAllowFlags(window, ImGuiCond.FirstUseEver, false)
end
@@ -1042,7 +1042,7 @@ local function InitOrLoadWindowSettings(window, settings)
window.SetWindowPosAllowFlags = bit32.bor(ImGuiCond.Always, ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing)
window.SetWindowSizeAllowFlags = window.SetWindowPosAllowFlags
window.SetWindowCollapsedAllowFlags = window.SetWindowPosAllowFlags
-- window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags
window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags
ApplyWindowSettings(window, settings)
if settings ~= nil then
-- 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))
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
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
@@ -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
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_draw_list = window.DrawList
local bg_rounding_flags
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)
if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(1) end
end
end
if window.DockIsActive then window.DockNode.IsBgDrawnThisFrame = true end
-- Title bar
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
-- 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
ImGui.Scrollbar(ImGuiAxis.X)
end
@@ -5212,13 +5239,14 @@ local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_high
end
end
if handle_borders_and_resize_grips then
if handle_borders_and_resize_grips and not window.DockNodeAsHost then
RenderWindowOuterBorders(window)
end
end
window.DC.NavLayerCurrent = ImGuiNavLayer.Main
end
ImGui.RenderWindowOuterBorders = RenderWindowOuterBorders
--- @param window ImGuiWindow
--- @param title_bar_rect ImRect
@@ -6068,6 +6096,11 @@ end
--- @param popup_flags ImGuiPopupFlags
function ImGui.IsPopupOpen(id, popup_flags)
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
-- 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 viewport = window.Viewport
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)
for _, child in window.DC.ChildWindows:iter() do
if (ImGui.IsWindowActiveAndVisible(child)) then -- Clipped children may have been marked not active
@@ -17305,6 +17340,7 @@ local function FixLargeWindowsWhenUndocking(size, ref_viewport)
end
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
ImGui.DockNodeRemoveWindow(window.DockNode, window, clear_persistent_docking_ref and 0 or window.DockId)
else
@@ -18291,7 +18327,7 @@ function ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, dir,
local g = ImGui.GetCurrentContext()
local dock_spacing = g.Style.ItemInnerSpacing.x
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]
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)
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.Size = size_new
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 center = ImVec2(math.floor(cc.x), math.floor(cc.y))
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
dl:AddLineV(center.x, draw_r_in.Min.y, draw_r_in.Max.y, overlay_col_lines, thickness)
end
@@ -18562,7 +18598,7 @@ function ImGui.DockNodeTreeSplit(ctx, parent_node, split_axis, split_inheritor_c
parent_node.AuthorityForPos = 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
size_avail = ImMax(size_avail, g.Style.WindowMinSize[a] * 2.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
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_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
local axis = node.SplitAxis
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))
bb.Min[a] = bb.Min[a] + child_0.Size[a]
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)
if held then
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_2_maximum_delta = ImMax(0.0, size2 - min_size2)
@@ -20104,7 +20140,7 @@ 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)
draw_list:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTarget), rounding, g.Style.DragDropTargetBorderSize)
end
--- @return ImGuiPayload?
@@ -23622,7 +23658,8 @@ function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx)
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]
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
draw_list:AddDrawCmd()
elseif curr_cmd.ElemCount == 0 then
@@ -25099,6 +25136,25 @@ function ImGui.RenderColorRectWithAlphaCheckerboard(draw_list, p_min, p_max, col
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
end
@@ -32207,7 +32263,7 @@ function ImGui.EndTabBar()
end
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
function ImGui.TabBarLayout(tab_bar)