2017-10-05 17:07:48 +00:00
|
|
|
local class = require('class')
|
|
|
|
local Region = require('ui.region')
|
|
|
|
local Util = require('util')
|
|
|
|
|
2017-10-06 07:07:24 +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
|
|
|
|
|
|
|
|
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)
|
2018-01-24 22:39:38 +00:00
|
|
|
for i = self.height, h do
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
for i = 1, self.height do
|
|
|
|
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
|
|
|
|
if not visible then
|
|
|
|
self.parent:dirty()
|
|
|
|
-- set parent's lines to dirty for each line in self
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
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
|
|
|
|
self:writeBlit(x, y, text, bg, fg)
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:writeBlit(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
|
|
|
|
|
|
|
|
local function replace(sstr, pos, rstr, width)
|
|
|
|
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
|
|
|
|
|
|
|
|
function Canvas:reset()
|
2018-01-24 22:39:38 +00:00
|
|
|
self.regions = nil
|
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)
|
|
|
|
for i = 1, self.height do
|
|
|
|
self:writeLine(i, text, fg, bg)
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:punch(rect)
|
2018-01-24 22:39:38 +00:00
|
|
|
if not self.regions then
|
|
|
|
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
|
|
|
end
|
|
|
|
self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey)
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:blitClipped(device)
|
2018-01-24 22:39:38 +00:00
|
|
|
for _,region in ipairs(self.regions.region) do
|
|
|
|
self:blit(device,
|
|
|
|
{ x = region[1] - self.x + 1,
|
|
|
|
y = region[2] - self.y + 1,
|
|
|
|
ex = region[3]- self.x + 1,
|
|
|
|
ey = region[4] - self.y + 1 },
|
|
|
|
{ x = region[1], y = region[2] })
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:redraw(device)
|
2018-01-24 22:39:38 +00:00
|
|
|
self:reset()
|
|
|
|
if #self.layers > 0 then
|
|
|
|
for _,layer in pairs(self.layers) do
|
|
|
|
self:punch(layer)
|
|
|
|
end
|
|
|
|
self:blitClipped(device)
|
|
|
|
else
|
|
|
|
self:blit(device)
|
|
|
|
end
|
|
|
|
self:clean()
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:isDirty()
|
2018-01-24 22:39:38 +00:00
|
|
|
for _, line in pairs(self.lines) do
|
|
|
|
if line.dirty then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:dirty()
|
2018-01-24 22:39:38 +00:00
|
|
|
for _, line in pairs(self.lines) do
|
|
|
|
line.dirty = true
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:clean()
|
2018-01-24 22:39:38 +00:00
|
|
|
for _, line in pairs(self.lines) do
|
|
|
|
line.dirty = false
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:render(device, layers) --- redrawAll ?
|
2018-01-24 22:39:38 +00:00
|
|
|
layers = layers or self.layers
|
|
|
|
if #layers > 0 then
|
|
|
|
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
|
|
|
local l = Util.shallowCopy(layers)
|
|
|
|
for _, canvas in ipairs(layers) do
|
|
|
|
table.remove(l, 1)
|
|
|
|
if canvas.visible then
|
|
|
|
self:punch(canvas)
|
|
|
|
canvas:render(device, l)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:blitClipped(device)
|
|
|
|
self:reset()
|
|
|
|
else
|
|
|
|
self:blit(device)
|
|
|
|
end
|
|
|
|
self:clean()
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Canvas:blit(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
|
|
|
|
local line = self.lines[src.y + i]
|
|
|
|
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
|
|
|
|
--if tgt.y + i > self.ey then -- wrong place to do clipping ??
|
|
|
|
-- break
|
|
|
|
--end
|
|
|
|
device.setCursorPos(tgt.x, tgt.y + i)
|
|
|
|
device.blit(t, fg, bg)
|
|
|
|
end
|
|
|
|
end
|
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-08 03:03:18 +00:00
|
|
|
function Canvas.convertWindow(win, parent, wx, wy)
|
2018-01-24 22:39:38 +00:00
|
|
|
local w, h = win.getSize()
|
|
|
|
|
|
|
|
win.canvas = Canvas({
|
|
|
|
x = wx,
|
|
|
|
y = wy,
|
|
|
|
width = w,
|
|
|
|
height = h,
|
|
|
|
isColor = win.isColor(),
|
|
|
|
})
|
|
|
|
|
|
|
|
function win.clear()
|
|
|
|
win.canvas:clear(win.getBackgroundColor(), win.getTextColor())
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.clearLine()
|
|
|
|
local _, y = win.getCursorPos()
|
|
|
|
win.canvas:write(1,
|
|
|
|
y,
|
|
|
|
_rep(' ', win.canvas.width),
|
|
|
|
win.getBackgroundColor(),
|
|
|
|
win.getTextColor())
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.write(str)
|
|
|
|
local x, y = win.getCursorPos()
|
|
|
|
win.canvas:write(x,
|
|
|
|
y,
|
|
|
|
str,
|
|
|
|
win.getBackgroundColor(),
|
|
|
|
win.getTextColor())
|
|
|
|
win.setCursorPos(x + #str, y)
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.blit(text, fg, bg)
|
|
|
|
local x, y = win.getCursorPos()
|
|
|
|
win.canvas:writeBlit(x, y, text, bg, fg)
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.redraw()
|
|
|
|
win.canvas:redraw(parent)
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.scroll(n)
|
|
|
|
table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1))
|
|
|
|
win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width)
|
|
|
|
win.canvas:dirty()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.reposition(x, y, width, height)
|
|
|
|
win.canvas.x, win.canvas.y = x, y
|
|
|
|
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
|
|
|
|
end
|
|
|
|
|
|
|
|
win.clear()
|
2017-10-05 17:07:48 +00:00
|
|
|
end
|
|
|
|
|
2018-01-24 22:39:38 +00:00
|
|
|
function Canvas.scrollingWindow(win, wx, wy)
|
|
|
|
local w, h = win.getSize()
|
|
|
|
local scrollPos = 0
|
|
|
|
local maxScroll = h
|
|
|
|
|
|
|
|
-- canvas lines are are a sliding window within the local lines table
|
|
|
|
local lines = { }
|
|
|
|
|
|
|
|
local parent
|
|
|
|
local canvas = Canvas({
|
|
|
|
x = wx,
|
|
|
|
y = wy,
|
|
|
|
width = w,
|
|
|
|
height = h,
|
|
|
|
isColor = win.isColor(),
|
|
|
|
})
|
|
|
|
win.canvas = canvas
|
|
|
|
|
|
|
|
local function scrollTo(p, forceRedraw)
|
|
|
|
local ms = #lines - canvas.height -- max scroll
|
|
|
|
p = math.min(math.max(p, 0), ms) -- normalize
|
|
|
|
|
|
|
|
if p ~= scrollPos or forceRedraw then
|
|
|
|
scrollPos = p
|
|
|
|
for i = 1, canvas.height do
|
|
|
|
canvas.lines[i] = lines[i + scrollPos]
|
|
|
|
end
|
|
|
|
canvas:dirty()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.blit(text, fg, bg)
|
|
|
|
local x, y = win.getCursorPos()
|
|
|
|
win.canvas:writeBlit(x, y, text, bg, fg)
|
|
|
|
win.redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.clear()
|
|
|
|
lines = { }
|
|
|
|
for i = 1, canvas.height do
|
|
|
|
lines[i] = canvas.lines[i]
|
|
|
|
end
|
|
|
|
scrollPos = 0
|
|
|
|
canvas:clear(win.getBackgroundColor(), win.getTextColor())
|
|
|
|
win.redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.clearLine()
|
|
|
|
local _, y = win.getCursorPos()
|
|
|
|
|
|
|
|
scrollTo(#lines - canvas.height)
|
|
|
|
win.canvas:write(1,
|
|
|
|
y,
|
|
|
|
_rep(' ', win.canvas.width),
|
|
|
|
win.getBackgroundColor(),
|
|
|
|
win.getTextColor())
|
|
|
|
win.redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.redraw()
|
|
|
|
if parent and canvas.visible then
|
|
|
|
local x, y = win.getCursorPos()
|
|
|
|
for i = 1, canvas.height do
|
|
|
|
local line = canvas.lines[i]
|
|
|
|
if line and line.dirty then
|
|
|
|
parent.setCursorPos(canvas.x, canvas.y + i - 1)
|
|
|
|
parent.blit(line.text, line.fg, line.bg)
|
|
|
|
line.dirty = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
win.setCursorPos(x, y)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- doesn't support negative scrolling...
|
|
|
|
function win.scroll(n)
|
|
|
|
for _ = 1, n do
|
|
|
|
lines[#lines + 1] = {
|
|
|
|
text = _rep(' ', canvas.width),
|
|
|
|
fg = _rep(canvas.palette[win.getTextColor()], canvas.width),
|
|
|
|
bg = _rep(canvas.palette[win.getBackgroundColor()], canvas.width),
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
while #lines > maxScroll do
|
|
|
|
table.remove(lines, 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
scrollTo(maxScroll, true)
|
|
|
|
win.redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.scrollDown()
|
|
|
|
scrollTo(scrollPos + 1)
|
|
|
|
win.redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.scrollUp()
|
|
|
|
scrollTo(scrollPos - 1)
|
|
|
|
win.redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.setMaxScroll(ms)
|
|
|
|
maxScroll = ms
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.setParent(p)
|
|
|
|
parent = p
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.write(str)
|
|
|
|
str = tostring(str) or ''
|
|
|
|
|
|
|
|
local x, y = win.getCursorPos()
|
|
|
|
scrollTo(#lines - canvas.height)
|
|
|
|
win.blit(str,
|
|
|
|
_rep(canvas.palette[win.getTextColor()], #str),
|
|
|
|
_rep(canvas.palette[win.getBackgroundColor()], #str))
|
|
|
|
win.setCursorPos(x + #str, y)
|
|
|
|
end
|
|
|
|
|
|
|
|
function win.reposition(x, y, width, height)
|
|
|
|
win.canvas.x, win.canvas.y = x, y
|
|
|
|
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
|
|
|
|
end
|
|
|
|
|
|
|
|
win.clear()
|
|
|
|
end
|
2017-10-05 17:07:48 +00:00
|
|
|
return Canvas
|