docking: runs end to end (dock/undock by drag, builder, ini round trip), test scripts
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user