LuPPC/src/lua/core/textgpu.lua

325 lines
7.3 KiB
Lua

local textgpu = {}
local mapping = {}
local palette = {}
-- generate color mapping
for g=0, 255, 0x24 do
for b=0, 256, 0x40 do
for r=0, 255, 0x33 do
mapping[#mapping+1] = (math.min(r, 255) * 0x10000)
+ (math.min(g, 255) * 0x100) + math.min(b, 255)
end
end
end
for i=0, 15, 1 do
mapping[#mapping+1] = (i+1) * 0x0f0f0f
palette[i] = (i+1) * 0x0f0f0f
end
local write = io.write
local flush = io.flush
local background = 1
local foreground = 256
local tbuffer = {}
local bbuffer = {}
local fbuffer = {}
local function prep(w, h)
local tl = string.rep(" ", w)
local bb = string.rep("\0", w)
local fb = string.rep("\255", w)
for i=1, h, 1 do
tbuffer[i] = tl
bbuffer[i] = bb
fbuffer[i] = fb
end
end
local fg = 256
local bg = 1
local w, h = 1, 1
local function rgb(i)
return i>>16&0xFF, i>>8&0xFF, i&0xFF
end
local unsub, fullRefresh
local function set(x, y, text, f, b, safe)
if x > w or x < 1 or y > h or y < 1 or not tbuffer[y] then
if safe then
return
else
error("index out of bounds: " .. y, 3)
end
end
if not text then return end
f = f or fg
b = b or bg
local len = utf8.len(text)
if x + len > w then
--len = (x + len) - w
--text = unsub(text, 1, len)
end
tbuffer[y] = unsub(tbuffer[y], 1, x - 1) ..
text .. unsub(tbuffer[y], x + len)
if type(f) == "string" then
fbuffer[y] = fbuffer[y]:sub(1, x - 1) ..
f .. fbuffer[y]:sub(x + len)
else
local F = mapping[f]
local r, g, _b = rgb(F)
write("\27[38;2;", r, ";", g, ";", _b, "m")
fbuffer[y] = fbuffer[y]:sub(1, x - 1) ..
string.rep(string.char(f - 1), len) .. fbuffer[y]:sub(x + len)
end
if type(b) == "string" then
bbuffer[y] = bbuffer[y]:sub(1, x - 1) ..
b .. bbuffer[y]:sub(x + len)
else
local B = mapping[b]
local r, g, _b = rgb(B)
write("\27[48;2;", r, ";", g, ";", _b, "m")
bbuffer[y] = bbuffer[y]:sub(1, x - 1) ..
string.rep(string.char(b - 1), len) .. bbuffer[y]:sub(x + len)
end
write("\27[", y, ";", x, "H", text)
flush()
end
fullRefresh = function()
for i=1, h, 1 do
local text = tbuffer[i]
local fgt = fbuffer[i]
local bgt = bbuffer[i]
local pb, pf
fgt = fgt:gsub("()(.)", function(n, fc)
local bc, tc = bgt:sub(n,n), text:sub(n,n)
local str = ""
if bc ~= pb then
pb = bc
local B = mapping[bc:byte() + 1]
str = str .. string.format("\27[48;2;%d;%d;%dm",
rgb(B))
end
if fc ~= pf then
pf = fc
local F = mapping[fc:byte() + 1]
str = str .. string.format("\27[38;2;%d;%d;%dm",
rgb(F))
end
str = str .. tc
return str
end)
write("\27[", i, ";1H", fgt)
end
flush()
end
local function get(x, y, len, safe)
if x < 1 or x > w or y < 1 or y > h then
if safe then
return
else
error("index out of bounds: " .. y, 3)
end
end
len = (len or 1) - 1
if x + len > w then
len = (x + len) - w
end
local text = unsub(tbuffer[y], x, x + len)
if len == 0 then
return text,
mapping[fbuffer[y]:sub(x, x):byte() + 1],
mapping[bbuffer[y]:sub(x, x):byte() + 1]
else
return text, fbuffer[y]:sub(x, x + len), bbuffer[y]:sub(x, x + len)
end
end
function textgpu.start()
unsub = modules.sandbox.unicode.sub
local gpu = {}
function gpu.setForeground(color, ispalette)
checkArg(1, color, "number")
checkArg(2, ispalette, "boolean", "nil")
if ispalette then
if not palette[color] then
error("invalid palette index", 2)
end
color = palette[color]
end
local index = math.floor(modules.color.nearest(color, mapping))
fg = index or fg
return true
end
function gpu.setBackground(color, ispalette)
checkArg(1, color, "number")
checkArg(2, ispalette, "boolean", "nil")
if ispalette then
if not palette[color] then
error("invalid palette index", 2)
end
color = palette[color]
end
local index = math.floor(modules.color.nearest(color, mapping))
bg = index or bg
return true
end
function gpu.setPaletteColor(c, v)
checkArg(1, c, "number")
checkArg(2, v, "number")
if not palette[c] then
error("invalid palette index", 2)
end
palette[c] = v
end
function gpu.getPaletteColor(c)
checkArg(1, c, "number")
if not palette[c] then
error("invalid palette index", 2)
end
return palette[c]
end
function gpu.getForeground()
return mapping[fg]
end
function gpu.getBackground()
return mapping[bg]
end
function gpu.getDepth()
return 4
end
function gpu.maxDepth()
return 4
end
function gpu.setDepth()
return true
end
function gpu.getResolution()
return termutils.getSize()
end
function gpu.setResolution()
return false
end
gpu.maxResolution = gpu.getResolution
gpu.getViewport = gpu.getResolution
gpu.setViewport = gpu.setResolution
function gpu.set(x, y, text, vert)
checkArg(1, x, "number")
checkArg(2, y, "number")
checkArg(3, text, "string")
checkArg(4, vert, "boolean", "nil")
vert = false
if vert then
local i = 1
local len = utf8.len(text)
while i <= len do
set(x, y + i - 1, unsub(text, i, i))
i = i + 1
end
return true
else
set(x, y, text)
return true
end
end
function gpu.get(x, y)
checkArg(1, x, "number")
checkArg(2, y, "number")
return get(x, y)
end
function gpu.fill(x, y, W, H, c)
checkArg(1, x, "number")
checkArg(1, y, "number")
checkArg(1, W, "number")
checkArg(1, H, "number")
checkArg(1, c, "string")
c = unsub(c, 1, 1)
if #c == 0 then return true end
local str = c:rep(W)
for i=1, H, 1 do
set(x, y + i - 1, str, nil, nil, true)
end
return true
end
function gpu.copy(x, y, W, H, xd, yd)
checkArg(1, x, "number")
checkArg(2, y, "number")
checkArg(3, W, "number")
checkArg(4, H, "number")
checkArg(5, xd, "number")
checkArg(6, yd, "number")
if xd == 0 and yd == 0 then return true end -- hah
local start, stop, step
if yd < 0 then -- moving up - copy from the top down
start, stop, step = y, y + H, 1
else -- moving down - copy from the bottom up
start, stop, step = y + H, y, -1
end
for i=start, stop, step do
local str, fstr, bstr = get(x, i, W, true)
if str and fstr and bstr then
set(x + xd, i + yd, str, fstr, bstr, true)
end
end
fullRefresh()
return true
end
local screenAddr
function gpu.getScreen()
return screenAddr
end
function gpu.bind() --[[STUB]] end
if not termutils.init() then
return nil, "Cannot initialize terminal based gpu"
end
write("\x1b[?25l") --Disable cursor
w, h = gpu.getResolution()
prep(w, h)
fullRefresh()
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
local gpuaddr = modules.component.api.register(nil, "gpu", gpu)
screenAddr = modules.component.api.register(nil, "screen", {getKeyboards = function() return {"TODO:SetThisUuid"} end}) --verry dummy screen, TODO: make it better, kbd uuid also in epoll.c
modules.component.api.register("TODO:SetThisUuid", "keyboard", {})
deadhooks[#deadhooks + 1] = function()
write("\x1b[?25h\x1b[" .. ((h-1)|0) .. ";1H") --Enable cursor on quit
io.flush()
termutils.restore()
end
return gpuaddr
end
return textgpu