This commit is contained in:
4DBug
2026-10-07 15:12:59 -05:00
parent f5827282e7
commit 693ced0511
20 changed files with 36502 additions and 805 deletions
+36165
View File
File diff suppressed because it is too large Load Diff
-16
View File
@@ -1,16 +0,0 @@
function love.conf(t)
t.console = true
t.window.resizable = true
t.window.msaa = 4
t.window.width = 1920
t.window.height = 960
if love._version_major > 11 then
t.window.depth = true
else
t.window.depth = 24
end
t.window.vsync = true
t.highdpi = true
end
+14
View File
@@ -0,0 +1,14 @@
{
"name": "imgui",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"ImGui": { "$path": "build/ImGui.luau" }
},
"StarterPlayer": {
"StarterPlayerScripts": {
"ImGuiMain": { "$path": "main.client.luau" }
}
}
}
}
-524
View File
@@ -1,524 +0,0 @@
--- ImGui Backend for Love2D
--- Structurally mirrors imgui_impl_gmod.lua
local love = love
local ImGui_ImplLOVE_GetBackendData
local ImGui_ImplLOVE_UpdateTexture
local ImGui_ImplLOVE_RenderDrawData
local ImGui_ImplLOVE_Shutdown
local CURSOR_MAP = {
[ImGuiMouseCursor.None] = nil,
[ImGuiMouseCursor.Arrow] = "arrow",
[ImGuiMouseCursor.TextInput] = "ibeam",
[ImGuiMouseCursor.ResizeAll] = "sizeall",
[ImGuiMouseCursor.ResizeNS] = "sizens",
[ImGuiMouseCursor.ResizeEW] = "sizewe",
[ImGuiMouseCursor.ResizeNESW] = "sizenesw",
[ImGuiMouseCursor.ResizeNWSE] = "sizenwse",
[ImGuiMouseCursor.Hand] = "hand",
[ImGuiMouseCursor.Wait] = "wait",
[ImGuiMouseCursor.Progress] = "waitarrow",
[ImGuiMouseCursor.NotAllowed] = "no",
}
local KEY_MAP = {
["return"] = ImGuiKey.Enter, ["escape"] = ImGuiKey.Escape,
["backspace"] = ImGuiKey.Backspace, ["tab"] = ImGuiKey.Tab,
["space"] = ImGuiKey.Space, ["enter"] = ImGuiKey.KeypadEnter,
["up"] = ImGuiKey.UpArrow, ["down"] = ImGuiKey.DownArrow,
["left"] = ImGuiKey.LeftArrow, ["right"] = ImGuiKey.RightArrow,
["home"] = ImGuiKey.Home, ["end"] = ImGuiKey.End,
["pageup"] = ImGuiKey.PageUp, ["pagedown"] = ImGuiKey.PageDown,
["insert"] = ImGuiKey.Insert, ["delete"] = ImGuiKey.Delete,
["lctrl"] = ImGuiKey.LeftCtrl, ["rctrl"] = ImGuiKey.RightCtrl,
["lshift"] = ImGuiKey.LeftShift, ["rshift"] = ImGuiKey.RightShift,
["lalt"] = ImGuiKey.LeftAlt, ["ralt"] = ImGuiKey.RightAlt,
["lgui"] = ImGuiKey.LeftSuper, ["rgui"] = ImGuiKey.RightSuper,
}
for i = 0, 9 do KEY_MAP[tostring(i)] = ImGuiKey["K" .. i] end
for i = 1, 12 do KEY_MAP["f" .. i] = ImGuiKey["F" .. i] end
for c in ("abcdefghijklmnopqrstuvwxyz"):gmatch(".") do
KEY_MAP[c] = ImGuiKey[c:upper()]
end
local function ImGui_ImplLOVE_UpdateKeyModifiers()
local io = ImGui.GetIO()
io:AddKeyEvent(ImGuiMod_Ctrl, love.keyboard.isDown("lctrl", "rctrl"))
io:AddKeyEvent(ImGuiMod_Shift, love.keyboard.isDown("lshift", "rshift"))
io:AddKeyEvent(ImGuiMod_Alt, love.keyboard.isDown("lalt", "ralt"))
io:AddKeyEvent(ImGuiMod_Super, love.keyboard.isDown("lgui", "rgui"))
end
--- @return table?
function ImGui_ImplLOVE_GetBackendData()
return ImGui.GetCurrentContext() and ImGui.GetIO().BackendPlatformUserData or nil
end
---------------------------------------------------------
-- VIEWPORT STUBS
-- Required for ImGui BackendPlatformHasViewports logic
---------------------------------------------------------
local function ImGui_ImplLOVE_CreateWindow(viewport)
-- STUB: Triggered when ImGui creates a new multi-viewport window (e.g., dragging a panel outside main window).
-- If your LÖVE fork supports multiple OS windows (or if managing internal canvases), initialize it here.
local vd = { WindowID = nil, Owned = true }
viewport.PlatformUserData = vd
viewport.PlatformHandle = vd
viewport.PlatformHandleRaw = vd
end
local function ImGui_ImplLOVE_DestroyWindow(viewport)
-- STUB: Destroy the OS-level window or release the canvas associated with this viewport.
viewport.PlatformUserData = nil
viewport.PlatformHandle = nil
end
local function ImGui_ImplLOVE_ShowWindow(viewport)
-- STUB: OS-level call to make the window visible.
end
local function ImGui_ImplLOVE_SetWindowPos(viewport, pos)
-- STUB: Set the OS-level window position (pos.x, pos.y).
end
local function ImGui_ImplLOVE_GetWindowPos(viewport)
-- STUB: Return the OS-level window position as ImVec2(x, y).
return ImVec2(0, 0)
end
local function ImGui_ImplLOVE_SetWindowSize(viewport, size)
-- STUB: Resize the OS-level window to (size.x, size.y).
end
local function ImGui_ImplLOVE_GetWindowSize(viewport)
-- STUB: Return the OS-level window size.
return ImVec2(800, 600)
end
local function ImGui_ImplLOVE_SetWindowFocus(viewport)
-- STUB: Bring the OS-level window to focus.
end
local function ImGui_ImplLOVE_GetWindowFocus(viewport)
-- STUB: Return true if this OS-level window has focus.
return false
end
local function ImGui_ImplLOVE_SetWindowTitle(viewport, title)
-- STUB: Set the title of the OS-level window.
end
local function ImGui_ImplLOVE_RenderWindow(viewport)
-- STUB: Render routine for the secondary viewport.
-- Set render target to this window's canvas/context, then call ImGui_ImplLOVE_RenderDrawData(viewport.DrawData)
end
local function ImGui_ImplLOVE_SwapBuffers(viewport)
-- STUB: OS-level call to swap buffers for this specific window context.
end
local function ImGui_ImplGMOD_UpdateMonitors()
local bd = ImGui_ImplLOVE_GetBackendData()
local io = ImGui.GetPlatformIO()
io.Monitors:resize(0)
local imgui_monitor = ImGuiPlatformMonitor()
ImVec2_Copy(imgui_monitor.MainSize, ImVec2(800, 600))
ImVec2_Copy(imgui_monitor.WorkSize, ImVec2(800, 600))
io.Monitors:push_back(imgui_monitor)
bd.WantUpdateMonitors = false
end
local function ImGui_ImplLOVE_InitMultiViewportSupport()
local platform_io = ImGui.GetPlatformIO()
platform_io.Platform_CreateWindow = ImGui_ImplLOVE_CreateWindow
platform_io.Platform_DestroyWindow = ImGui_ImplLOVE_DestroyWindow
platform_io.Platform_ShowWindow = ImGui_ImplLOVE_ShowWindow
platform_io.Platform_SetWindowPos = ImGui_ImplLOVE_SetWindowPos
platform_io.Platform_GetWindowPos = ImGui_ImplLOVE_GetWindowPos
platform_io.Platform_SetWindowSize = ImGui_ImplLOVE_SetWindowSize
platform_io.Platform_GetWindowSize = ImGui_ImplLOVE_GetWindowSize
platform_io.Platform_SetWindowFocus = ImGui_ImplLOVE_SetWindowFocus
platform_io.Platform_GetWindowFocus = ImGui_ImplLOVE_GetWindowFocus
platform_io.Platform_SetWindowTitle = ImGui_ImplLOVE_SetWindowTitle
platform_io.Renderer_RenderWindow = ImGui_ImplLOVE_RenderWindow
platform_io.Renderer_SwapBuffers = ImGui_ImplLOVE_SwapBuffers
local main_viewport = ImGui.GetMainViewport()
main_viewport.PlatformUserData = { Owned = false }
end
---------------------------------------------------------
-- PLATFORM DATA & CALLBACKS
---------------------------------------------------------
local function ImGui_ImplLOVE_PlatformSetImeData(ctx, vp, data)
-- Unlike GMod's invisible VGUI TextEntry workaround[cite: 1],
-- Love2D has native IME support we can toggle directly.
if data.WantVisible or data.WantTextInput then
love.keyboard.setTextInput(true, data.InputPos.x, data.InputPos.y, 1, data.InputLineHeight)
else
love.keyboard.setTextInput(false)
end
end
local function ImGui_ImplLOVE_PlatformSetClipboardText(ctx, text)
love.system.setClipboardText(text)
end
local function ImGui_ImplLOVE_PlatformGetClipboardText(ctx)
return love.system.getClipboardText()
end
local function ImGui_ImplLOVE_OpenInShellFn(ctx, path)
love.system.openURL(path)
end
---------------------------------------------------------
-- TEXTURE MANAGEMENT
---------------------------------------------------------
local ffi = require("ffi")
function ImGui_ImplLOVE_UpdateTexture(tex)
if tex.Status == ImTextureStatus.WantCreate then
local w, h = tex.Width, tex.Height
local row, row_base = tex:GetPixelsAt(0, 0)
local imgData = love.image.newImageData(w, h)
local ptr = ffi.cast("uint8_t*", imgData:getFFIPointer())
-- Iterate through the Lua table to manually assign the bytes to the C pointer
local total_bytes = w * h * 4
for i = 0, total_bytes - 1 do
ptr[i] = row[row_base + i]
end
local loveTex = love.graphics.newImage(imgData)
loveTex:setFilter("nearest", "nearest")
tex.BackendUserData = loveTex
tex:SetTexID(1)
tex:SetStatus(ImTextureStatus.OK)
elseif tex.Status == ImTextureStatus.WantUpdates then
local loveTex = tex.BackendUserData
IM_ASSERT(loveTex ~= nil)
for _, r in tex.Updates:iter() do
local w, h = r.w, r.h
local updateData = love.image.newImageData(w, h)
local dst = ffi.cast("uint8_t*", updateData:getFFIPointer())
for y = 0, h - 1 do
local src, src_base = tex:GetPixelsAt(r.x, r.y + y)
local dst_base = y * w * 4
for i = 0, w * 4 - 1 do
dst[dst_base + i] = src[src_base + i]
end
end
loveTex:replacePixels(
updateData,
nil,
1,
r.x,
r.y,
false
)
updateData:release()
end
tex:SetStatus(ImTextureStatus.OK)
elseif tex.Status == ImTextureStatus.WantDestroy then
if tex.BackendUserData then
tex.BackendUserData:release()
tex.BackendUserData = nil
end
tex:SetTexID(ImTextureID_Invalid)
tex:SetStatus(ImTextureStatus.Destroyed)
end
end
---------------------------------------------------------
-- RENDERING
---------------------------------------------------------
local rshift, band = bit32.rshift, bit32.band
local function u32ToRGBA(col)
-- Extract bytes from u32 integer just like GMod implementation[cite: 1]
return band(col, 0xFF) / 255,
band(rshift(col, 8), 0xFF) / 255,
band(rshift(col, 16), 0xFF) / 255,
band(rshift(col, 24), 0xFF) / 255
end
function ImGui_ImplLOVE_RenderDrawData(draw_data)
if draw_data.DisplaySize.x <= 0.0 or draw_data.DisplaySize.y <= 0.0 then return end
if draw_data.Textures ~= nil then
for _, tex in draw_data.Textures:iter() do
if tex.Status ~= ImTextureStatus.OK then
ImGui_ImplLOVE_UpdateTexture(tex)
end
end
end
love.graphics.push("all")
love.graphics.setBlendMode("alpha", "alphamultiply")
local format = {
{"VertexPosition", "float", 2},
{"VertexTexCoord", "float", 2},
{"VertexColor", "byte", 4},
}
local idx_data, vtx_data
for _, draw_list in draw_data.CmdLists:iter() do
idx_data = draw_list.IdxBuffer; vtx_data = draw_list.VtxBuffer
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 and pcmd.ClipRect.z > pcmd.ClipRect.x and pcmd.ClipRect.w > pcmd.ClipRect.y then
-- LÖVE Scissor mapping
love.graphics.setScissor(pcmd.ClipRect.x, pcmd.ClipRect.y,
pcmd.ClipRect.z - pcmd.ClipRect.x, pcmd.ClipRect.w - pcmd.ClipRect.y)
-- Create a temporary table for vertices
-- Note: For production use, passing `ByteData` with an FFI pointer directly to `newMesh` is significantly faster.
local vertices = {}
for i = 0, pcmd.ElemCount - 1, 3 do
local idx0 = idx_data[pcmd.IdxOffset + 1 + i]
local idx1 = idx_data[pcmd.IdxOffset + 2 + i]
local idx2 = idx_data[pcmd.IdxOffset + 3 + i]
local vtx0 = vtx_data[pcmd.VtxOffset + idx0]
local vtx1 = vtx_data[pcmd.VtxOffset + idx1]
local vtx2 = vtx_data[pcmd.VtxOffset + idx2]
local r0, g0, b0, a0 = u32ToRGBA(vtx0[3])
table.insert(vertices, {vtx0[1][1], vtx0[1][2], vtx0[2][1], vtx0[2][2], r0, g0, b0, a0})
local r1, g1, b1, a1 = u32ToRGBA(vtx1[3])
table.insert(vertices, {vtx1[1][1], vtx1[1][2], vtx1[2][1], vtx1[2][2], r1, g1, b1, a1})
local r2, g2, b2, a2 = u32ToRGBA(vtx2[3])
table.insert(vertices, {vtx2[1][1], vtx2[1][2], vtx2[2][1], vtx2[2][2], r2, g2, b2, a2})
end
local mesh = love.graphics.newMesh(format, vertices, "triangles")
-- The Texture object is stored in BackendUserData
local tex_id = pcmd:GetTexID()
local tex = ImGui.GetPlatformIO().Textures[tex_id]
if tex and tex.BackendUserData then
mesh:setTexture(tex.BackendUserData)
end
love.graphics.draw(mesh)
end
end
end
love.graphics.setScissor()
love.graphics.pop()
end
---------------------------------------------------------
-- CORE LIFECYCLE
---------------------------------------------------------
function ImGui_ImplLOVE_Init()
local io = ImGui.GetIO()
local bd = { Time = love.timer.getTime() }
io.BackendPlatformUserData = bd
--io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.PlatformHasViewports)
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures)
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasVtxOffset)
--io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasViewports)
local platform_io = ImGui.GetPlatformIO()
platform_io.Platform_SetImeDataFn = ImGui_ImplLOVE_PlatformSetImeData
platform_io.Platform_SetClipboardTextFn = ImGui_ImplLOVE_PlatformSetClipboardText
platform_io.Platform_GetClipboardTextFn = ImGui_ImplLOVE_PlatformGetClipboardText
platform_io.Platform_OpenInShellFn = ImGui_ImplLOVE_OpenInShellFn
--ImGui_ImplLOVE_InitMultiViewportSupport()
ImGui_ImplGMOD_UpdateMonitors()
end
function ImGui_ImplLOVE_Shutdown()
local io = ImGui.GetIO()
ImGui.DestroyPlatformWindows()
io.BackendPlatformUserData = nil
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(
ImGuiBackendFlags.HasMouseCursors,
ImGuiBackendFlags.PlatformHasViewports,
ImGuiBackendFlags.RendererHasViewports
)))
end
function ImGui_ImplLOVE_NewFrame()
local io = ImGui.GetIO()
local bd = ImGui_ImplLOVE_GetBackendData()
local w, h = love.graphics.getDimensions()
ImVec2_CopyV(io.DisplaySize, w, h)
local current_time = love.timer.getTime()
io.DeltaTime = (bd.Time > 0.0) and (current_time - bd.Time) or (1.0 / 60.0)
bd.Time = current_time
-- Update Cursor
if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoMouseCursorChange) == 0 then
local cursor = CURSOR_MAP[ImGui.GetMouseCursor()]
if cursor then
love.mouse.setCursor(love.mouse.getSystemCursor(cursor))
else
love.mouse.setCursor()
end
end
end
---------------------------------------------------------
-- LOVE EVENT HOOKS (Call these from main.lua)
---------------------------------------------------------
local function Hook_MouseMoved(x, y)
local io = ImGui.GetIO()
io:AddMouseSourceEvent(ImGuiMouseSource.Mouse)
io:AddMousePosEvent(x, y)
end
local function Hook_MousePressed(x, y, button)
local io = ImGui.GetIO()
io:AddMouseSourceEvent(ImGuiMouseSource.Mouse)
-- LÖVE passes buttons as 1=left, 2=right, 3=middle. ImGui uses 0=left, 1=right, 2=middle.
if button <= 3 then
local ig_btn = (button == 1) and ImGuiMouseButton.Left or (button == 2 and ImGuiMouseButton.Right or ImGuiMouseButton.Middle)
io:AddMouseButtonEvent(ig_btn, true)
end
end
local function Hook_MouseReleased(x, y, button)
local io = ImGui.GetIO()
if button <= 3 then
local ig_btn = (button == 1) and ImGuiMouseButton.Left or (button == 2 and ImGuiMouseButton.Right or ImGuiMouseButton.Middle)
io:AddMouseButtonEvent(ig_btn, false)
end
end
local function Hook_WheelMoved(x, y)
local io = ImGui.GetIO()
io:AddMouseWheelEvent(x, y)
end
local function Hook_KeyPressed(key)
local io = ImGui.GetIO()
ImGui_ImplLOVE_UpdateKeyModifiers()
if KEY_MAP[key] then
io:AddKeyEvent(KEY_MAP[key], true)
end
end
local function Hook_KeyReleased(key)
local io = ImGui.GetIO()
ImGui_ImplLOVE_UpdateKeyModifiers()
if KEY_MAP[key] then
io:AddKeyEvent(KEY_MAP[key], false)
end
end
local function Hook_TextInput(text)
local io = ImGui.GetIO()
for _, code in utf8.codes(text) do
io:AddInputCharacter(code)
end
end
--Implementing some bullshit
function ImStd.ImFileOpen(_filename, _mode)
if _mode == "rb" then _mode = "r" end
local file, _ = love.filesystem.newFile(_filename, _mode)
return file
end
function ImStd.ImFileClose(f)
f:close()
end
function ImStd.ImFileGetSize(f)
return f:getSize()
end
function ImStd.ImFileRead(f, data, count)
local CHUNK_SIZE = 8000 -- We don't read byte by byte
local offset = 0
while offset < count do
local read_size = math.min(CHUNK_SIZE, count - offset)
local str = f:read(read_size)
-- Handle EOF or read failure gracefully
if not str or #str == 0 then break end
-- Iterate based on actual bytes read without risking stack overflow
for i = 1, #str do
data[offset + i] = string.byte(str, i)
end
offset = offset + #str
end
end
function ImStd.ImFileLoadToMemory(filename, mode)
local f = ImStd.ImFileOpen(filename, mode)
if not f then return end
local file_size = ImStd.ImFileGetSize(f)
if file_size <= 0 then
ImStd.ImFileClose(f)
return
end
local file_data = {}
ImStd.ImFileRead(f, file_data, file_size)
if #file_data == 0 then
ImStd.ImFileClose(f)
return
end
ImStd.ImFileClose(f)
return file_data, file_size
end
ImGui_ImplLove = {
Init = ImGui_ImplLOVE_Init,
Shutdown = ImGui_ImplLOVE_Shutdown,
NewFrame = ImGui_ImplLOVE_NewFrame,
RenderDrawData = ImGui_ImplLOVE_RenderDrawData,
-- Event hooks to connect in love.* callbacks
MouseMoved = Hook_MouseMoved,
MousePressed = Hook_MousePressed,
MouseReleased = Hook_MouseReleased,
WheelMoved = Hook_WheelMoved,
KeyPressed = Hook_KeyPressed,
KeyReleased = Hook_KeyReleased,
TextInput = Hook_TextInput,
}
+252 -86
View File
@@ -1,26 +1,17 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Backend for Roblox
--- Structurally mirrors imgui_impl_love.lua
--- - Renderer: software rasterizer drawing into a CanvasDraw canvas (EditableImage). CanvasDraw's own shape
--- functions can't do per-vertex colours, texture tinting or clip rects, so triangles are rasterized straight
--- into `Canvas.Buffer` (same RGBA8 layout as ImU32) and pushed with `Canvas:Render()`.
--- - Renderer: exact software rasterizer (1:1 with imgui's triangles) into 256x256 EditableImage tiles at native
--- screen resolution; only tiles whose content changed are redrawn/uploaded (see RenderDrawData).
--- - Platform: UserInputService
--- - Files: an in-memory file system (VFS), pre-filled with the fonts from imgui_impl_roblox_fonts.lua
---
--- Usage (LocalScript):
--- local G = require(ReplicatedStorage.imgui)
--- G.ImGui.CreateContext()
--- G.ImGui_ImplRoblox.Init(require(ReplicatedStorage.CanvasDraw))
--- Requires "Allow Mesh / Image APIs" (EditableImage) in game settings.
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local AssetService = game:GetService("AssetService")
local floor, ceil, min, max = math.floor, math.ceil, math.min, math.max
local readu8, writeu8, writeu32 = buffer.readu8, buffer.writeu8, buffer.writeu32
-- EditableImage size limit. Bigger screens get a downscaled canvas that is stretched to fit.
-- Lower it to trade sharpness for speed.
local MAX_CANVAS_SIZE = 1024
local readu8, readu32, writeu32, readstring = buffer.readu8, buffer.readu32, buffer.writeu32, buffer.readstring
local band, rshift = bit32.band, bit32.rshift
local ImGui_ImplRoblox_GetBackendData
local ImGui_ImplRoblox_UpdateTexture
@@ -196,49 +187,79 @@ function ImGui_ImplRoblox_UpdateTexture(tex)
end
---------------------------------------------------------
-- RENDERING (software rasterizer into the canvas buffer)
-- RENDERING (software rasterizer into tile buffers)
---------------------------------------------------------
-- Buffer layout: 4 bytes per pixel, R G B A, row-major, 0-based offsets. ImU32 colours are already R | G<<8 | B<<16 | A<<24.
-- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui.
local buf, buf_w -- current canvas buffer/width, set at the start of RenderDrawData
local buf, buf_w -- buffer/width of the tile being drawn
local TILE_SIZE -- Vector2, set below
-- Blends one pixel (r, g, b, a are 0..255)
-- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case.
-- FillSpan below inlines the same math with per-span constants - keep the two in sync.
local function BlendPixel(o, r, g, b, a)
if a >= 255 then
writeu32(buf, o, floor(r + 0.5) + floor(g + 0.5) * 256 + floor(b + 0.5) * 65536 + 4278190080)
return
end
if a <= 0 then return end
local da = readu8(buf, o + 3)
local d = readu32(buf, o)
local da = rshift(d, 24)
if da == 0 then
writeu32(buf, o, floor(r + 0.5) + floor(g + 0.5) * 256 + floor(b + 0.5) * 65536 + floor(a + 0.5) * 16777216)
elseif da == 255 then
return
end
local sa = a / 255
local dr, dg, db = readu8(buf, o), readu8(buf, o + 1), readu8(buf, o + 2)
writeu8(buf, o, dr + (r - dr) * sa + 0.5)
writeu8(buf, o + 1, dg + (g - dg) * sa + 0.5)
writeu8(buf, o + 2, db + (b - db) * sa + 0.5)
local dr, dg, db = band(d, 255), band(rshift(d, 8), 255), band(rshift(d, 16), 255)
if da == 255 then
local ns = 1 - sa
writeu32(buf, o, floor(r * sa + dr * ns + 0.5) + floor(g * sa + dg * ns + 0.5) * 256 + floor(b * sa + db * ns + 0.5) * 65536 + 4278190080)
else
local sa = a / 255
local k = (da / 255) * (1 - sa)
local k = da * ((1 - sa) / 255)
local oa = sa + k
writeu8(buf, o, (r * sa + readu8(buf, o) * k) / oa + 0.5)
writeu8(buf, o + 1, (g * sa + readu8(buf, o + 1) * k) / oa + 0.5)
writeu8(buf, o + 2, (b * sa + readu8(buf, o + 2) * k) / oa + 0.5)
writeu8(buf, o + 3, oa * 255 + 0.5)
local inv = 1 / oa
writeu32(buf, o, floor((r * sa + dr * k) * inv + 0.5) + floor((g * sa + dg * k) * inv + 0.5) * 256
+ floor((b * sa + db * k) * inv + 0.5) * 65536 + floor(oa * 255 + 0.5) * 16777216)
end
end
-- Fills pixels [x0, x1) of row y with one colour
local function FillSpan(y, x0, x1, r, g, b, a)
if a <= 0 then return end
local o = (y * buf_w + x0) * 4
local o0 = (y * buf_w + x0) * 4
local o1 = o0 + (x1 - x0 - 1) * 4
if a >= 255 then
local packed = floor(r + 0.5) + floor(g + 0.5) * 256 + floor(b + 0.5) * 65536 + 4278190080
for x = 0, x1 - x0 - 1 do writeu32(buf, o + x * 4, packed) end
for o = o0, o1, 4 do writeu32(buf, o, packed) end
return
end
local packed0 = floor(r + 0.5) + floor(g + 0.5) * 256 + floor(b + 0.5) * 65536 + floor(a + 0.5) * 16777216
local sa = a / 255
local ns = 1 - sa
local c1 = ns / 255
local rs, gs, bs = r * sa, g * sa, b * sa
local last_d, last_v = -1, 0 -- UI backgrounds are mostly flat: reuse the previous result for the same destination
for o = o0, o1, 4 do
local d = readu32(buf, o)
local da = rshift(d, 24)
if d == last_d then
writeu32(buf, o, last_v)
elseif da == 0 then
writeu32(buf, o, packed0)
else
for x = 0, x1 - x0 - 1 do BlendPixel(o + x * 4, r, g, b, a) end
last_d = d
local dr, dg, db = band(d, 255), band(rshift(d, 8), 255), band(rshift(d, 16), 255)
if da == 255 then
last_v = floor(rs + dr * ns + 0.5) + floor(gs + dg * ns + 0.5) * 256 + floor(bs + db * ns + 0.5) * 65536 + 4278190080
else
local k = da * c1
local oa = sa + k
local inv = 1 / oa
last_v = floor((rs + dr * k) * inv + 0.5) + floor((gs + dg * k) * inv + 0.5) * 256
+ floor((bs + db * k) * inv + 0.5) * 65536 + floor(oa * 255 + 0.5) * 16777216
end
writeu32(buf, o, last_v)
end
end
end
@@ -270,7 +291,26 @@ local function DrawRect(x0, y0, x1, y1, u0, v0, u1, v1, col, t, cx0, cy0, cx1, c
local tr, tg, tb, ta = SampleTexture(t, u0, v0)
r, g, b, a = r * tr / 255, g * tg / 255, b * tb / 255, a * ta / 255
end
for y = py0, py1 - 1 do FillSpan(y, px0, px1, r, g, b, a) end
if a <= 0 then return end
-- Rows of a flat rect usually blend over identical rows: when this row's destination equals the previous
-- row's (exact string compare), the result is identical too, so memcpy the previous row's result.
local len = (px1 - px0) * 4
local prev_dst, prev_o = nil, 0
for y = py0, py1 - 1 do
local o = (y * buf_w + px0) * 4
if a >= 255 then
if prev_dst then buffer.copy(buf, o, buf, prev_o, len) else FillSpan(y, px0, px1, r, g, b, a); prev_dst = true end
else
local dst = readstring(buf, o, len)
if dst == prev_dst then
buffer.copy(buf, o, buf, prev_o, len)
else
FillSpan(y, px0, px1, r, g, b, a)
prev_dst = dst
end
end
prev_o = o
end
return
end
@@ -383,24 +423,87 @@ local function TryDrawRect(va, vb, vc, vd, t, cx0, cy0, cx1, cy1, ox, oy)
return true
end
-- Tiles: the screen is covered by TILE x TILE EditableImages (one ImageLabel each). Every frame each primitive
-- (triangle, or axis aligned quad) is hashed into the tiles it touches; only tiles whose hash changed are
-- re-rasterized and uploaded. Static UI therefore costs ~nothing to draw, and the result is pixel-identical to a
-- full redraw (the hash covers everything that affects a tile's pixels: geometry, uv, colour, texture, clip, order).
local TILE = 256
TILE_SIZE = Vector2.new(TILE, TILE)
local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile (collision odds ~2^-62)
-- Primitive arrays (reused between frames, no per-frame allocation)
local pr_a, pr_b, pr_c, pr_d, pr_t = {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture
local pr_cx0, pr_cy0, pr_cx1, pr_cy1 = {}, {}, {}, {} -- clip (screen pixels)
local function HashVertex(h1, h2, v)
local p, u, c = v[1], v[2], v[3]
local x, y = p[1], p[2]
local uv = u[1] * 8192 + u[2]
h1 = (h1 * 48271 + x * 4096 + y) % P1
h1 = (h1 * 48271 + uv * 4096) % P1
h1 = (h1 * 48271 + c) % P1
h2 = (h2 * 16807 + y * 4093 + x) % P2
h2 = (h2 * 16807 + uv * 65521) % P2
h2 = (h2 * 16807 + c) % P2
return h1, h2
end
local function GetTile(bd, index, tx, ty)
local tile = bd.Tiles[index]
if tile then return tile end
local img = AssetService:CreateEditableImage({ Size = Vector2.new(TILE, TILE) })
assert(img, "imgui_impl_roblox: EditableImage memory budget exceeded")
local label = Instance.new("ImageLabel")
label.Name = "Tile" .. index
label.BackgroundTransparency = 1
label.BorderSizePixel = 0
label.Size = UDim2.fromOffset(TILE, TILE)
label.Position = UDim2.fromOffset(tx * TILE, ty * TILE)
label.ResampleMode = Enum.ResamplerMode.Pixelated
label.ImageContent = Content.fromObject(img)
label.Visible = false
label.Parent = bd.Gui
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false }
bd.Tiles[index] = tile
return tile
end
local function DestroyTiles(bd)
for _, tile in pairs(bd.Tiles) do tile.Label:Destroy(); tile.Image:Destroy() end
bd.Tiles = {}
end
function ImGui_ImplRoblox_RenderDrawData(draw_data)
local bd = ImGui_ImplRoblox_GetBackendData()
local canvas = bd.Canvas
if draw_data.Textures ~= nil then
for _, tex in draw_data.Textures:iter() do
if tex.Status ~= ImTextureStatus.OK then
ImGui_ImplRoblox_UpdateTexture(tex)
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
end
end
end
canvas:Clear()
buf, buf_w = canvas.Buffer, canvas.CurrentResX
local buf_h = canvas.CurrentResY
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
if sw <= 0 or sh <= 0 then return end
local cols, rows = ceil(sw / TILE), ceil(sh / TILE)
if cols ~= bd.TileCols or rows ~= bd.TileRows then
DestroyTiles(bd)
bd.TileCols, bd.TileRows = cols, rows
end
local ntiles = cols * rows
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
if draw_data.DisplaySize.x > 0.0 and draw_data.DisplaySize.y > 0.0 then
-- Per tile: hashes and the list of primitives touching it
local th1, th2, tn, tl = bd.TH1, bd.TH2, bd.TN, bd.TL
for t = 0, ntiles - 1 do
th1[t], th2[t], tn[t] = 0, 0, 0
if not tl[t] then tl[t] = {} end
end
-- Pass 1: collect primitives, hash them into tiles
local n = 0
for _, draw_list in draw_data.CmdLists:iter() do
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
for _, pcmd in draw_list.CmdBuffer:iter() do
@@ -409,21 +512,64 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), 0), max(floor(clip.y - oy), 0)
local cx1, cy1 = min(floor(clip.z - ox), buf_w), min(floor(clip.w - oy), buf_h)
local cx1, cy1 = min(floor(clip.z - ox), sw), min(floor(clip.w - oy), sh)
if cx1 > cx0 and cy1 > cy0 then
local t = bd.Textures[pcmd:GetTexID()]
local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id]
local ch1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
local ch2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vo = pcmd.VtxOffset
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
while i <= last do
local ia, ic = idx[i], idx[i + 2]
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic
and TryDrawRect(va, vb, vc, vtx[vo + idx[i + 5]], t, cx0, cy0, cx1, cy1, ox, oy) then
local vd = false
local pa, pb, pc = va[1], vb[1], vc[1]
local bx0, by0, bx1, by1
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
local d = vtx[vo + idx[i + 5]]
local pd = d[1]
-- axis aligned quad (TL, TR, BR, BL) with one colour and a matching uv rect
if pa[2] == pb[2] and pb[1] == pc[1] and pc[2] == pd[2] and pd[1] == pa[1] and pa[1] < pb[1] and pa[2] < pd[2] then
local col = va[3]
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
if vb[3] == col and vc[3] == col and d[3] == col
and tb[1] == tc[1] and tb[2] == ta[2] and td[1] == ta[1] and td[2] == tc[2] then
vd = d
bx0, by0 = ceil(pa[1] - ox - 0.5), ceil(pa[2] - oy - 0.5)
bx1, by1 = ceil(pc[1] - ox - 0.5), ceil(pc[2] - oy - 0.5)
end
end
end
if vd then
i = i + 6
else
DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
bx0, by0 = floor(min(pa[1], pb[1], pc[1]) - ox), floor(min(pa[2], pb[2], pc[2]) - oy)
bx1, by1 = ceil(max(pa[1], pb[1], pc[1]) - ox) + 1, ceil(max(pa[2], pb[2], pc[2]) - oy) + 1
i = i + 3
end
if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end
if by1 > cy1 then by1 = cy1 end
if bx0 < bx1 and by0 < by1 then
n = n + 1
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n] = va, vb, vc, vd, t
pr_cx0[n], pr_cy0[n], pr_cx1[n], pr_cy1[n] = cx0, cy0, cx1, cy1
local h1, h2 = HashVertex(ch1, ch2, va)
h1, h2 = HashVertex(h1, h2, vb)
h1, h2 = HashVertex(h1, h2, vc)
if vd then h1, h2 = HashVertex(h1, h2, vd) end
for ty = floor(by0 / TILE), floor((by1 - 1) / TILE) do
for tx = floor(bx0 / TILE), floor((bx1 - 1) / TILE) do
local ti = ty * cols + tx
local k = tn[ti] + 1
tn[ti] = k
tl[ti][k] = n
th1[ti] = (th1[ti] * 48271 + h1) % P1
th2[ti] = (th2[ti] * 16807 + h2 + k) % P2
end
end
end
end
end
@@ -431,50 +577,76 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end
end
canvas:Render()
-- Pass 2: redraw + upload changed tiles
local force = bd.ForceRedraw
bd.ForceRedraw = false
for ty = 0, rows - 1 do
for tx = 0, cols - 1 do
local ti = ty * cols + tx
local count = tn[ti]
local tile = bd.Tiles[ti]
if count == 0 then
if tile and tile.Visible then
tile.Visible = false
tile.Label.Visible = false
tile.H1 = -1
end
else
tile = tile or GetTile(bd, ti, tx, ty)
if force or tile.H1 ~= th1[ti] or tile.H2 ~= th2[ti] then
tile.H1, tile.H2 = th1[ti], th2[ti]
local x0, y0 = tx * TILE, ty * TILE
local x1, y1 = x0 + TILE, y0 + TILE
buf, buf_w = tile.Buffer, TILE
buffer.fill(buf, 0, 0)
local list = tl[ti]
local tox, toy = ox + x0, oy + y0
for k = 1, count do
local p = list[k]
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
if vd then
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
DrawRect(pa[1] - tox, pa[2] - toy, pc[1] - tox, pc[2] - toy, ta[1], ta[2], tc[1], tc[2], va[3], pr_t[p], cx0, cy0, cx1, cy1)
else
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
end
end
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
end
if not tile.Visible then
tile.Visible = true
tile.Label.Visible = true
end
end
end
end
-- drop references so old vertex tables can be collected
for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end
bd.LastPrimCount = n
end
---------------------------------------------------------
-- CORE LIFECYCLE
---------------------------------------------------------
-- Canvas resolution for a given screen size (capped by MAX_CANVAS_SIZE, aspect ratio kept)
local function GetCanvasSize(screen)
local scale = max(1, screen.X / MAX_CANVAS_SIZE, screen.Y / MAX_CANVAS_SIZE)
return max(1, floor(screen.X / scale)), max(1, floor(screen.Y / scale))
end
--- @param CanvasDraw table # require(path.to.CanvasDraw)
--- @param parent Instance? # where the ScreenGui goes, defaults to the local player's PlayerGui
function ImGui_ImplRoblox_Init(CanvasDraw, parent)
assert(CanvasDraw, "ImGui_ImplRoblox.Init: pass the CanvasDraw module, e.g. Init(require(ReplicatedStorage.CanvasDraw))")
function ImGui_ImplRoblox_Init(parent)
local io = ImGui.GetIO()
local gui = Instance.new("ScreenGui")
gui.Name = "ImGui"
gui.IgnoreGuiInset = true -- so GetMouseLocation() and the canvas share the same origin
gui.IgnoreGuiInset = true -- so GetMouseLocation() and the tiles share the same origin
gui.ResetOnSpawn = false
gui.DisplayOrder = 1000
gui.Parent = parent or Players.LocalPlayer:WaitForChild("PlayerGui")
local frame = Instance.new("Frame")
frame.Size = UDim2.fromScale(1, 1)
frame.BackgroundTransparency = 1
frame.Parent = gui
local screen = gui.AbsoluteSize
if screen.X < 1 or screen.Y < 1 then screen = workspace.CurrentCamera.ViewportSize end
local w, h = GetCanvasSize(screen)
local canvas = CanvasDraw.new(frame, Vector2.new(w, h), Color3.new(0, 0, 0))
canvas.AutoRender = false
canvas:SetStretchToFit(true)
canvas:SetClearRGBA(0, 0, 0, 0)
canvas:Clear()
local bd = {
Time = os.clock(), Gui = gui, Canvas = canvas, Connections = {},
Time = os.clock(), Gui = gui, Connections = {},
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
Tiles = {}, TileCols = 0, TileRows = 0, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true,
}
io.BackendPlatformUserData = bd
@@ -484,7 +656,8 @@ function ImGui_ImplRoblox_Init(CanvasDraw, parent)
platform_io.Platform_SetClipboardTextFn = ImGui_ImplRoblox_PlatformSetClipboardText
platform_io.Platform_GetClipboardTextFn = ImGui_ImplRoblox_PlatformGetClipboardText
ImGui_ImplRoblox_UpdateMonitors(w, h)
local screen = gui.AbsoluteSize
ImGui_ImplRoblox_UpdateMonitors(screen.X, screen.Y)
local function connect(signal, fn) table.insert(bd.Connections, signal:Connect(fn)) end
@@ -535,7 +708,7 @@ function ImGui_ImplRoblox_Shutdown()
local io = ImGui.GetIO()
local bd = ImGui_ImplRoblox_GetBackendData()
for _, c in ipairs(bd.Connections) do c:Disconnect() end
bd.Canvas:Destroy()
DestroyTiles(bd)
bd.Gui:Destroy()
io.BackendPlatformUserData = nil
@@ -548,31 +721,24 @@ end
function ImGui_ImplRoblox_NewFrame()
local io = ImGui.GetIO()
local bd = ImGui_ImplRoblox_GetBackendData()
local canvas = bd.Canvas
-- Follow screen size changes
local screen = bd.Gui.AbsoluteSize
if screen.X >= 1 and screen.Y >= 1 then
local w, h = GetCanvasSize(screen)
if w ~= canvas.CurrentResX or h ~= canvas.CurrentResY then
canvas:Resize(Vector2.new(w, h))
local w, h = floor(screen.X), floor(screen.Y)
if w ~= bd.ScreenW or h ~= bd.ScreenH then
bd.ScreenW, bd.ScreenH = w, h
ImGui_ImplRoblox_UpdateMonitors(w, h)
end
end
local w, h = canvas.CurrentResX, canvas.CurrentResY
ImVec2_CopyV(io.DisplaySize, w, h)
local current_time = os.clock()
io.DeltaTime = (current_time > bd.Time) and (current_time - bd.Time) or (1.0 / 60.0)
bd.Time = current_time
-- Mouse position: screen pixels -> canvas pixels (the canvas is stretched over the whole screen)
local m = UserInputService:GetMouseLocation()
local mx, my = m.X * w / max(screen.X, 1), m.Y * h / max(screen.Y, 1)
if mx ~= bd.MouseX or my ~= bd.MouseY then
bd.MouseX, bd.MouseY = mx, my
if m.X ~= bd.MouseX or m.Y ~= bd.MouseY then
bd.MouseX, bd.MouseY = m.X, m.Y
io:AddMouseSourceEvent(ImGuiMouseSource.Mouse)
io:AddMousePosEvent(mx, my)
io:AddMousePosEvent(m.X, m.Y)
end
end
+2 -46
View File
@@ -1,22 +1,6 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env for all imgui ModuleScripts, see init.lua
--- ImGui Sincerely WIP
-- (Core Code)
--- [LuaJIT/LOVE] bit32 polyfill (Luau and Lua 5.2 have it built in). Results are unsigned like real bit32.
if not bit32 then
local bit = require("bit")
local band, bor, bxor, bnot, lshift, rshift = bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift
local U32 = 4294967296
bit32 = {
band = function(...) return band(...) % U32 end,
bor = function(...) return bor(...) % U32 end,
bxor = function(...) return bxor(...) % U32 end,
bnot = function(x) return bnot(x) % U32 end,
lshift = function(x, n) if n >= 32 then return 0 end return lshift(x, n) % U32 end,
rshift = function(x, n) if n >= 32 then return 0 end return rshift(x, n) % U32 end,
}
end
--- Flags:
-- IMGUI_DISABLE_OBSOLETE_FUNCTIONS = true -- This flag is not implemented
@@ -32,37 +16,9 @@ ImGui = {}
ImStd = {} -- Contains functions that originally don't belong to cpp namespaces
--- This executes Lua script at _filename and returns the result of the script.
--- @param _filename string
--- @return any
function IM_INCLUDE(_filename) end
-- IM_INCLUDE(name) is provided by the bundle (see tools/bundle.py): runs the bundled file once, returns its result.
--- [GMod] Platform specific include function
if gmod then
IM_INCLUDE = include
elseif IMGUI_ROOT then
--- [Roblox] files are ModuleScripts somewhere under the `imgui` ModuleScript (IMGUI_ROOT, set by init.lua)
IM_INCLUDE = function(_filename)
return require(IMGUI_ROOT:FindFirstChild((_filename:gsub("%.lua$", "")), true))
end
else
IM_INCLUDE = function(_filename)
return require("imgui." .. _filename:gsub(".lua", ""))
end
end
function ImGui._GetTypeFunc()
local type
if gmod then
-- [GMod] `type` is [detoured](https://wiki.facepunch.com/gmod/Global.type). get the original function.
local upvalue_name
upvalue_name, type = debug.getupvalue(_G.type, 1)
assert(upvalue_name == "C_type")
else
type = getfenv(0).type or _G.type -- [Roblox] _G is not the global table there
end
return type
end
function ImGui._GetTypeFunc() return type end
local function LUA_TableConstructorWrapper(T, size, userdata)
local p = {}
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely WIP
-- (Demo Code)
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely WIP
-- (Draw Code)
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely WIP
-- (Definitions)
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely WIP
-- (internal structures/api)
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely WIP
-- (Tables and Columns Code)
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely WIP
-- (Widgets Code)
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely
-- This is a Lua port of original `imstb_rectpack.h`
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely
-- This is a Lua port of original `imstb_textedit.h`
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely
-- This is a Lua port of original `imstb_truetype.h`
-1
View File
@@ -1,4 +1,3 @@
if _G.__IMGUI_ENV then setfenv(1, _G.__IMGUI_ENV) end -- [Roblox] shared env, see init.lua
--- ImGui Sincerely
-- This is a minimal Lua impl of C-like sscanf, sprintf sub-set
-17
View File
@@ -1,17 +0,0 @@
-- [Roblox] `imgui` is a ModuleScript (this file) whose descendants are the other files as ModuleScripts
-- (what Rojo produces from this folder). ModuleScripts don't share globals, so every imgui file runs inside
-- one shared environment table (_G.__IMGUI_ENV). require() of this module returns that environment.
if game and script then
local env = setmetatable({}, { __index = getfenv(1) })
env.IMGUI_ROOT = script
_G.__IMGUI_ENV = env
require(script:FindFirstChild("imgui"))
require(script:FindFirstChild("imgui_impl_roblox", true))
require(script:FindFirstChild("imgui_demo"))
return env
end
-- LOVE / LuaJIT
require("imgui.imgui")
require("imgui.backend.imgui_impl_love")
require("imgui.imgui_demo")
+3 -6
View File
@@ -1,12 +1,9 @@
-- Roblox version (LocalScript). The LOVE version lives in main_love.lua.
if not game then require("main_love") return end -- running under LOVE: use the LOVE entry point
-- LocalScript. Build the library first: `python3 tools/bundle.py` -> build/ImGui.luau (see default.project.json)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
-- `imgui` and CanvasDraw (https://create.roblox.com/store/asset/8314640895/CanvasDraw) are ModuleScripts in ReplicatedStorage
local G = require(ReplicatedStorage:WaitForChild("imgui")) -- the shared imgui environment
local CanvasDraw = require(ReplicatedStorage:WaitForChild("CanvasDraw"))
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec4, ImGuiConfigFlags, ImGui_ImplRoblox = G.ImGui, G.ImVec4, G.ImGuiConfigFlags, G.ImGui_ImplRoblox
@@ -26,7 +23,7 @@ local style = ImGui.GetStyle()
style:ScaleAllSizes(main_scale)
style.FontScaleDpi = main_scale
ImGui_ImplRoblox.Init(CanvasDraw)
ImGui_ImplRoblox.Init()
RunService.Stepped:Connect(function()
ImGui_ImplRoblox.NewFrame()
-79
View File
@@ -1,79 +0,0 @@
require("imgui")
local RunService = Instance.new("RunService")
local main_scale = 1.0
local clear_color = ImVec4(0.45, 0.55, 0.60, 1.00)
local f = 0.0
local counter = 0
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard)
--io.ConfigFlags = bit32.bor(io.ConfigFlags, ImGuiConfigFlags.ViewportsEnable)
local style = ImGui.GetStyle()
style:ScaleAllSizes(main_scale)
style.FontScaleDpi = main_scale
ImGui_ImplLove.Init()
RunService.Stepped:Connect(function(dt)
ImGui_ImplLove.NewFrame()
ImGui.NewFrame()
ImGui.ShowDemoWindow()
do
ImGui.Begin("Hello, world!")
ImGui.Text("This is some useful text.")
_, show_demo_window = ImGui.Checkbox("Demo Window", show_demo_window)
_, show_another_window = ImGui.Checkbox("Another Window", show_another_window)
f = ImGui.SliderFloat("float", f, 0.0, 1.0)
ImGui.ColorEdit3("clear color", clear_color)
if ImGui.Button("Button") then
counter = counter + 1
end
ImGui.SameLine()
ImGui.Text("counter = %d", counter)
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0 / io.Framerate, io.Framerate)
ImGui.End()
end
if show_another_window then
show_another_window = ImGui.Begin("Another Window", show_another_window)
ImGui.Text("Hello from another window!")
if ImGui.Button("Close Me") then
show_another_window = false
end
ImGui.End()
end
ImGui.EndFrame()
return ImGui.Render()
end)
RunService.PreAnimation:Connect(function()
ImGui_ImplLove.RenderDrawData(ImGui.GetDrawData())
if bit32.band(io.ConfigFlags, ImGuiConfigFlags.ViewportsEnable) ~= 0 then
ImGui.UpdatePlatformWindows()
ImGui.RenderPlatformWindowsDefault()
end
end)
function love.keypressed(k) ImGui_ImplLove.KeyPressed(k) end
function love.keyreleased(k) ImGui_ImplLove.KeyReleased(k) end
function love.mousepressed(x, y, b) ImGui_ImplLove.MousePressed(x, y, b) end
function love.mousereleased(x, y, b) ImGui_ImplLove.MouseReleased(x, y, b) end
function love.wheelmoved(x, y) ImGui_ImplLove.WheelMoved(x, y) end
function love.textinput(t) ImGui_ImplLove.TextInput(t) end
function love.mousemoved(x, y) ImGui_ImplLove.MouseMoved(x, y) end
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# Bundles imgui/*.lua + backend into ONE ModuleScript: build/ImGui.luau
# Why: ModuleScripts don't share globals, and the setfenv() alternative turns off Luau's fast paths / native codegen.
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
import os, re
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = os.path.join(ROOT, "imgui")
FILES = {}
for dp, _, fn in os.walk(SRC):
for f in sorted(fn):
if f.endswith(".lua"):
FILES[f] = open(os.path.join(dp, f)).read()
ENTRY = ["imgui.lua", "imgui_impl_roblox.lua", "imgui_demo.lua"] # run order
names = set()
for src in FILES.values():
names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M))
names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M))
out = ["--!native", "--!optimize 2", "-- GENERATED by tools/bundle.py from imgui/*.lua - do not edit", "",
"local __FILES, __LOADED = {}, {}",
"function IM_INCLUDE(name)",
" local r = __LOADED[name]",
" if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end",
" return r",
"end", ""]
for name, src in sorted(FILES.items()):
out.append("__FILES[%r] = function()" % name)
out.append(src.rstrip("\n"))
out.append("end")
out.append("")
for name in ENTRY:
out.append("IM_INCLUDE(%r)" % name)
out.append("")
out.append("return {")
for n in sorted(names):
out.append(" %s = %s," % (n, n))
out.append("}")
os.makedirs(os.path.join(ROOT, "build"), exist_ok=True)
open(os.path.join(ROOT, "build", "ImGui.luau"), "w").write("\n".join(out) + "\n")
print("wrote build/ImGui.luau (%d files, %d exports)" % (len(FILES), len(names)))