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()`. --- - 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)) local UserInputService = game:GetService("UserInputService") local Players = game:GetService("Players") 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 ImGui_ImplRoblox_GetBackendData local ImGui_ImplRoblox_UpdateTexture local ImGui_ImplRoblox_RenderDrawData local ImGui_ImplRoblox_Shutdown local KEY_MAP = {} -- Enum.KeyCode -> ImGuiKey do local names = { Return = "Enter", Escape = "Escape", Backspace = "Backspace", Tab = "Tab", Space = "Space", KeypadEnter = "KeypadEnter", Up = "UpArrow", Down = "DownArrow", Left = "LeftArrow", Right = "RightArrow", Home = "Home", End = "End", PageUp = "PageUp", PageDown = "PageDown", Insert = "Insert", Delete = "Delete", LeftControl = "LeftCtrl", RightControl = "RightCtrl", LeftShift = "LeftShift", RightShift = "RightShift", LeftAlt = "LeftAlt", RightAlt = "RightAlt", LeftSuper = "LeftSuper", RightSuper = "RightSuper", Quote = "Apostrophe", Comma = "Comma", Minus = "Minus", Period = "Period", Slash = "Slash", Semicolon = "Semicolon", Equals = "Equal", LeftBracket = "LeftBracket", BackSlash = "Backslash", RightBracket = "RightBracket", Backquote = "GraveAccent", CapsLock = "CapsLock", KeypadPeriod = "KeypadDecimal", KeypadDivide = "KeypadDivide", KeypadMultiply = "KeypadMultiply", KeypadMinus = "KeypadSubtract", KeypadPlus = "KeypadAdd", KeypadEquals = "KeypadEqual", } local digits = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" } for i = 0, 9 do names[digits[i + 1]] = "K" .. i names["Keypad" .. digits[i + 1]] = "Keypad" .. i end for i = 1, 12 do names["F" .. i] = "F" .. i end for c in ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"):gmatch(".") do names[c] = c end for rbx_name, imgui_name in pairs(names) do KEY_MAP[Enum.KeyCode[rbx_name]] = ImGuiKey[imgui_name] end end -- Roblox has no "text input" event, so characters come from KeyCode.Value (ASCII for printable keys) + a US layout shift table. local SHIFTED = { ["1"] = "!", ["2"] = "@", ["3"] = "#", ["4"] = "$", ["5"] = "%", ["6"] = "^", ["7"] = "&", ["8"] = "*", ["9"] = "(", ["0"] = ")", ["-"] = "_", ["="] = "+", ["["] = "{", ["]"] = "}", ["\\"] = "|", [";"] = ":", ["'"] = "\"", [","] = "<", ["."] = ">", ["/"] = "?", ["`"] = "~", } local MOUSE_BUTTONS = { [Enum.UserInputType.MouseButton1] = 0, -- ImGuiMouseButton.Left [Enum.UserInputType.MouseButton2] = 1, -- ImGuiMouseButton.Right [Enum.UserInputType.MouseButton3] = 2, -- ImGuiMouseButton.Middle } --- @return boolean ctrl, boolean shift, boolean alt local function ImGui_ImplRoblox_UpdateKeyModifiers() local io = ImGui.GetIO() local K = Enum.KeyCode local ctrl = UserInputService:IsKeyDown(K.LeftControl) or UserInputService:IsKeyDown(K.RightControl) local shift = UserInputService:IsKeyDown(K.LeftShift) or UserInputService:IsKeyDown(K.RightShift) local alt = UserInputService:IsKeyDown(K.LeftAlt) or UserInputService:IsKeyDown(K.RightAlt) io:AddKeyEvent(ImGuiMod_Ctrl, ctrl) io:AddKeyEvent(ImGuiMod_Shift, shift) io:AddKeyEvent(ImGuiMod_Alt, alt) io:AddKeyEvent(ImGuiMod_Super, UserInputService:IsKeyDown(K.LeftSuper) or UserInputService:IsKeyDown(K.RightSuper)) return ctrl, shift, alt end --- @return table? function ImGui_ImplRoblox_GetBackendData() return ImGui.GetCurrentContext() and ImGui.GetIO().BackendPlatformUserData or nil end local function ImGui_ImplRoblox_UpdateMonitors(w, h) local platform_io = ImGui.GetPlatformIO() platform_io.Monitors:resize(0) local imgui_monitor = ImGuiPlatformMonitor() ImVec2_Copy(imgui_monitor.MainSize, ImVec2(w, h)) ImVec2_Copy(imgui_monitor.WorkSize, ImVec2(w, h)) platform_io.Monitors:push_back(imgui_monitor) end --------------------------------------------------------- -- PLATFORM DATA & CALLBACKS --------------------------------------------------------- -- Games can't touch the OS clipboard, so copy/paste only works inside ImGui local clipboard = "" local function ImGui_ImplRoblox_PlatformSetClipboardText(ctx, text) clipboard = text end local function ImGui_ImplRoblox_PlatformGetClipboardText(ctx) return clipboard end --------------------------------------------------------- -- FILE SYSTEM (in-memory, Roblox has no file access) --------------------------------------------------------- local VFS = {} -- path -> string of raw bytes --- Registers a file so ImGui can load it, e.g. ImGui_ImplRoblox.AddFile("fonts/MyFont.ttf", bytes) --- then io.Fonts:AddFontFromFileTTF("fonts/MyFont.ttf", 16) local function ImGui_ImplRoblox_AddFile(path, bytes) VFS[path] = bytes end for path, hex in pairs(IM_INCLUDE("imgui_impl_roblox_fonts.lua")) do VFS[path] = hex:gsub("%s", ""):gsub("%x%x", function(h) return string.char(tonumber(h, 16)) end) end function ImStd.ImFileOpen(filename, mode) local data = VFS[filename] return data and { Data = data, Pos = 0 } or nil end function ImStd.ImFileClose(f) end function ImStd.ImFileGetSize(f) return #f.Data end function ImStd.ImFileRead(f, data, count) local s, pos = f.Data, f.Pos count = min(count, #s - pos) for i = 1, count do data[i] = string.byte(s, pos + i) end f.Pos = pos + count return count end function ImStd.ImFileLoadToMemory(filename, mode) local f = ImStd.ImFileOpen(filename, mode) if not f or #f.Data == 0 then return end local file_data = {} local size = ImStd.ImFileRead(f, file_data, #f.Data) return file_data, size end --------------------------------------------------------- -- TEXTURE MANAGEMENT --------------------------------------------------------- -- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer local function CopyTexturePixels(tex, dst, x, y, w, h) local bpp, tex_w = tex.BytesPerPixel, tex.Width for row = 0, h - 1 do local src, src_base = tex:GetPixelsAt(x, y + row) local dst_off = ((y + row) * tex_w + x) * 4 if bpp == 4 then for i = 0, w - 1 do local s = src_base + i * 4 writeu32(dst, dst_off + i * 4, src[s] + src[s + 1] * 256 + src[s + 2] * 65536 + src[s + 3] * 16777216) end else -- Alpha8: white with alpha for i = 0, w - 1 do writeu32(dst, dst_off + i * 4, 16777215 + src[src_base + i] * 16777216) end end end end function ImGui_ImplRoblox_UpdateTexture(tex) local bd = ImGui_ImplRoblox_GetBackendData() if tex.Status == ImTextureStatus.WantCreate then local t = { Buffer = buffer.create(tex.Width * tex.Height * 4), Width = tex.Width, Height = tex.Height } CopyTexturePixels(tex, t.Buffer, 0, 0, tex.Width, tex.Height) bd.TextureCount = bd.TextureCount + 1 bd.Textures[bd.TextureCount] = t tex.BackendUserData = bd.TextureCount tex:SetTexID(bd.TextureCount) tex:SetStatus(ImTextureStatus.OK) elseif tex.Status == ImTextureStatus.WantUpdates then local t = bd.Textures[tex.BackendUserData] IM_ASSERT(t ~= nil) for _, r in tex.Updates:iter() do CopyTexturePixels(tex, t.Buffer, r.x, r.y, r.w, r.h) end tex:SetStatus(ImTextureStatus.OK) elseif tex.Status == ImTextureStatus.WantDestroy then if tex.BackendUserData then bd.Textures[tex.BackendUserData] = nil tex.BackendUserData = nil end tex:SetTexID(ImTextureID_Invalid) tex:SetStatus(ImTextureStatus.Destroyed) end end --------------------------------------------------------- -- RENDERING (software rasterizer into the canvas buffer) --------------------------------------------------------- -- 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 -- Blends one pixel (r, g, b, a are 0..255) 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) 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) else local sa = a / 255 local k = (da / 255) * (1 - sa) 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) 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 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 end end local function UnpackColor(col) return col % 256, floor(col / 256) % 256, floor(col / 65536) % 256, floor(col / 16777216) % 256 end -- Nearest texel at (u, v) as r, g, b, a (0..255) local function SampleTexture(t, u, v) local tw, th = t.Width, t.Height local tx, ty = floor(u * tw), floor(v * th) if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end local o = (ty * tw + tx) * 4 local tb = t.Buffer return readu8(tb, o), readu8(tb, o + 1), readu8(tb, o + 2), readu8(tb, o + 3) end -- Axis aligned, single colour quad (rects, glyphs, images). Pixel centers inside [x0, x1) x [y0, y1) are drawn. local function DrawRect(x0, y0, x1, y1, u0, v0, u1, v1, col, t, cx0, cy0, cx1, cy1) local r, g, b, a = UnpackColor(col) if a == 0 then return end local px0, py0 = max(ceil(x0 - 0.5), cx0), max(ceil(y0 - 0.5), cy0) local px1, py1 = min(ceil(x1 - 0.5), cx1), min(ceil(y1 - 0.5), cy1) if px0 >= px1 or py0 >= py1 then return end if not t or (u0 == u1 and v0 == v1) then if t then 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 return end local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0) local tw, th, tbuf = t.Width, t.Height, t.Buffer local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255 for y = py0, py1 - 1 do local ty = floor((v0 + (y + 0.5 - y0) * dv) * th) if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end local trow = ty * tw local o = (y * buf_w + px0) * 4 for x = px0, px1 - 1 do local tx = floor((u0 + (x + 0.5 - x0) * du) * tw) if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end local to = (trow + tx) * 4 local ta = readu8(tbuf, to + 3) if ta > 0 then BlendPixel(o, readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255, ta * a255) end o = o + 4 end end end -- Generic triangle: per-vertex colour + uv, top-left fill rule, clipped to [cx0, cx1) x [cy0, cy1) local function DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy) local x0, y0 = va[1][1] - ox, va[1][2] - oy local x1, y1 = vb[1][1] - ox, vb[1][2] - oy local x2, y2 = vc[1][1] - ox, vc[1][2] - oy local area = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0) if area == 0 then return end if area < 0 then -- make the winding consistent so "inside" means all edge functions >= 0 vb, vc = vc, vb x1, y1, x2, y2 = x2, y2, x1, y1 area = -area end local minx, maxx = max(floor(min(x0, x1, x2)), cx0), min(ceil(max(x0, x1, x2)), cx1 - 1) local miny, maxy = max(floor(min(y0, y1, y2)), cy0), min(ceil(max(y0, y1, y2)), cy1 - 1) if minx > maxx or miny > maxy then return end -- Edge functions E(p) = A*px + B*py + C, w0 is opposite va, w1 opposite vb, w2 opposite vc local A0, B0 = y1 - y2, x2 - x1; local C0 = -(A0 * x1 + B0 * y1) local A1, B1 = y2 - y0, x0 - x2; local C1 = -(A1 * x2 + B1 * y2) local A2, B2 = y0 - y1, x1 - x0; local C2 = -(A2 * x0 + B2 * y0) local tl0 = A0 > 0 or (A0 == 0 and B0 > 0) local tl1 = A1 > 0 or (A1 == 0 and B1 > 0) local tl2 = A2 > 0 or (A2 == 0 and B2 > 0) local inv = 1 / area local ra, ga, ba, aa = UnpackColor(va[3]) local rb, gb, bb, ab = UnpackColor(vb[3]) local rc, gc, bc, ac = UnpackColor(vc[3]) local ua, vva, ub, vvb, uc, vvc = va[2][1], va[2][2], vb[2][1], vb[2][2], vc[2][1], vc[2][2] local textured = t and not (ua == ub and ua == uc and vva == vvb and vva == vvc) if t and not textured then -- constant uv (e.g. the atlas white pixel): fold the texel into the vertex colours local tr, tg, tb, ta = SampleTexture(t, ua, vva) ra, ga, ba, aa = ra * tr / 255, ga * tg / 255, ba * tb / 255, aa * ta / 255 rb, gb, bb, ab = rb * tr / 255, gb * tg / 255, bb * tb / 255, ab * ta / 255 rc, gc, bc, ac = rc * tr / 255, gc * tg / 255, bc * tb / 255, ac * ta / 255 end local flat = not textured and ra == rb and ra == rc and ga == gb and ga == gc and ba == bb and ba == bc and aa == ab and aa == ac if flat and aa <= 0 then return end for y = miny, maxy do local py = y + 0.5 -- Narrow the x range of this row using the edges (expanded by one pixel, exact test below) local xl, xr = minx, maxx local e0, e1, e2 = B0 * py + C0, B1 * py + C1, B2 * py + C2 if A0 ~= 0 then local xe = -e0 / A0 - 0.5; if A0 > 0 then xl = max(xl, floor(xe)) else xr = min(xr, ceil(xe)) end end if A1 ~= 0 then local xe = -e1 / A1 - 0.5; if A1 > 0 then xl = max(xl, floor(xe)) else xr = min(xr, ceil(xe)) end end if A2 ~= 0 then local xe = -e2 / A2 - 0.5; if A2 > 0 then xl = max(xl, floor(xe)) else xr = min(xr, ceil(xe)) end end local row = y * buf_w for x = xl, xr do local px = x + 0.5 local w0, w1, w2 = A0 * px + e0, A1 * px + e1, A2 * px + e2 if (w0 > 0 or (w0 == 0 and tl0)) and (w1 > 0 or (w1 == 0 and tl1)) and (w2 > 0 or (w2 == 0 and tl2)) then local o = (row + x) * 4 if flat then BlendPixel(o, ra, ga, ba, aa) else w0, w1, w2 = w0 * inv, w1 * inv, w2 * inv local r = w0 * ra + w1 * rb + w2 * rc local g = w0 * ga + w1 * gb + w2 * gc local b = w0 * ba + w1 * bb + w2 * bc local a = w0 * aa + w1 * ab + w2 * ac if textured then local tr, tg, tb, ta = SampleTexture(t, w0 * ua + w1 * ub + w2 * uc, w0 * vva + w1 * vvb + w2 * vvc) r, g, b, a = r * tr / 255, g * tg / 255, b * tb / 255, a * ta / 255 end BlendPixel(o, r, g, b, a) end end end end end -- Quads emitted by PrimRect/PrimRectUV/RenderText use indices (a, b, c, a, c, d) with a=TL, b=TR, c=BR, d=BL. -- When such a quad is axis aligned with one colour it is drawn with the much cheaper DrawRect. local function TryDrawRect(va, vb, vc, vd, t, cx0, cy0, cx1, cy1, ox, oy) local pa, pb, pc, pd = va[1], vb[1], vc[1], vd[1] if not (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 return false end local col = va[3] if vb[3] ~= col or vc[3] ~= col or vd[3] ~= col then return false end local ta, tb, tc, td = va[2], vb[2], vc[2], vd[2] if not (tb[1] == tc[1] and tb[2] == ta[2] and td[1] == ta[1] and td[2] == tc[2]) then return false end DrawRect(pa[1] - ox, pa[2] - oy, pc[1] - ox, pc[2] - oy, ta[1], ta[2], tc[1], tc[2], col, t, cx0, cy0, cx1, cy1) return true 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) end end end canvas:Clear() buf, buf_w = canvas.Buffer, canvas.CurrentResX local buf_h = canvas.CurrentResY 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 end end end end end end end canvas:Render() 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))") 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.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 = {}, Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1, } io.BackendPlatformUserData = bd io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset) local platform_io = ImGui.GetPlatformIO() platform_io.Platform_SetClipboardTextFn = ImGui_ImplRoblox_PlatformSetClipboardText platform_io.Platform_GetClipboardTextFn = ImGui_ImplRoblox_PlatformGetClipboardText ImGui_ImplRoblox_UpdateMonitors(w, h) local function connect(signal, fn) table.insert(bd.Connections, signal:Connect(fn)) end connect(UserInputService.InputBegan, function(input, game_processed) local io = ImGui.GetIO() local button = MOUSE_BUTTONS[input.UserInputType] if button then io:AddMouseSourceEvent(ImGuiMouseSource.Mouse) io:AddMouseButtonEvent(button, true) elseif input.UserInputType == Enum.UserInputType.Keyboard and not game_processed then local ctrl, shift, alt = ImGui_ImplRoblox_UpdateKeyModifiers() local key = KEY_MAP[input.KeyCode] if key then io:AddKeyEvent(key, true) end -- Text: printable ASCII keys, skipped while Ctrl/Alt are held (shortcuts) local v = input.KeyCode.Value if v >= 32 and v < 127 and not (ctrl or alt) then local c = string.char(v) if shift then c = SHIFTED[c] or c:upper() end io:AddInputCharacter(string.byte(c)) end end end) connect(UserInputService.InputEnded, function(input, game_processed) local io = ImGui.GetIO() local button = MOUSE_BUTTONS[input.UserInputType] if button then io:AddMouseButtonEvent(button, false) elseif input.UserInputType == Enum.UserInputType.Keyboard then ImGui_ImplRoblox_UpdateKeyModifiers() local key = KEY_MAP[input.KeyCode] if key then io:AddKeyEvent(key, false) end end end) connect(UserInputService.InputChanged, function(input, game_processed) if input.UserInputType == Enum.UserInputType.MouseWheel then ImGui.GetIO():AddMouseWheelEvent(0, input.Position.Z) end end) connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end) connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end) end 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() bd.Gui:Destroy() io.BackendPlatformUserData = nil io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor( ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset ))) 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 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 io:AddMouseSourceEvent(ImGuiMouseSource.Mouse) io:AddMousePosEvent(mx, my) end end ImGui_ImplRoblox = { Init = ImGui_ImplRoblox_Init, Shutdown = ImGui_ImplRoblox_Shutdown, NewFrame = ImGui_ImplRoblox_NewFrame, RenderDrawData = ImGui_ImplRoblox_RenderDrawData, AddFile = ImGui_ImplRoblox_AddFile, } return true -- [Roblox] ModuleScripts must return exactly one value