Ui enhancements 2.0 (#31)
* canvas overhaul * minor tweaks * list mode for overview * bugfixes + tweaks for editor 2.0 * minor tweaks * more editor work * refactor + new transitions * use layout() where appropriate and cleanup * mouse triple click + textEntry scroll ind * cleanup * cleanup + theme editor * color rework + cleanup * changes for deprecated ui methods * can now use named colors
This commit was merged in pull request #31.
This commit is contained in:
@@ -9,28 +9,34 @@ local colors = _G.colors
|
||||
|
||||
local Canvas = class()
|
||||
|
||||
Canvas.__visualize = false
|
||||
Canvas.colorPalette = { }
|
||||
Canvas.darkPalette = { }
|
||||
Canvas.grayscalePalette = { }
|
||||
|
||||
for n = 1, 16 do
|
||||
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)
|
||||
local function genPalette(map)
|
||||
local t = { }
|
||||
local rcolors = Util.transpose(colors)
|
||||
for n = 1, 16 do
|
||||
local pow = 2 ^ (n - 1)
|
||||
local ch = _sub(map, n, n)
|
||||
t[pow] = ch
|
||||
t[rcolors[pow]] = ch
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
Canvas.colorPalette = genPalette('0123456789abcdef')
|
||||
Canvas.grayscalePalette = genPalette('088888878877787f')
|
||||
|
||||
--[[
|
||||
A canvas can have more lines than canvas.height in order to scroll
|
||||
]]
|
||||
|
||||
TODO: finish vertical scrolling
|
||||
]]
|
||||
function Canvas:init(args)
|
||||
self.x = 1
|
||||
self.y = 1
|
||||
self.layers = { }
|
||||
self.bg = colors.black
|
||||
self.fg = colors.white
|
||||
|
||||
Util.merge(self, args)
|
||||
|
||||
self.x = self.x or 1
|
||||
self.y = self.y or 1
|
||||
self.ex = self.x + self.width - 1
|
||||
self.ey = self.y + self.height - 1
|
||||
|
||||
@@ -46,16 +52,31 @@ function Canvas:init(args)
|
||||
for i = 1, self.height do
|
||||
self.lines[i] = { }
|
||||
end
|
||||
|
||||
self:clear()
|
||||
end
|
||||
|
||||
function Canvas:move(x, y)
|
||||
self.x, self.y = x, y
|
||||
self.ex = self.x + self.width - 1
|
||||
self.ey = self.y + self.height - 1
|
||||
if self.parent then
|
||||
self.parent:dirty(true)
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:resize(w, h)
|
||||
for i = #self.lines, h do
|
||||
self:resizeBuffer(w, h)
|
||||
|
||||
self.ex = self.x + w - 1
|
||||
self.ey = self.y + h - 1
|
||||
self.width = w
|
||||
self.height = h
|
||||
end
|
||||
|
||||
-- resize the canvas buffer - not the canvas itself
|
||||
function Canvas:resizeBuffer(w, h)
|
||||
for i = #self.lines + 1, h do
|
||||
self.lines[i] = { }
|
||||
self:clearLine(i)
|
||||
end
|
||||
@@ -66,26 +87,24 @@ function Canvas:resize(w, h)
|
||||
|
||||
if w < self.width then
|
||||
for i = 1, h do
|
||||
self.lines[i].text = _sub(self.lines[i].text, 1, w)
|
||||
self.lines[i].fg = _sub(self.lines[i].fg, 1, w)
|
||||
self.lines[i].bg = _sub(self.lines[i].bg, 1, w)
|
||||
local ln = self.lines[i]
|
||||
ln.text = _sub(ln.text, 1, w)
|
||||
ln.fg = _sub(ln.fg, 1, w)
|
||||
ln.bg = _sub(ln.bg, 1, w)
|
||||
end
|
||||
elseif w > self.width then
|
||||
local d = w - self.width
|
||||
local text = _rep(' ', d)
|
||||
local fg = _rep(self.palette[self.fg or colors.white], d)
|
||||
local bg = _rep(self.palette[self.bg or colors.black], d)
|
||||
local fg = _rep(self.palette[self.fg], d)
|
||||
local bg = _rep(self.palette[self.bg], d)
|
||||
for i = 1, h do
|
||||
self.lines[i].text = self.lines[i].text .. text
|
||||
self.lines[i].fg = self.lines[i].fg .. fg
|
||||
self.lines[i].bg = self.lines[i].bg .. bg
|
||||
local ln = self.lines[i]
|
||||
ln.text = ln.text .. text
|
||||
ln.fg = ln.fg .. fg
|
||||
ln.bg = ln.bg .. bg
|
||||
ln.dirty = true
|
||||
end
|
||||
end
|
||||
|
||||
self.ex = self.x + w - 1
|
||||
self.ey = self.y + h - 1
|
||||
self.width = w
|
||||
self.height = h
|
||||
end
|
||||
|
||||
function Canvas:copy()
|
||||
@@ -105,30 +124,26 @@ function Canvas:copy()
|
||||
end
|
||||
|
||||
function Canvas:addLayer(layer)
|
||||
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
|
||||
layer.parent = self
|
||||
if not self.children then
|
||||
self.children = { }
|
||||
end
|
||||
table.insert(self.children, 1, layer)
|
||||
return layer
|
||||
end
|
||||
|
||||
function Canvas:removeLayer()
|
||||
for k, layer in pairs(self.parent.layers) do
|
||||
for k, layer in pairs(self.parent.children) do
|
||||
if layer == self then
|
||||
self:setVisible(false)
|
||||
table.remove(self.parent.layers, k)
|
||||
table.remove(self.parent.children, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:setVisible(visible)
|
||||
self.visible = visible
|
||||
self.visible = visible -- TODO: use self.active = visible
|
||||
if not visible and self.parent then
|
||||
self.parent:dirty()
|
||||
-- TODO: set parent's lines to dirty for each line in self
|
||||
@@ -137,11 +152,10 @@ end
|
||||
|
||||
-- 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 self.parent and self.parent.children then
|
||||
for k, v in pairs(self.parent.children) do
|
||||
if v == self then
|
||||
table.insert(layers, table.remove(layers, k))
|
||||
table.insert(self.parent.children, table.remove(self.parent.children, k))
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -161,54 +175,42 @@ end
|
||||
function Canvas:blit(x, y, text, bg, fg)
|
||||
if y > 0 and y <= #self.lines and x <= self.width then
|
||||
local width = #text
|
||||
local tx, tex
|
||||
|
||||
-- fix ffs
|
||||
if x < 1 then
|
||||
text = _sub(text, 2 - x)
|
||||
if bg then
|
||||
bg = _sub(bg, 2 - x)
|
||||
end
|
||||
if fg then
|
||||
fg = _sub(fg, 2 - x)
|
||||
end
|
||||
tx = 2 - x
|
||||
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
|
||||
if fg then
|
||||
fg = _sub(fg, 1, self.width - x + 1)
|
||||
end
|
||||
width = #text
|
||||
tex = self.width - x + (tx or 1)
|
||||
width = tex - (tx or 1) + 1
|
||||
end
|
||||
|
||||
if width > 0 then
|
||||
|
||||
local function replace(sstr, pos, rstr)
|
||||
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
|
||||
local function replace(sstr, rstr)
|
||||
if tx or tex then
|
||||
rstr = _sub(rstr, tx or 1, tex)
|
||||
end
|
||||
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
|
||||
if x == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif x == 1 then
|
||||
return rstr .. _sub(sstr, x + width)
|
||||
elseif x + width > self.width then
|
||||
return _sub(sstr, 1, x - 1) .. rstr
|
||||
end
|
||||
return _sub(sstr, 1, x - 1) .. rstr .. _sub(sstr, x + width)
|
||||
end
|
||||
|
||||
local line = self.lines[y]
|
||||
if line then
|
||||
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
|
||||
line.dirty = true
|
||||
line.text = replace(line.text, text)
|
||||
if fg then
|
||||
line.fg = replace(line.fg, fg)
|
||||
end
|
||||
if bg then
|
||||
line.bg = replace(line.bg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -224,15 +226,15 @@ function Canvas:writeLine(y, text, fg, bg)
|
||||
end
|
||||
|
||||
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)
|
||||
fg = _rep(self.palette[fg or self.fg], self.width)
|
||||
bg = _rep(self.palette[bg or self.bg], self.width)
|
||||
self:writeLine(y, _rep(' ', self.width), fg, bg)
|
||||
end
|
||||
|
||||
function Canvas:clear(bg, fg)
|
||||
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)
|
||||
fg = _rep(self.palette[fg or self.fg], self.width)
|
||||
bg = _rep(self.palette[bg or self.bg], self.width)
|
||||
for i = 1, #self.lines do
|
||||
self:writeLine(i, text, fg, bg)
|
||||
end
|
||||
@@ -246,13 +248,16 @@ function Canvas:isDirty()
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:dirty()
|
||||
for i = 1, #self.lines do
|
||||
self.lines[i].dirty = true
|
||||
end
|
||||
if self.layers then
|
||||
for _, canvas in pairs(self.layers) do
|
||||
canvas:dirty()
|
||||
function Canvas:dirty(includingChildren)
|
||||
if self.lines then
|
||||
for i = 1, #self.lines do
|
||||
self.lines[i].dirty = true
|
||||
end
|
||||
|
||||
if includingChildren and self.children then
|
||||
for _, child in pairs(self.children) do
|
||||
child:dirty(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -278,115 +283,95 @@ function Canvas:applyPalette(palette)
|
||||
self.palette = palette
|
||||
end
|
||||
|
||||
function Canvas:render(device)
|
||||
local offset = { x = 0, y = 0 }
|
||||
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
|
||||
if #self.layers > 0 then
|
||||
self:__renderLayers(device, offset)
|
||||
else
|
||||
self:__blitRect(device, nil, {
|
||||
x = self.x + offset.x,
|
||||
y = self.y + offset.y
|
||||
})
|
||||
self:clean()
|
||||
-- either render directly to the device
|
||||
-- or use another canvas as a backing buffer
|
||||
function Canvas:render(device, doubleBuffer)
|
||||
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }, doubleBuffer)
|
||||
|
||||
-- doubleBuffering to reduce the amount of
|
||||
-- setCursorPos, blits
|
||||
if doubleBuffer then
|
||||
--[[
|
||||
local drew = false
|
||||
local bg = _rep(2, device.width)
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, bg)
|
||||
drew = true
|
||||
end
|
||||
end
|
||||
if drew then
|
||||
local c = os.clock()
|
||||
repeat until os.clock()-c > .1
|
||||
end
|
||||
]]
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, v.bg)
|
||||
v.dirty = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- regions are comprised of absolute values that coorespond to the output device.
|
||||
-- regions are comprised of absolute values that correspond to the output device.
|
||||
-- canvases have coordinates relative to their parent.
|
||||
-- canvas layer's stacking order is determined by the position within the array.
|
||||
-- layers in the beginning of the array are overlayed by layers further down in
|
||||
-- the array.
|
||||
function Canvas:__renderLayers(device, offset)
|
||||
if #self.layers > 0 then
|
||||
self.regions = self.regions or Region.new(self.x + offset.x, self.y + offset.y, self.ex + offset.x, self.ey + offset.y)
|
||||
|
||||
for i = 1, #self.layers do
|
||||
local canvas = self.layers[i]
|
||||
if canvas.visible then
|
||||
|
||||
-- punch out this area from the parent's canvas
|
||||
self:__punch(canvas, offset)
|
||||
|
||||
function Canvas:__renderLayers(device, offset, doubleBuffer)
|
||||
if self.children then
|
||||
for i = #self.children, 1, -1 do
|
||||
local canvas = self.children[i]
|
||||
if canvas.visible or canvas.enabled then
|
||||
-- get the area to render for this layer
|
||||
canvas.regions = Region.new(
|
||||
canvas.x + offset.x,
|
||||
canvas.y + offset.y,
|
||||
canvas.ex + offset.x,
|
||||
canvas.ey + offset.y)
|
||||
canvas.x + offset.x - (self.offx or 0),
|
||||
canvas.y + offset.y - (self.offy or 0),
|
||||
canvas.ex + offset.x - (self.offx or 0),
|
||||
canvas.ey + offset.y - (self.offy or 0))
|
||||
|
||||
-- contain within parent
|
||||
canvas.regions:andRegion(self.regions)
|
||||
|
||||
-- punch out this area from the parent's canvas
|
||||
self.regions:subRect(
|
||||
canvas.x + offset.x - (self.offx or 0),
|
||||
canvas.y + offset.y - (self.offy or 0),
|
||||
canvas.ex + offset.x - (self.offx or 0),
|
||||
canvas.ey + offset.y - (self.offy or 0))
|
||||
|
||||
-- punch 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], offset)
|
||||
end
|
||||
end
|
||||
if #canvas.regions.region > 0 then
|
||||
canvas:__renderLayers(device, {
|
||||
x = canvas.x + offset.x - 1,
|
||||
y = canvas.y + offset.y - 1,
|
||||
})
|
||||
x = canvas.x + offset.x - 1 - (self.offx or 0),
|
||||
y = canvas.y + offset.y - 1 - (self.offy or 0),
|
||||
}, doubleBuffer)
|
||||
end
|
||||
canvas.regions = nil
|
||||
end
|
||||
end
|
||||
|
||||
self:__blitClipped(device, offset)
|
||||
self.regions = nil
|
||||
|
||||
elseif self.regions and #self.regions.region > 0 then
|
||||
self:__blitClipped(device, offset)
|
||||
self.regions = nil
|
||||
|
||||
else
|
||||
self:__blitRect(device, nil, {
|
||||
x = self.x + offset.x,
|
||||
y = self.y + offset.y
|
||||
})
|
||||
self.regions = nil
|
||||
end
|
||||
self:clean()
|
||||
end
|
||||
|
||||
function Canvas:__blitClipped(device, offset)
|
||||
if self.parent then
|
||||
-- contain the rendered region in the parent's region
|
||||
local p = Region.new(1, 1,
|
||||
self.parent.width + offset.x - self.x + 1,
|
||||
self.parent.height + offset.y - self.y + 1)
|
||||
self.regions:andRegion(p)
|
||||
end
|
||||
|
||||
for _,region in ipairs(self.regions.region) do
|
||||
self:__blitRect(device,
|
||||
{ x = region[1] - offset.x,
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y},
|
||||
{ x = region[1], y = region[2] })
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y },
|
||||
{ x = region[1], y = region[2] }, doubleBuffer)
|
||||
end
|
||||
self.regions = nil
|
||||
|
||||
self:clean()
|
||||
end
|
||||
|
||||
function Canvas:__punch(rect, offset)
|
||||
self.regions:subRect(
|
||||
rect.x + offset.x,
|
||||
rect.y + offset.y,
|
||||
rect.ex + offset.x,
|
||||
rect.ey + offset.y)
|
||||
end
|
||||
|
||||
-- performance can probably be improved by using one more buffer tied to the device
|
||||
function Canvas:__blitRect(device, src, tgt)
|
||||
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
|
||||
tgt = tgt or self
|
||||
|
||||
function Canvas:__blitRect(device, src, tgt, doubleBuffer)
|
||||
-- for visualizing updates on the screen
|
||||
if Canvas.__visualize then
|
||||
--[[
|
||||
if Canvas.__visualize or self.visualize then
|
||||
local drew
|
||||
local t = _rep(' ', src.ex-src.x + 1)
|
||||
local bg = _rep(2, src.ex-src.x + 1)
|
||||
@@ -399,10 +384,11 @@ function Canvas:__blitRect(device, src, tgt)
|
||||
end
|
||||
end
|
||||
if drew then
|
||||
local t = os.clock()
|
||||
repeat until os.clock()-t > .2
|
||||
local c = os.clock()
|
||||
repeat until os.clock()-c > .03
|
||||
end
|
||||
end
|
||||
]]
|
||||
for i = 0, src.ey - src.y do
|
||||
local line = self.lines[src.y + i + (self.offy or 0)]
|
||||
if line and line.dirty then
|
||||
@@ -412,8 +398,13 @@ function Canvas:__blitRect(device, src, tgt)
|
||||
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)
|
||||
if doubleBuffer then
|
||||
Canvas.blit(device, tgt.x, tgt.y + i,
|
||||
t, bg, fg)
|
||||
else
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user