forked from izaya/LuPPC
318 lines
7.4 KiB
Lua
318 lines
7.4 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 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
|
|
text = unsub(text, 1, -1 - ((x + len) - w))
|
|
end
|
|
tbuffer[y] = tbuffer[y]:sub(1, x - 1) ..
|
|
text .. tbuffer[y]:sub(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]
|
|
write("\27[38;2;", F&0xFF0000>>16, ";", F&0x00FF00>>8, ";", F&0x0000FF, "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]
|
|
write("\27[48;2;", B&0xFF0000>>16, ";", B&0x00FF00>>8, ";", B&0x0000FF, "m")
|
|
bbuffer[y] = bbuffer[y]:sub(1, x - 1) ..
|
|
string.rep(string.char(b - 1), len) .. bbuffer[y]:sub(x + len)
|
|
end
|
|
write("\27[", x, ";", y, "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",
|
|
B&0xFF0000>>16, B&0x00FF00>>8, B&0x0000FF)
|
|
end
|
|
if fc ~= pf then
|
|
pf = fc
|
|
local F = mapping[fc:byte() + 1]
|
|
str = str .. string.format("\27[38;2;%d;%d;%dm",
|
|
F&0xFF0000>>16, F&0x00FF00>>8, F&0x0000FF)
|
|
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")
|
|
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
|
|
lprint("GPU_COPY: " .. str .. " FROM " .. i .. " TO " .. (i + yd))
|
|
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
|