-- Renders README screenshots headless: runs one SCENE (prepended by tools/screenshots/make.py), composites the
-- backend's tile ImageLabels exactly like Roblox stacks them (ZIndex, nested offsets, ClipsDescendants) over a
-- background colour, and prints frames as "PX " for the crop rect.
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
local Cond = G.ImGuiCond
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
io.IniFilename = nil
G.ImGui_ImplRoblox.Init()
G.ImGui_ImplRoblox.SetRenderRate(0)
local BG = { 115, 140, 153 } -- examples/example_roblox clear_color
-- force tree nodes / headers / tabs open by label
local OPEN = {}
for _, fname in ipairs({ "TreeNode", "CollapsingHeader" }) do
local real = ImGui[fname]
ImGui[fname] = function(label, ...)
if OPEN[label] then ImGui.SetNextItemOpen(true, Cond.Always) end
return real(label, ...)
end
end
local SELECT_TAB = nil
local realBTI = ImGui.BeginTabItem
ImGui.BeginTabItem = function(label, p_open, flags)
if label == SELECT_TAB then flags = bit32.bor(flags or 0, G.ImGuiTabItemFlags.SetSelected) end
return realBTI(label, p_open, flags)
end
-- the demo sets its own pos/size (FirstUseEver) right before Begin: override it there
local DEMO_RECT = nil
local realBegin = ImGui.Begin
ImGui.Begin = function(name, ...)
if name == "Dear ImGui Demo" and DEMO_RECT then
ImGui.SetNextWindowPos(ImVec2(DEMO_RECT[1], DEMO_RECT[2]), Cond.Always); ImGui.SetNextWindowSize(ImVec2(DEMO_RECT[3], DEMO_RECT[4]), Cond.Always)
end
return realBegin(name, ...)
end
local function Pos(x, y, w, h) ImGui.SetNextWindowPos(ImVec2(x, y), Cond.Always); if w then ImGui.SetNextWindowSize(ImVec2(w, h), Cond.Always) end end
-- ---------------------------------------------------------------- scenes
-- Each: crop {x, y, w, h}, frames to capture (list), mouse position, draw(frame)
local scenes = {}
-- README "Usage" example, verbatim
local f, counter = 0.6, 0
local buf = ImGui.NewTextBuffer("Hello, world!", 256)
local my_color = ImVec4(1.0, 0.4, 0.2, 1.0)
local function HelloWorld()
ImGui.Begin("My First Tool")
ImGui.Text("Hello, world %d", 123)
if ImGui.Button("Save") then counter += 1 end
ImGui.InputText("string", buf, 256)
f = ImGui.SliderFloat("float", f, 0.0, 1.0)
ImGui.ColorEdit4("Color", my_color)
ImGui.End()
end
scenes.hello = { crop = { 0, 0, 420, 200 }, shots = { 30 }, mouse = { 1900, 1060 }, draw = function()
Pos(10, 10, 400, 180); HelloWorld()
end }
scenes.demo = { crop = { 0, 0, 1000, 740 }, shots = { 40 }, mouse = { 640, 330 }, draw = function()
OPEN["Widgets"] = true; OPEN["Basic"] = true
Pos(10, 10, 380, 200)
ImGui.Begin("Hello, world!")
ImGui.Text("This is some useful text.")
ImGui.Checkbox("Demo Window", true)
ImGui.SliderFloat("float", 0.42, 0.0, 1.0)
ImGui.Button("Button"); ImGui.SameLine(); ImGui.Text("counter = %d", 3)
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 16.667, 60.0)
ImGui.End()
DEMO_RECT = { 410, 10, 580, 720 }; ImGui.ShowDemoWindow()
end }
scenes.tables = { crop = { 0, 0, 700, 740 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function()
OPEN["Tables & Columns"] = true; OPEN["Borders, background"] = true; OPEN["Sorting"] = true
DEMO_RECT = { 10, 10, 680, 720 }; ImGui.ShowDemoWindow()
end }
scenes.style = { crop = { 0, 0, 560, 740 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function()
SELECT_TAB = "Colors"
Pos(10, 10, 540, 720); ImGui.Begin("Dear ImGui Style Editor"); ImGui.ShowStyleEditor(); ImGui.End()
end }
scenes.metrics = { crop = { 0, 0, 620, 560 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function()
OPEN["Windows"] = true; OPEN["DrawLists"] = true
Pos(10, 10, 600, 540); ImGui.ShowMetricsWindow()
end }
scenes.docking = { crop = { 0, 0, 1100, 640 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function(frame)
Pos(10, 10, 1080, 620)
ImGui.Begin("Editor", nil, G.ImGuiWindowFlags.MenuBar)
if ImGui.BeginMenuBar() then
if ImGui.BeginMenu("File") then ImGui.EndMenu() end
if ImGui.BeginMenu("View") then ImGui.EndMenu() end
ImGui.EndMenuBar()
end
local id = ImGui.GetID("MainDock")
if frame == 2 then
ImGui.DockBuilderRemoveNode(id)
ImGui.DockBuilderAddNode(id, G.ImGuiDockNodeFlags.None)
ImGui.DockBuilderSetNodeSize(id, ImVec2(1064, 560))
local left, right = ImGui.DockBuilderSplitNode(id, G.ImGuiDir.Left, 0.3)
local rtop, rbottom = ImGui.DockBuilderSplitNode(right, G.ImGuiDir.Up, 0.65)
ImGui.DockBuilderDockWindow("Scene", left)
ImGui.DockBuilderDockWindow("Inspector", rtop)
ImGui.DockBuilderDockWindow("Properties", rtop)
ImGui.DockBuilderDockWindow("Console", rbottom)
ImGui.DockBuilderFinish(id)
end
ImGui.DockSpace(id)
ImGui.End()
ImGui.Begin("Scene")
for _, n in ipairs({ "Workspace", "Lighting", "Players", "ReplicatedStorage" }) do
if ImGui.TreeNode(n) then ImGui.BulletText("Part"); ImGui.BulletText("Model"); ImGui.TreePop() end
end
ImGui.End()
ImGui.Begin("Inspector")
ImGui.SeparatorText("Transform")
ImGui.DragFloat3("Position", { 12.0, 4.5, -3.0 }, 0.1)
ImGui.DragFloat3("Rotation", { 0.0, 90.0, 0.0 }, 1.0)
ImGui.SeparatorText("Appearance")
ImGui.ColorEdit3("Color", ImVec4(0.2, 0.6, 1.0, 1.0))
ImGui.SliderFloat("Transparency", 0.25, 0.0, 1.0)
ImGui.Checkbox("Anchored", true); ImGui.SameLine(); ImGui.Checkbox("CanCollide", false)
ImGui.End()
ImGui.Begin("Properties"); ImGui.Text("Properties"); ImGui.End()
ImGui.Begin("Console")
for i = 1, 6 do ImGui.TextColored(ImVec4(0.6, 0.9, 0.6, 1), "[%02d:00] Loaded chunk %d", i, i * 7) end
ImGui.End()
end }
-- README plot example (animated -> GIF)
local values, hist = {}, { 0.6, 0.1, 1.0, 0.5, 0.92, 0.1, 0.2 }
scenes.plot = { crop = { 0, 0, 440, 330 }, shots = {}, mouse = { 1900, 1060 }, draw = function(frame)
local t = frame / 30
for i = 1, 90 do values[i] = math.sin((i / 90) * math.pi * 4 + t * 3) * math.cos(t + i * 0.05) end
for i = 1, #hist do hist[i] = 0.5 + 0.5 * math.sin(t * 2 + i) end
Pos(10, 10, 420, 310)
ImGui.Begin("Plotting")
ImGui.PlotLines("Lines", values, nil, #values, 0, "sine", -1.0, 1.0, ImVec2(0, 90))
ImGui.PlotHistogram("Histogram", hist, nil, #hist, 0, nil, 0.0, 1.0, ImVec2(0, 90))
ImGui.ProgressBar((t * 0.4) % 1.0, ImVec2(-1, 0))
ImGui.Text("frame %d", frame)
ImGui.End()
end }
for i = 30, 149 do table.insert(scenes.plot.shots, i) end
scenes.drawlist = { crop = { 0, 0, 380, 300 }, shots = { 30 }, mouse = { 1900, 1060 }, draw = function()
Pos(10, 10, 360, 280)
ImGui.Begin("Custom drawing")
local dl = ImGui.GetWindowDrawList()
local p = ImGui.GetCursorScreenPos()
dl:AddRectFilled(p, p + ImVec2(100, 100), G.IM_COL32(255, 120, 60, 255), 12.0)
dl:AddCircleFilled(p + ImVec2(170, 50), 45, G.IM_COL32(80, 160, 255, 255))
dl:AddTriangleFilled(p + ImVec2(230, 100), p + ImVec2(280, 0), p + ImVec2(330, 100), G.IM_COL32(120, 220, 120, 255))
dl:AddBezierCubic(p + ImVec2(0, 140), p + ImVec2(100, 100), p + ImVec2(200, 220), p + ImVec2(330, 150), G.IM_COL32(255, 255, 0, 255), 3.0)
dl:AddText(p + ImVec2(0, 190), G.IM_COL32(255, 255, 255, 255), "ImDrawList: lines, shapes, text")
ImGui.Dummy(ImVec2(330, 220))
ImGui.End()
end }
-- README hero: same "Debug" window in the dark and light styles (like upstream's README)
local dbg_buf = ImGui.NewTextBuffer("quick brown fox", 256)
local function DebugWindow(title)
ImGui.Begin(title)
ImGui.Text("Hello, world %d", 123)
ImGui.Button("Save")
ImGui.InputText("string", dbg_buf, 256)
ImGui.SliderFloat("float", 0.6, 0.0, 1.0)
ImGui.End()
end
scenes.debug = { crop = { 0, 0, 540, 150 }, shots = { 30 }, mouse = { 140, 113 }, cursor = true, draw = function()
Pos(10, 10, 250, 130); DebugWindow("Debug##dark")
local saved = {}
for i, c in pairs(ImGui.GetStyle().Colors) do saved[i] = ImVec4(c.x, c.y, c.z, c.w) end
ImGui.StyleColorsLight()
Pos(280, 10, 250, 130); DebugWindow("Debug##light")
for i, c in pairs(saved) do G.ImVec4_Copy(ImGui.GetStyle().Colors[i], c) end
end }
-- README "My First Tool" (verbatim from README.md / t_readme.luau), with the color picker popup opened by a click
local my_tool_active, tool_color = true, ImVec4(0.455, 0.282, 0.455, 1.0)
local samples = {}
local btn = nil
scenes.tool = { crop = { 0, 0, 560, 420 }, shots = { 40 }, cursor = true,
mouse = function(frame) if frame < 16 then return btn and btn[1] or 5, btn and btn[2] or 5 end return 300, 240 end,
button = function(frame) if frame == 12 then return true elseif frame == 13 then return false end end,
draw = function()
io.ConfigColorEditFlags = bit32.bor(G.ImGuiColorEditFlags.PickerHueWheel, G.ImGuiColorEditFlags.Uint8, G.ImGuiColorEditFlags.DisplayRGB, G.ImGuiColorEditFlags.InputRGB)
Pos(10, 10, 340, 330)
-- [snippet:tool]
-- Create a window called "My First Tool", with a menu bar.
my_tool_active = ImGui.Begin("My First Tool", my_tool_active, G.ImGuiWindowFlags.MenuBar)
if ImGui.BeginMenuBar() then
if ImGui.BeginMenu("File") then
if ImGui.MenuItem("Open..", "Ctrl+O") then --[[ Do stuff ]] end
if ImGui.MenuItem("Save", "Ctrl+S") then --[[ Do stuff ]] end
if ImGui.MenuItem("Close", "Ctrl+W") then my_tool_active = false end
ImGui.EndMenu()
end
ImGui.EndMenuBar()
end
-- Edit a color stored as 4 floats
local p, w, h = ImGui.GetCursorScreenPos(), ImGui.CalcItemWidth(), ImGui.GetFrameHeight()
btn = { p.x + w - h * 0.5, p.y + h * 0.5 }
ImGui.ColorEdit4("Color", tool_color)
-- Generate samples and plot them
for n = 1, 100 do samples[n] = math.sin((n - 1) * 0.2 + ImGui.GetTime() * 1.5) end
ImGui.PlotLines("Samples", samples, nil, 100)
-- Display contents in a scrolling region
ImGui.TextColored(ImVec4(1, 1, 0, 1), "Important Stuff")
ImGui.BeginChild("Scrolling")
for n = 0, 49 do ImGui.Text("%04d: Some text", n) end
ImGui.EndChild()
ImGui.End()
-- [/snippet]
end }
local SC = assert(scenes[SCENE], "unknown SCENE")
io.MouseDrawCursor = SC.cursor == true
-- ---------------------------------------------------------------- compositor
local CX, CY, CW, CH = SC.crop[1], SC.crop[2], SC.crop[3], SC.crop[4]
local function composite(gui)
local screen = buffer.create(CW * CH * 3)
for i = 0, CW * CH - 1 do buffer.writeu8(screen, i * 3, BG[1]); buffer.writeu8(screen, i * 3 + 1, BG[2]); buffer.writeu8(screen, i * 3 + 2, BG[3]) end
local function walk(inst, ox, oy, kx0, ky0, kx1, ky1)
local kids = inst:GetChildren()
table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end)
for _, c in ipairs(kids) do
if c.Visible ~= false then
local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0)
local cx0, cy0, cx1, cy1 = kx0, ky0, kx1, ky1
if c.ClipsDescendants then
cx0, cy0 = math.max(cx0, x), math.max(cy0, y)
cx1, cy1 = math.min(cx1, x + c.Size.X), math.min(cy1, y + c.Size.Y)
end
if c.ClassName == "ImageLabel" then
local img = c.ImageContent.Object
local px, sz = img.Pixels, img.Size.X
for ty = 0, sz - 1 do
local sy = y + ty
if sy >= ky0 and sy < ky1 then
for tx = 0, sz - 1 do
local sx = x + tx
if sx >= kx0 and sx < kx1 then
local o = (ty * sz + tx) * 4
local a = buffer.readu8(px, o + 3)
if a > 0 then
local d = ((sy - CY) * CW + (sx - CX)) * 3
local fa = a / 255
for ch = 0, 2 do
local v = buffer.readu8(px, o + ch) * fa + buffer.readu8(screen, d + ch) * (1 - fa)
buffer.writeu8(screen, d + ch, math.floor(v + 0.5))
end
end
end
end
end
end
end
walk(c, x, y, cx0, cy0, cx1, cy1)
end
end
end
walk(gui, 0, 0, CX, CY, CX + CW, CY + CH)
return screen
end
local shoot = {}
for _, s in ipairs(SC.shots) do shoot[s] = true end
local frame = 0
game:GetService("RunService").Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
io.DeltaTime = 1 / 30
if type(SC.mouse) == "function" then io:AddMousePosEvent(SC.mouse(frame)) else io:AddMousePosEvent(SC.mouse[1], SC.mouse[2]) end
local b = SC.button and SC.button(frame)
if b ~= nil then io:AddMouseButtonEvent(0, b) end
ImGui.NewFrame()
SC.draw(frame)
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if shoot[frame] then
local screen = composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui"))
print("SIZE", CW, CH)
for y = 0, CH - 1 do
local t = table.create(CW * 3)
for i = 0, CW * 3 - 1 do t[i + 1] = string.format("%02x", buffer.readu8(screen, y * CW * 3 + i)) end
print("PX", frame, y, table.concat(t))
end
end
end)