opus/sys/apis/ui/canvas.lua

388 lines
8.0 KiB
Lua
Raw Normal View History

2017-10-05 17:07:48 +00:00
local class = require('class')
local Region = require('ui.region')
local Util = require('util')
2019-02-06 04:03:57 +00:00
local _rep = string.rep
local _sub = string.sub
local _gsub = string.gsub
2017-10-08 03:03:18 +00:00
local colors = _G.colors
2017-10-06 07:07:24 +00:00
local Canvas = class()
2017-10-05 17:07:48 +00:00
2017-10-06 07:07:24 +00:00
Canvas.colorPalette = { }
Canvas.darkPalette = { }
Canvas.grayscalePalette = { }
for n = 1, 16 do
2018-01-24 22:39:38 +00:00
Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n)
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
--[[
A canvas can have more lines than canvas.height in order to scroll
]]
2017-10-05 17:07:48 +00:00
function Canvas:init(args)
2018-01-24 22:39:38 +00:00
self.x = 1
self.y = 1
self.layers = { }
Util.merge(self, args)
self.ex = self.x + self.width - 1
self.ey = self.y + self.height - 1
if not self.palette then
if self.isColor then
self.palette = Canvas.colorPalette
else
self.palette = Canvas.grayscalePalette
end
end
self.lines = { }
for i = 1, self.height do
self.lines[i] = { }
end
2017-10-05 17:07:48 +00:00
end
2017-10-06 17:39:47 +00:00
function Canvas:move(x, y)
2018-01-24 22:39:38 +00:00
self.x, self.y = x, y
self.ex = self.x + self.width - 1
self.ey = self.y + self.height - 1
2017-10-06 17:39:47 +00:00
end
2017-10-05 17:07:48 +00:00
function Canvas:resize(w, h)
2019-02-06 04:03:57 +00:00
for i = #self.lines, h do
2018-01-24 22:39:38 +00:00
self.lines[i] = { }
end
while #self.lines > h do
table.remove(self.lines, #self.lines)
end
if w ~= self.width then
for i = 1, self.height do
self.lines[i] = { dirty = true }
end
end
self.ex = self.x + w - 1
self.ey = self.y + h - 1
self.width = w
self.height = h
2017-10-05 17:07:48 +00:00
end
function Canvas:copy()
2018-01-24 22:39:38 +00:00
local b = Canvas({
x = self.x,
y = self.y,
width = self.width,
height = self.height,
isColor = self.isColor,
})
2019-02-06 04:03:57 +00:00
for i = 1, #self.lines do
2018-01-24 22:39:38 +00:00
b.lines[i].text = self.lines[i].text
b.lines[i].fg = self.lines[i].fg
b.lines[i].bg = self.lines[i].bg
end
return b
2017-10-05 17:07:48 +00:00
end
2017-10-11 15:37:52 +00:00
function Canvas:addLayer(layer)
2018-01-24 22:39:38 +00:00
local canvas = Canvas({
x = layer.x,
y = layer.y,
width = layer.width,
height = layer.height,
isColor = self.isColor,
})
canvas.parent = self
table.insert(self.layers, canvas)
return canvas
2017-10-05 17:07:48 +00:00
end
function Canvas:removeLayer()
2018-01-24 22:39:38 +00:00
for k, layer in pairs(self.parent.layers) do
if layer == self then
self:setVisible(false)
table.remove(self.parent.layers, k)
break
end
end
2017-10-05 17:07:48 +00:00
end
function Canvas:setVisible(visible)
2018-01-24 22:39:38 +00:00
self.visible = visible
2019-01-30 21:27:09 +00:00
if not visible and self.parent then
2018-01-24 22:39:38 +00:00
self.parent:dirty()
2019-01-30 21:27:09 +00:00
-- TODO: set parent's lines to dirty for each line in self
2018-01-24 22:39:38 +00:00
end
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
-- Push a layer to the top
function Canvas:raise()
if self.parent then
local layers = self.parent.layers or { }
for k, v in pairs(layers) do
if v == self then
table.insert(layers, table.remove(layers, k))
break
end
end
end
end
2017-10-05 17:07:48 +00:00
function Canvas:write(x, y, text, bg, fg)
2018-01-24 22:39:38 +00:00
if bg then
bg = _rep(self.palette[bg], #text)
end
if fg then
fg = _rep(self.palette[fg], #text)
end
2019-02-06 04:03:57 +00:00
self:blit(x, y, text, bg, fg)
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
function Canvas:blit(x, y, text, bg, fg)
2018-01-24 22:39:38 +00:00
if y > 0 and y <= #self.lines and x <= self.width then
local width = #text
-- fix ffs
if x < 1 then
text = _sub(text, 2 - x)
if bg then
bg = _sub(bg, 2 - x)
end
2018-12-01 05:55:42 +00:00
if fg then
2018-01-24 22:39:38 +00:00
fg = _sub(fg, 2 - x)
end
width = width + x - 1
x = 1
end
if x + width - 1 > self.width then
text = _sub(text, 1, self.width - x + 1)
if bg then
bg = _sub(bg, 1, self.width - x + 1)
end
2018-12-01 05:55:42 +00:00
if fg then
2018-01-24 22:39:38 +00:00
fg = _sub(fg, 1, self.width - x + 1)
end
width = #text
end
if width > 0 then
2019-02-06 04:03:57 +00:00
local function replace(sstr, pos, rstr)
2018-01-24 22:39:38 +00:00
if pos == 1 and width == self.width then
return rstr
elseif pos == 1 then
return rstr .. _sub(sstr, pos+width)
elseif pos + width > self.width then
return _sub(sstr, 1, pos-1) .. rstr
end
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
end
local line = self.lines[y]
line.dirty = true
line.text = replace(line.text, x, text, width)
if fg then
line.fg = replace(line.fg, x, fg, width)
end
if bg then
line.bg = replace(line.bg, x, bg, width)
end
end
end
2017-10-05 17:07:48 +00:00
end
function Canvas:writeLine(y, text, fg, bg)
2018-01-24 22:39:38 +00:00
self.lines[y].dirty = true
self.lines[y].text = text
self.lines[y].fg = fg
self.lines[y].bg = bg
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
function Canvas:clearLine(y, bg, fg)
fg = _rep(self.palette[fg or colors.white], self.width)
bg = _rep(self.palette[bg or colors.black], self.width)
self:writeLine(y, _rep(' ', self.width), fg, bg)
2017-10-05 17:07:48 +00:00
end
function Canvas:clear(bg, fg)
2018-01-24 22:39:38 +00:00
local text = _rep(' ', self.width)
fg = _rep(self.palette[fg or colors.white], self.width)
bg = _rep(self.palette[bg or colors.black], self.width)
2019-02-06 04:03:57 +00:00
for i = 1, #self.lines do
2018-01-24 22:39:38 +00:00
self:writeLine(i, text, fg, bg)
end
2017-10-05 17:07:48 +00:00
end
function Canvas:punch(rect)
2019-02-06 04:03:57 +00:00
local offset = { x = 0, y = 0 }
self.regions:subRect(rect.x + offset.x, rect.y + offset.y, rect.ex + offset.x, rect.ey + offset.y)
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
function Canvas:blitClipped(device, offset)
offset = { x = self.x, y = self.y }
local parent = self.parent
while parent do
offset.x = offset.x + parent.x - 1
offset.y = offset.y + parent.y - 1
parent = parent.parent
end
2018-01-24 22:39:38 +00:00
for _,region in ipairs(self.regions.region) do
2019-02-06 04:03:57 +00:00
self:blitRect(device,
{ x = region[1],
y = region[2],
ex = region[3],
ey = region[4] },
{ x = region[1] + offset.x - 1, y = region[2] + offset.y - 1 })
2018-01-24 22:39:38 +00:00
end
2017-10-05 17:07:48 +00:00
end
function Canvas:redraw(device)
2019-02-07 08:10:05 +00:00
-- self:dirty()
-- self:render(device)
2018-01-24 22:39:38 +00:00
if #self.layers > 0 then
for _,layer in pairs(self.layers) do
self:punch(layer)
end
self:blitClipped(device)
else
2019-02-07 08:10:05 +00:00
self:renderLayers(device)
2018-01-24 22:39:38 +00:00
end
self:clean()
2017-10-05 17:07:48 +00:00
end
function Canvas:isDirty()
2019-02-06 04:03:57 +00:00
for i = 1, #self.lines do
if self.lines[i].dirty then
2018-01-24 22:39:38 +00:00
return true
end
end
2017-10-05 17:07:48 +00:00
end
function Canvas:dirty()
2019-02-06 04:03:57 +00:00
for i = 1, #self.lines do
self.lines[i].dirty = true
2018-01-24 22:39:38 +00:00
end
2019-01-30 21:27:09 +00:00
if self.layers then
for _, canvas in pairs(self.layers) do
canvas:dirty()
end
end
2017-10-05 17:07:48 +00:00
end
function Canvas:clean()
2019-02-06 04:03:57 +00:00
for i = 1, #self.lines do
self.lines[i].dirty = nil
2018-01-24 22:39:38 +00:00
end
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
function Canvas:renderLayers(device, offset)
if not offset then
offset = { x = self.x, y = self.y }
end
if #self.layers > 0 then
self.regions = Region.new(1, 1, self.ex, self.ey)
for i = 1, #self.layers do
local canvas = self.layers[i]
2018-01-24 22:39:38 +00:00
if canvas.visible then
2019-02-06 04:03:57 +00:00
-- punch out this area from the parent's canvas
2018-01-24 22:39:38 +00:00
self:punch(canvas)
2019-02-06 04:03:57 +00:00
-- get the area to render for this layer
canvas.regions = Region.new(canvas.x, canvas.y, canvas.ex, canvas.ey)
-- determine if we should render this layer by punching
-- out any layers that overlap this one
for j = i + 1, #self.layers do
if self.layers[j].visible then
canvas:punch(self.layers[j])
end
end
2018-01-24 22:39:38 +00:00
end
end
2019-02-06 04:03:57 +00:00
for _, canvas in ipairs(self.layers) do
if canvas.visible and #canvas.regions.region > 0 then
canvas:renderLayers(device, {
x = canvas.x, --offset.x + self.x - 1,
y = canvas.y,
})
end
end
self:blitClipped(device, offset)
--elseif #self.regions.region > 0 then
-- self:blitClipped(device, offset)
2018-01-24 22:39:38 +00:00
else
2019-02-06 04:03:57 +00:00
offset = { x = self.x, y = self.y }
local parent = self.parent
while parent do
offset.x = offset.x + parent.x - 1
offset.y = offset.y + parent.y - 1
parent = parent.parent
end
self:blitRect(device, nil, offset)
2018-01-24 22:39:38 +00:00
end
self:clean()
2017-10-05 17:07:48 +00:00
end
2019-02-06 04:03:57 +00:00
function Canvas:render(device)
--_G._p = self
if #self.layers > 0 then
self:renderLayers(device)
else
self:blitRect(device)
self:clean()
end
end
function Canvas:blitRect(device, src, tgt)
2018-01-24 22:39:38 +00:00
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
tgt = tgt or self
for i = 0, src.ey - src.y do
2019-02-06 04:03:57 +00:00
local line = self.lines[src.y + i + (self.offy or 0)]
2018-01-24 22:39:38 +00:00
if line and line.dirty then
local t, fg, bg = line.text, line.fg, line.bg
if src.x > 1 or src.ex < self.ex then
t = _sub(t, src.x, src.ex)
fg = _sub(fg, src.x, src.ex)
bg = _sub(bg, src.x, src.ex)
end
device.setCursorPos(tgt.x, tgt.y + i)
device.blit(t, fg, bg)
end
end
2019-02-06 04:03:57 +00:00
--os.sleep(.1)
2017-10-05 17:07:48 +00:00
end
2017-10-06 07:07:24 +00:00
function Canvas:applyPalette(palette)
2018-01-24 22:39:38 +00:00
local lookup = { }
for n = 1, 16 do
lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)]
end
2017-10-06 07:07:24 +00:00
2018-01-24 22:39:38 +00:00
for _, l in pairs(self.lines) do
l.fg = _gsub(l.fg, '%w', lookup)
l.bg = _gsub(l.bg, '%w', lookup)
l.dirty = true
end
2017-10-06 07:07:24 +00:00
2018-01-24 22:39:38 +00:00
self.palette = palette
2017-10-06 07:07:24 +00:00
end
2017-10-05 17:07:48 +00:00
return Canvas