Files
Basalt2/src/plugins/canvas.lua

277 lines
7.7 KiB
Lua

local tHex = require("libraries/colorHex")
local errorManager = require("errorManager")
local Canvas = {}
Canvas.__index = Canvas
---@configDefault false
local sub, rep = string.sub, string.rep
function Canvas.new(element)
local self = setmetatable({}, Canvas)
self.commands = {pre={},post={}}
self.type = "pre"
self.element = element
return self
end
function Canvas:clear()
self.commands = {pre={},post={}}
return self
end
function Canvas:getValue(v)
if type(v) == "function" then
return v(self.element)
end
return v
end
function Canvas:setType(type)
if type == "pre" or type == "post" then
self.type = type
else
errorManager.error("Invalid type. Use 'pre' or 'post'.")
end
return self
end
function Canvas:addCommand(drawFn)
local index = #self.commands[self.type] + 1
self.commands[self.type][index] = drawFn
return index
end
function Canvas:setCommand(index, drawFn)
self.commands[index] = drawFn
return self
end
function Canvas:removeCommand(index)
--self.commands[self.type][index] = nil
table.remove(self.commands[self.type], index)
return self
end
function Canvas:text(x, y, text, fg, bg)
return self:addCommand(function(render)
local _x, _y = self:getValue(x), self:getValue(y)
local _text = self:getValue(text)
local _fg = self:getValue(fg)
local _bg = self:getValue(bg)
local __fg = type(_fg) == "number" and tHex[_fg]:rep(#text) or _fg
local __bg = type(_bg) == "number" and tHex[_bg]:rep(#text) or _bg
render:drawText(_x, _y, _text)
if __fg then render:drawFg(_x, _y, __fg) end
if __bg then render:drawBg(_x, _y, __bg) end
end)
end
function Canvas:bg(x, y, bg)
return self:addCommand(function(render)
render:drawBg(x, y, bg)
end)
end
function Canvas:fg(x, y, fg)
return self:addCommand(function(render)
render:drawFg(x, y, fg)
end)
end
function Canvas:rect(x, y, width, height, char, fg, bg)
return self:addCommand(function(render)
local _x, _y = self:getValue(x), self:getValue(y)
local _width, _height = self:getValue(width), self:getValue(height)
local _char = self:getValue(char)
local _fg = self:getValue(fg)
local _bg = self:getValue(bg)
if(type(_fg) == "number") then _fg = tHex[_fg] end
if(type(_bg) == "number") then _bg = tHex[_bg] end
local bgLine = _bg and sub(_bg:rep(_width), 1, _width)
local fgLine = _fg and sub(_fg:rep(_width), 1, _width)
local textLine = _char and sub(_char:rep(_width), 1, _width)
for i = 0, _height - 1 do
if _bg then render:drawBg(_x, _y + i, bgLine) end
if _fg then render:drawFg(_x, _y + i, fgLine) end
if _char then render:drawText(_x, _y + i, textLine) end
end
end)
end
function Canvas:line(x1, y1, x2, y2, char, fg, bg)
local function linePoints(x1, y1, x2, y2)
local points = {}
local count = 0
local dx = math.abs(x2 - x1)
local dy = math.abs(y2 - y1)
local sx = (x1 < x2) and 1 or -1
local sy = (y1 < y2) and 1 or -1
local err = dx - dy
while true do
count = count + 1
points[count] = {x = x1, y = y1}
if (x1 == x2) and (y1 == y2) then break end
local err2 = err * 2
if err2 > -dy then
err = err - dy
x1 = x1 + sx
end
if err2 < dx then
err = err + dx
y1 = y1 + sy
end
end
return points
end
local needsRecreate = false
local points
if type(x1) == "function" or type(y1) == "function" or type(x2) == "function" or type(y2) == "function" then
needsRecreate = true
else
points = linePoints(self:getValue(x1), self:getValue(y1), self:getValue(x2), self:getValue(y2))
end
return self:addCommand(function(render)
if needsRecreate then
points = linePoints(self:getValue(x1), self:getValue(y1), self:getValue(x2), self:getValue(y2))
end
local _char = self:getValue(char)
local _fg = self:getValue(fg)
local _bg = self:getValue(bg)
local __fg = type(_fg) == "number" and tHex[_fg] or _fg
local __bg = type(_bg) == "number" and tHex[_bg] or _bg
for _, point in ipairs(points) do
local x = math.floor(point.x)
local y = math.floor(point.y)
if _char then render:drawText(x, y, _char) end
if __fg then render:drawFg(x, y, __fg) end
if __bg then render:drawBg(x, y, __bg) end
end
end)
end
function Canvas:ellipse(centerX, centerY, radiusX, radiusY, char, fg, bg)
local function ellipsePoints(x, y, radiusX, radiusY)
local points = {}
local count = 0
local a2 = radiusX * radiusX
local b2 = radiusY * radiusY
local px = 0
local py = radiusY
local p = b2 - a2 * radiusY + 0.25 * a2
local px2 = 0
local py2 = 2 * a2 * py
local function addPoint(px, py)
count = count + 1
points[count] = {x = x + px, y = y + py}
count = count + 1
points[count] = {x = x - px, y = y + py}
count = count + 1
points[count] = {x = x + px, y = y - py}
count = count + 1
points[count] = {x = x - px, y = y - py}
end
addPoint(px, py)
while px2 < py2 do
px = px + 1
px2 = px2 + 2 * b2
if p < 0 then
p = p + b2 + px2
else
py = py - 1
py2 = py2 - 2 * a2
p = p + b2 + px2 - py2
end
addPoint(px, py)
end
p = b2 * (px + 0.5) * (px + 0.5) + a2 * (py - 1) * (py - 1) - a2 * b2
while py > 0 do
py = py - 1
py2 = py2 - 2 * a2
if p > 0 then
p = p + a2 - py2
else
px = px + 1
px2 = px2 + 2 * b2
p = p + a2 - py2 + px2
end
addPoint(px, py)
end
return points
end
local points = ellipsePoints(centerX, centerY, radiusX, radiusY)
return self:addCommand(function(render)
local _char = self:getValue(char)
local _fg = self:getValue(fg)
local _bg = self:getValue(bg)
local __fg = type(_fg) == "number" and tHex[_fg] or _fg
local __bg = type(_bg) == "number" and tHex[_bg] or _bg
for y, line in pairs(points) do
local x = math.floor(line.x)
local y = math.floor(line.y)
if _char then render:drawText(x, y, _char) end
if __fg then render:drawFg(x, y, __fg) end
if __bg then render:drawBg(x, y, __bg) end
end
end)
end
local VisualElement = {hooks={}}
function VisualElement.setup(element)
element.defineProperty(element, "canvas", {
default = nil,
type = "table",
getter = function(self)
if not self._values.canvas then
self._values.canvas = Canvas.new(self)
end
return self._values.canvas
end
})
end
function VisualElement.hooks.render(self)
local canvas = self.get("canvas")
if canvas and #canvas.commands.pre > 0 then
for _, cmd in pairs(canvas.commands.pre) do
cmd(self)
end
end
end
function VisualElement.hooks.postRender(self)
local canvas = self.get("canvas")
if canvas and #canvas.commands.post > 0 then
for _, cmd in pairs(canvas.commands.post) do
cmd(self)
end
end
end
return {
VisualElement = VisualElement,
API = Canvas
}