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
+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,
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.
- **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
for frame = 1, NFRAMES do
local r = rnd(100)
local r = ARG_QUIET and 100 or 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
@@ -221,25 +221,5 @@ for frame = 1, NFRAMES do
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)))
+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 --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
import argparse, os, shutil, subprocess, sys, tempfile
TMP = tempfile.mkdtemp(prefix="imgui_test_") # per process: parallel runs must not share files
HERE = os.path.dirname(os.path.abspath(__file__))
@@ -34,7 +31,7 @@ def build_run(args, flags):
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)
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:
p = subprocess.run([LUAU, "-O2", "--codegen", build_run(args, flags)], capture_output=True, text=True, timeout=args.timeout)
except subprocess.TimeoutExpired as e:
@@ -56,57 +53,18 @@ def remap(text):
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)
ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)")
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)
code, out = run(args, "")
print(out[-4000:])
sys.exit(code)
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)