optimize
This commit is contained in:
+36165
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "imgui",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ReplicatedStorage": {
|
||||
"ImGui": { "$path": "build/ImGui.luau" }
|
||||
},
|
||||
"StarterPlayer": {
|
||||
"StarterPlayerScripts": {
|
||||
"ImGuiMain": { "$path": "main.client.luau" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
+273
-107
@@ -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
|
||||
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)
|
||||
return
|
||||
end
|
||||
local sa = a / 255
|
||||
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
|
||||
else
|
||||
for x = 0, x1 - x0 - 1 do BlendPixel(o + x * 4, r, g, b, a) 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
|
||||
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,46 +423,152 @@ 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
|
||||
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
|
||||
if pcmd.UserCallback ~= nil then
|
||||
pcmd.UserCallback(draw_list, pcmd)
|
||||
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)
|
||||
if cx1 > cx0 and cy1 > cy0 then
|
||||
local t = bd.Textures[pcmd:GetTexID()]
|
||||
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
|
||||
i = i + 6
|
||||
else
|
||||
DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
|
||||
i = i + 3
|
||||
-- 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
|
||||
if pcmd.UserCallback ~= nil then
|
||||
pcmd.UserCallback(draw_list, pcmd)
|
||||
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), sw), min(floor(clip.w - oy), sh)
|
||||
if cx1 > cx0 and cy1 > cy0 then
|
||||
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]
|
||||
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
|
||||
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
|
||||
@@ -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))")
|
||||
--- @param parent Instance? # where the ScreenGui goes, defaults to the local player's PlayerGui
|
||||
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))
|
||||
ImGui_ImplRoblox_UpdateMonitors(w, h)
|
||||
end
|
||||
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
|
||||
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
@@ -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,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,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,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,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,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,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,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,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,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,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
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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)))
|
||||
Reference in New Issue
Block a user