538 lines
23 KiB
Lua
538 lines
23 KiB
Lua
--- Dear ImGui backend for LÖVE (11.x, LuaJIT). Platform (input, clipboard, cursors, IME) + renderer (Mesh per draw list).
|
|
--- Built into build/imgui_love.lua by `python3 tools/bundle.py --target love`.
|
|
--- Usage (see examples/example_love/main.lua):
|
|
--- love.load: ImGui.CreateContext(); ImGui_ImplLove.Init({ root = "<repo root>/" })
|
|
--- love.update: ImGui_ImplLove.NewFrame(); ImGui.NewFrame(); ...your UI...; ImGui.Render()
|
|
--- love.draw: ImGui_ImplLove.RenderDrawData(ImGui.GetDrawData())
|
|
--- forward love.mousemoved/mousepressed/mousereleased/wheelmoved/keypressed/keyreleased/textinput to ImGui_ImplLove.*
|
|
|
|
-- [SECTION] COMPAT (Luau stdlib for LuaJIT). tools/bundle.py --target love runs this section before the library.
|
|
--@compat-begin
|
|
local ffi = require("ffi")
|
|
local floor = math.floor
|
|
-- Luau standard library pieces the port uses, for LuaJIT (LÖVE). Prepended by `tools/bundle.py --target love` only:
|
|
-- the Roblox build uses Luau's native bit32/buffer/table/utf8 and never sees this file.
|
|
|
|
local bit = require("bit")
|
|
local TWO32 = 4294967296
|
|
|
|
-- bit32: unsigned 32-bit results like Luau/Lua 5.2 (LuaJIT's `bit` returns signed values)
|
|
if not bit32 then
|
|
local band, bor, bxor, bnot, lshift, rshift, arshift, tobit = bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift, bit.arshift, bit.tobit
|
|
local function u(x) return x % TWO32 end
|
|
local function t(x) return tobit(x % TWO32) end -- any number -> int32 (bit.* wraps doubles >= 2^31 unreliably)
|
|
bit32 = {}
|
|
function bit32.band(a, b, ...)
|
|
if a == nil then return TWO32 - 1 end
|
|
if b == nil then return u(t(a)) end
|
|
local r = band(t(a), t(b))
|
|
if ... ~= nil then for i = 1, select("#", ...) do r = band(r, t((select(i, ...)))) end end
|
|
return u(r)
|
|
end
|
|
function bit32.bor(a, b, ...)
|
|
if a == nil then return 0 end
|
|
if b == nil then return u(t(a)) end
|
|
local r = bor(t(a), t(b))
|
|
if ... ~= nil then for i = 1, select("#", ...) do r = bor(r, t((select(i, ...)))) end end
|
|
return u(r)
|
|
end
|
|
function bit32.bxor(a, b, ...)
|
|
if a == nil then return 0 end
|
|
if b == nil then return u(t(a)) end
|
|
local r = bxor(t(a), t(b))
|
|
if ... ~= nil then for i = 1, select("#", ...) do r = bxor(r, t((select(i, ...)))) end end
|
|
return u(r)
|
|
end
|
|
function bit32.bnot(a) return u(bnot(t(a))) end
|
|
function bit32.lshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(rshift(t(a), -n)) end return u(lshift(t(a), n)) end
|
|
function bit32.rshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(lshift(t(a), -n)) end return u(rshift(t(a), n)) end
|
|
function bit32.arshift(a, n) if n >= 32 then n = 31 end return u(arshift(t(a), n)) end
|
|
function bit32.btest(...) return bit32.band(...) ~= 0 end
|
|
function bit32.extract(n, field, width) width = width or 1; return u(band(rshift(t(n), field), 2 ^ width - 1)) end
|
|
end
|
|
|
|
-- buffer: fixed-size byte buffers (subset used by the port), on FFI memory. Little-endian like Luau.
|
|
if not buffer then
|
|
ffi.cdef("void *memmove(void *dst, const void *src, size_t n); void *memset(void *s, int c, size_t n);")
|
|
local C = ffi.C
|
|
local u8p, u32p, f64p = ffi.typeof("uint8_t*"), ffi.typeof("uint32_t*"), ffi.typeof("double*")
|
|
buffer = {}
|
|
function buffer.create(n)
|
|
local mem = ffi.new("uint8_t[?]", math.max(n, 1)) -- zero-filled
|
|
return { p = ffi.cast(u8p, mem), n = n, mem = mem }
|
|
end
|
|
function buffer.len(b) return b.n end
|
|
function buffer.readu8(b, o) return b.p[o] end
|
|
function buffer.writeu8(b, o, v) b.p[o] = v end
|
|
function buffer.readu32(b, o) return ffi.cast(u32p, b.p + o)[0] end
|
|
function buffer.writeu32(b, o, v) ffi.cast(u32p, b.p + o)[0] = v % TWO32 end
|
|
function buffer.readf64(b, o) return ffi.cast(f64p, b.p + o)[0] end
|
|
function buffer.writef64(b, o, v) ffi.cast(f64p, b.p + o)[0] = v end
|
|
function buffer.readstring(b, o, n) return ffi.string(b.p + o, n) end
|
|
function buffer.writestring(b, o, s, n) ffi.copy(b.p + o, s, n or #s) end
|
|
function buffer.copy(dst, doff, src, soff, n)
|
|
soff = soff or 0
|
|
n = n or (src.n - soff)
|
|
if n > 0 then C.memmove(dst.p + doff, src.p + soff, n) end
|
|
end
|
|
function buffer.fill(b, o, v, n) n = n or (b.n - o); if n > 0 then C.memset(b.p + o, v, n) end end
|
|
function buffer.tostring(b) return ffi.string(b.p, b.n) end
|
|
function buffer.fromstring(s) local b = buffer.create(#s); ffi.copy(b.p, s, #s); return b end
|
|
end
|
|
|
|
-- table / math / utf8 additions from Luau / Lua 5.3
|
|
do
|
|
local ok, clear = pcall(require, "table.clear")
|
|
table.clear = table.clear or (ok and clear) or function(t) for k in pairs(t) do t[k] = nil end end
|
|
table.unpack = table.unpack or unpack
|
|
table.find = table.find or function(t, v, init) for i = init or 1, #t do if t[i] == v then return i end end end
|
|
table.move = table.move or function(a1, f, e, t, a2)
|
|
a2 = a2 or a1
|
|
if e >= f then
|
|
if t > f or t > e or a1 ~= a2 then for i = 0, e - f do a2[t + i] = a1[f + i] end
|
|
else for i = e - f, 0, -1 do a2[t + i] = a1[f + i] end end
|
|
end
|
|
return a2
|
|
end
|
|
math.pow = math.pow or function(a, b) return a ^ b end
|
|
end
|
|
|
|
if not utf8 then
|
|
utf8 = { charpattern = "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" }
|
|
function utf8.char(...)
|
|
local out = {}
|
|
for i = 1, select("#", ...) do
|
|
local c = select(i, ...)
|
|
if c < 0x80 then out[#out + 1] = string.char(c)
|
|
elseif c < 0x800 then out[#out + 1] = string.char(0xC0 + floor(c / 64), 0x80 + c % 64)
|
|
elseif c < 0x10000 then out[#out + 1] = string.char(0xE0 + floor(c / 4096), 0x80 + floor(c / 64) % 64, 0x80 + c % 64)
|
|
else out[#out + 1] = string.char(0xF0 + floor(c / 262144), 0x80 + floor(c / 4096) % 64, 0x80 + floor(c / 64) % 64, 0x80 + c % 64) end
|
|
end
|
|
return table.concat(out)
|
|
end
|
|
local function decode(s, i)
|
|
local c = s:byte(i)
|
|
if not c then return nil end
|
|
if c < 0x80 then return c, 1 end
|
|
if c < 0xE0 then return (c % 32) * 64 + s:byte(i + 1) % 64, 2 end
|
|
if c < 0xF0 then return (c % 16) * 4096 + (s:byte(i + 1) % 64) * 64 + s:byte(i + 2) % 64, 3 end
|
|
return (c % 8) * 262144 + (s:byte(i + 1) % 64) * 4096 + (s:byte(i + 2) % 64) * 64 + s:byte(i + 3) % 64, 4
|
|
end
|
|
function utf8.codes(s)
|
|
local i = 1
|
|
return function()
|
|
if i > #s then return nil end
|
|
local c, n = decode(s, i)
|
|
local p = i; i = i + n
|
|
return p, c
|
|
end
|
|
end
|
|
function utf8.codepoint(s, i, j)
|
|
i = i or 1; j = j or i
|
|
local out = {}
|
|
while i <= j do local c, n = decode(s, i); out[#out + 1] = c; i = i + n end
|
|
return unpack(out)
|
|
end
|
|
function utf8.len(s) local n = 0; for _ in utf8.codes(s) do n = n + 1 end return n end
|
|
function utf8.offset(s, n, i)
|
|
i = i or (n >= 0 and 1 or #s + 1)
|
|
if n > 0 then
|
|
n = n - 1
|
|
while n > 0 and i <= #s do i = i + 1; while i <= #s and s:byte(i) >= 0x80 and s:byte(i) < 0xC0 do i = i + 1 end; n = n - 1 end
|
|
if n > 0 then return nil end
|
|
return i
|
|
end
|
|
return i
|
|
end
|
|
end
|
|
--@compat-end
|
|
|
|
local floor, min = math.floor, math.min
|
|
|
|
local ImGui_ImplLOVE_GetBackendData
|
|
|
|
local CURSOR_MAP = {
|
|
[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, ["kpenter"] = 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,
|
|
["-"] = ImGuiKey.Minus, ["="] = ImGuiKey.Equal, ["["] = ImGuiKey.LeftBracket, ["]"] = ImGuiKey.RightBracket,
|
|
[";"] = ImGuiKey.Semicolon, ["'"] = ImGuiKey.Apostrophe, [","] = ImGuiKey.Comma, ["."] = ImGuiKey.Period,
|
|
["/"] = ImGuiKey.Slash, ["\\"] = ImGuiKey.Backslash, ["`"] = ImGuiKey.GraveAccent,
|
|
}
|
|
for i = 0, 9 do KEY_MAP[tostring(i)] = ImGuiKey["K" .. i]; KEY_MAP["kp" .. i] = ImGuiKey["Keypad" .. 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 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
|
|
|
|
function ImGui_ImplLOVE_GetBackendData()
|
|
return ImGui.GetCurrentContext() and ImGui.GetIO().BackendPlatformUserData or nil
|
|
end
|
|
|
|
local function UpdateMonitors(w, h)
|
|
local platform_io = ImGui.GetPlatformIO()
|
|
platform_io.Monitors:resize(0)
|
|
local m = ImGuiPlatformMonitor()
|
|
ImVec2_Copy(m.MainSize, ImVec2(w, h))
|
|
ImVec2_Copy(m.WorkSize, ImVec2(w, h))
|
|
platform_io.Monitors:push_back(m)
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- PLATFORM DATA & CALLBACKS
|
|
---------------------------------------------------------
|
|
|
|
local function PlatformSetImeData(ctx, vp, data)
|
|
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 PlatformSetClipboardText(ctx, text) love.system.setClipboardText(text) end
|
|
local function PlatformGetClipboardText(ctx) return love.system.getClipboardText() end
|
|
local function PlatformOpenInShell(ctx, path) return love.system.openURL(path) end
|
|
|
|
---------------------------------------------------------
|
|
-- FILE SYSTEM: love.filesystem (game folder), then io.open(<root> .. path) so fonts/ etc. load from the repo
|
|
---------------------------------------------------------
|
|
|
|
local function ReadFile(filename)
|
|
local data = love.filesystem.getInfo(filename) and love.filesystem.read(filename)
|
|
if data then return data end
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
local f = io.open(((bd and bd.Root) or "") .. filename, "rb")
|
|
if not f then return nil end
|
|
data = f:read("*a"); f:close()
|
|
return data
|
|
end
|
|
|
|
function ImStd.ImFileOpen(filename, mode)
|
|
local data = ReadFile(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
|
|
|
|
---------------------------------------------------------
|
|
-- TEXTURES
|
|
---------------------------------------------------------
|
|
|
|
local function CopyToImageData(tex, x, y, w, h)
|
|
local img = love.image.newImageData(w, h, "rgba8")
|
|
local dst = ffi.cast("uint8_t*", img:getFFIPointer())
|
|
local bpp = tex.BytesPerPixel
|
|
for row = 0, h - 1 do
|
|
local src, base = tex:GetPixelsAt(x, y + row)
|
|
local d = row * w * 4
|
|
if bpp == 4 then
|
|
for i = 0, w * 4 - 1 do dst[d + i] = src[base + i] end
|
|
else -- Alpha8: white with alpha
|
|
for i = 0, w - 1 do
|
|
local o = d + i * 4
|
|
dst[o], dst[o + 1], dst[o + 2], dst[o + 3] = 255, 255, 255, src[base + i]
|
|
end
|
|
end
|
|
end
|
|
return img
|
|
end
|
|
|
|
function ImGui_ImplLOVE_UpdateTexture(tex)
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
if tex.Status == ImTextureStatus.WantCreate then
|
|
local data = CopyToImageData(tex, 0, 0, tex.Width, tex.Height)
|
|
local image = love.graphics.newImage(data)
|
|
image:setFilter("nearest", "nearest")
|
|
bd.TextureCount = bd.TextureCount + 1
|
|
bd.Textures[bd.TextureCount] = image
|
|
tex.BackendUserData = bd.TextureCount
|
|
tex:SetTexID(bd.TextureCount)
|
|
tex:SetStatus(ImTextureStatus.OK)
|
|
elseif tex.Status == ImTextureStatus.WantUpdates then
|
|
local image = bd.Textures[tex.BackendUserData]
|
|
for _, r in tex.Updates:iter() do
|
|
local data = CopyToImageData(tex, r.x, r.y, r.w, r.h)
|
|
image:replacePixels(data, nil, 1, r.x, r.y, false)
|
|
data:release()
|
|
end
|
|
tex:SetStatus(ImTextureStatus.OK)
|
|
elseif tex.Status == ImTextureStatus.WantDestroy then
|
|
local image = tex.BackendUserData and bd.Textures[tex.BackendUserData]
|
|
if image then image:release(); bd.Textures[tex.BackendUserData] = nil end
|
|
tex.BackendUserData = nil
|
|
tex:SetTexID(ImTextureID_Invalid)
|
|
tex:SetStatus(ImTextureStatus.Destroyed)
|
|
end
|
|
end
|
|
|
|
--- User texture from RGBA8 pixels (a `buffer` of w*h*4 bytes, or a love ImageData). Returns an ImTextureRef.
|
|
function ImGui_ImplLOVE_CreateTexture(pixels, w, h)
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
local data = pixels
|
|
if type(pixels) == "table" and pixels.p then
|
|
data = love.image.newImageData(w, h, "rgba8")
|
|
ffi.copy(data:getFFIPointer(), pixels.p, w * h * 4)
|
|
end
|
|
bd.TextureCount = bd.TextureCount + 1
|
|
bd.Textures[bd.TextureCount] = love.graphics.newImage(data)
|
|
return ImTextureRef(bd.TextureCount)
|
|
end
|
|
|
|
function ImGui_ImplLOVE_DestroyTexture(tex_ref)
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
local image = bd.Textures[tex_ref._TexID]
|
|
if image then image:release(); bd.Textures[tex_ref._TexID] = nil end
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- RENDERING: one streamed Mesh per draw list; vertices converted from the port's flat VtxBuffer (f64 x, y, u, v,
|
|
-- u32 col; 36 bytes) to LÖVE's float x, y, u, v + byte rgba (20 bytes); indices -> 0-based uint32 vertex map.
|
|
---------------------------------------------------------
|
|
|
|
ffi.cdef("typedef struct { float x, y, u, v; uint8_t r, g, b, a; } ImGuiLoveVertex;")
|
|
local VERTEX_FORMAT = { { "VertexPosition", "float", 2 }, { "VertexTexCoord", "float", 2 }, { "VertexColor", "byte", 4 } }
|
|
local vtx_ct, u32p, f64p = ffi.typeof("ImGuiLoveVertex*"), ffi.typeof("uint32_t*"), ffi.typeof("double*")
|
|
|
|
local function GetMesh(bd, draw_list, nvtx, nidx)
|
|
local m = bd.Meshes[draw_list]
|
|
if not m or m.VtxCap < nvtx or m.IdxCap < nidx then
|
|
local vcap = math.max(nvtx, m and m.VtxCap * 2 or 256)
|
|
local icap = math.max(nidx, m and m.IdxCap * 2 or 512)
|
|
if m then m.Mesh:release() end
|
|
m = {
|
|
Mesh = love.graphics.newMesh(VERTEX_FORMAT, vcap, "triangles", "stream"),
|
|
VtxData = love.data.newByteData(vcap * 20), IdxData = love.data.newByteData(icap * 4),
|
|
VtxCap = vcap, IdxCap = icap,
|
|
}
|
|
bd.Meshes[draw_list] = m
|
|
end
|
|
m.Used = bd.FrameNo
|
|
return m
|
|
end
|
|
|
|
function ImGui_ImplLOVE_RenderDrawData(draw_data)
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
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
|
|
local fb_w, fb_h = draw_data.DisplaySize.x, draw_data.DisplaySize.y
|
|
if fb_w <= 0 or fb_h <= 0 then return end
|
|
bd.FrameNo = bd.FrameNo + 1
|
|
|
|
love.graphics.push("all")
|
|
love.graphics.setBlendMode("alpha", "alphamultiply")
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
|
|
love.graphics.translate(-ox, -oy)
|
|
|
|
for _, draw_list in draw_data.CmdLists:iter() do
|
|
local nvtx, nidx = draw_list.VtxBuffer.Size, draw_list.IdxBuffer.Size
|
|
if nvtx > 0 and nidx > 0 then
|
|
local m = GetMesh(bd, draw_list, nvtx, nidx)
|
|
local base = draw_list.VtxBuffer.Buf.p
|
|
local srcu = ffi.cast(u32p, base)
|
|
local dst = ffi.cast(vtx_ct, m.VtxData:getFFIPointer())
|
|
for i = 0, nvtx - 1 do
|
|
local v, src = dst[i], ffi.cast(f64p, base + i * 36) -- 36-byte stride: cast per vertex
|
|
v.x, v.y, v.u, v.v = src[0], src[1], src[2], src[3]
|
|
local c = srcu[i * 9 + 8]
|
|
v.r, v.g, v.b, v.a = c % 256, floor(c / 256) % 256, floor(c / 65536) % 256, floor(c / 16777216)
|
|
end
|
|
m.Mesh:setVertices(m.VtxData, 1, nvtx)
|
|
|
|
-- vertex map: per command, 0-based vertex = VtxOffset + idx - 1 (port indices are 1-based)
|
|
local map = ffi.cast(u32p, m.IdxData:getFFIPointer())
|
|
local idx = draw_list.IdxBuffer.Data
|
|
for _, pcmd in draw_list.CmdBuffer:iter() do
|
|
local vo = pcmd.VtxOffset
|
|
for i = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount do map[i - 1] = vo + idx[i] - 1 end
|
|
end
|
|
m.Mesh:setVertexMap(m.IdxData, "uint32")
|
|
|
|
for _, pcmd in draw_list.CmdBuffer:iter() do
|
|
if pcmd.UserCallback ~= nil then
|
|
pcmd.UserCallback(draw_list, pcmd)
|
|
elseif pcmd.ElemCount > 0 then
|
|
local c = pcmd.ClipRect
|
|
local x0, y0 = math.max(c.x - ox, 0), math.max(c.y - oy, 0)
|
|
local x1, y1 = min(c.z - ox, fb_w), min(c.w - oy, fb_h)
|
|
if x1 > x0 and y1 > y0 then
|
|
love.graphics.setScissor(x0, y0, x1 - x0, y1 - y0)
|
|
m.Mesh:setTexture(bd.Textures[pcmd:GetTexID()])
|
|
m.Mesh:setDrawRange(pcmd.IdxOffset + 1, pcmd.ElemCount)
|
|
love.graphics.draw(m.Mesh)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- free meshes of draw lists that went away
|
|
for dl, m in pairs(bd.Meshes) do
|
|
if bd.FrameNo - m.Used > 120 then m.Mesh:release(); bd.Meshes[dl] = nil end
|
|
end
|
|
love.graphics.setScissor()
|
|
love.graphics.pop()
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- LIFECYCLE
|
|
---------------------------------------------------------
|
|
|
|
--- opts.root: path prefix for files not in the LÖVE game folder (e.g. the repo root, for fonts/)
|
|
function ImGui_ImplLOVE_Init(opts)
|
|
opts = opts or {}
|
|
local io = ImGui.GetIO()
|
|
local bd = { Time = love.timer.getTime(), Root = opts.root or "", Textures = {}, TextureCount = 0, Meshes = {}, FrameNo = 0 }
|
|
io.BackendPlatformUserData = bd
|
|
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)
|
|
|
|
local platform_io = ImGui.GetPlatformIO()
|
|
platform_io.Platform_SetImeDataFn = PlatformSetImeData
|
|
platform_io.Platform_SetClipboardTextFn = PlatformSetClipboardText
|
|
platform_io.Platform_GetClipboardTextFn = PlatformGetClipboardText
|
|
platform_io.Platform_OpenInShellFn = PlatformOpenInShell
|
|
local w, h = love.graphics.getDimensions()
|
|
UpdateMonitors(w, h)
|
|
love.keyboard.setKeyRepeat(true)
|
|
end
|
|
|
|
function ImGui_ImplLOVE_Shutdown()
|
|
local io = ImGui.GetIO()
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
for _, m in pairs(bd.Meshes) do m.Mesh:release() end
|
|
for _, t in pairs(bd.Textures) do t:release() end
|
|
io.BackendPlatformUserData = nil
|
|
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)))
|
|
end
|
|
|
|
function ImGui_ImplLOVE_NewFrame()
|
|
local io = ImGui.GetIO()
|
|
local bd = ImGui_ImplLOVE_GetBackendData()
|
|
|
|
local w, h = love.graphics.getDimensions()
|
|
if w ~= bd.ScreenW or h ~= bd.ScreenH then bd.ScreenW, bd.ScreenH = w, h; UpdateMonitors(w, h) end
|
|
ImVec2_CopyV(io.DisplaySize, w, h)
|
|
|
|
local now = love.timer.getTime()
|
|
io.DeltaTime = (now > bd.Time) and (now - bd.Time) or (1.0 / 60.0)
|
|
bd.Time = now
|
|
|
|
if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoMouseCursorChange) == 0 then
|
|
local cursor = ImGui.GetMouseCursor()
|
|
if cursor ~= bd.LastCursor then
|
|
bd.LastCursor = cursor
|
|
local name = CURSOR_MAP[cursor]
|
|
love.mouse.setVisible(cursor ~= ImGuiMouseCursor.None and not io.MouseDrawCursor)
|
|
love.mouse.setCursor(name and love.mouse.getSystemCursor(name) or nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------
|
|
-- LÖVE EVENT HOOKS (forward from love.* callbacks)
|
|
---------------------------------------------------------
|
|
|
|
local MOUSE_BUTTONS = { [1] = 0, [2] = 1, [3] = 2, [4] = 3, [5] = 4 } -- LÖVE: 1 left, 2 right, 3 middle
|
|
|
|
local function MouseMoved(x, y)
|
|
local io = ImGui.GetIO()
|
|
io:AddMouseSourceEvent(ImGuiMouseSource.Mouse)
|
|
io:AddMousePosEvent(x, y)
|
|
end
|
|
local function MousePressed(x, y, button)
|
|
local b = MOUSE_BUTTONS[button]
|
|
if b then local io = ImGui.GetIO(); io:AddMouseSourceEvent(ImGuiMouseSource.Mouse); io:AddMouseButtonEvent(b, true) end
|
|
end
|
|
local function MouseReleased(x, y, button)
|
|
local b = MOUSE_BUTTONS[button]
|
|
if b then ImGui.GetIO():AddMouseButtonEvent(b, false) end
|
|
end
|
|
local function WheelMoved(x, y) ImGui.GetIO():AddMouseWheelEvent(-x, y) end
|
|
local function KeyPressed(key)
|
|
UpdateKeyModifiers()
|
|
local k = KEY_MAP[key]
|
|
if k then ImGui.GetIO():AddKeyEvent(k, true) end
|
|
end
|
|
local function KeyReleased(key)
|
|
UpdateKeyModifiers()
|
|
local k = KEY_MAP[key]
|
|
if k then ImGui.GetIO():AddKeyEvent(k, false) end
|
|
end
|
|
local function TextInput(text)
|
|
local io = ImGui.GetIO()
|
|
for _, c in utf8.codes(text) do io:AddInputCharacter(c) end
|
|
end
|
|
local function Focus(f) ImGui.GetIO():AddFocusEvent(f) end
|
|
|
|
--- True when ImGui wants the mouse / keyboard (don't pass the event to your game)
|
|
local function WantCaptureMouse() return ImGui.GetIO().WantCaptureMouse end
|
|
local function WantCaptureKeyboard() return ImGui.GetIO().WantCaptureKeyboard end
|
|
|
|
ImGui_ImplLove = {
|
|
Init = ImGui_ImplLOVE_Init,
|
|
Shutdown = ImGui_ImplLOVE_Shutdown,
|
|
NewFrame = ImGui_ImplLOVE_NewFrame,
|
|
RenderDrawData = ImGui_ImplLOVE_RenderDrawData,
|
|
CreateTexture = ImGui_ImplLOVE_CreateTexture,
|
|
DestroyTexture = ImGui_ImplLOVE_DestroyTexture,
|
|
MouseMoved = MouseMoved,
|
|
MousePressed = MousePressed,
|
|
MouseReleased = MouseReleased,
|
|
WheelMoved = WheelMoved,
|
|
KeyPressed = KeyPressed,
|
|
KeyReleased = KeyReleased,
|
|
TextInput = TextInput,
|
|
Focus = Focus,
|
|
WantCaptureMouse = WantCaptureMouse,
|
|
WantCaptureKeyboard = WantCaptureKeyboard,
|
|
}
|
|
|
|
return true
|