Canvas test

This commit is contained in:
Robert Jelic
2025-03-31 03:28:16 +02:00
parent 4f631b47cb
commit d27760adc2
7 changed files with 418 additions and 1 deletions

View File

@@ -109,6 +109,27 @@ function BaseFrame:textBg(x, y, text, bg)
self._render:textBg(x, y, text, bg)
end
--- @shortDescription Renders a text with a background color to the render Object
--- @param x number The x position to render to
--- @param y number The y position to render to
--- @param text string The text to render
--- @param bg colors The background color
--- @protected
function BaseFrame:drawText(x, y, text)
if x < 1 then text = string.sub(text, 1 - x); x = 1 end
self._render:text(x, y, text)
end
function BaseFrame:drawFg(x, y, fg)
if x < 1 then fg = string.sub(fg, 1 - x); x = 1 end
self._render:fg(x, y, fg)
end
function BaseFrame:drawBg(x, y, bg)
if x < 1 then bg = string.sub(bg, 1 - x); x = 1 end
self._render:bg(x, y, bg)
end
--- @shortDescription Renders a text with a foreground and background color to the render Object
--- @param x number The x position to render to
--- @param y number The y position to render to

View File

@@ -1,4 +1,5 @@
local elementManager = require("elementManager")
local errorManager = require("errorManager")
local VisualElement = elementManager.getElement("VisualElement")
local expect = require("libraries/expect")
local split = require("libraries/utils").split
@@ -576,6 +577,46 @@ function Container:textBg(x, y, text, bg)
return self
end
function Container:drawText(x, y, text)
local w, h = self.get("width"), self.get("height")
if y < 1 or y > h then return self end
local textStart = x < 1 and (2 - x) or 1
local textLen = math.min(#text - textStart + 1, w - math.max(1, x) + 1)
if textLen <= 0 then return self end
VisualElement.drawText(self, math.max(1, x), math.max(1, y), text:sub(textStart, textStart + textLen - 1))
return self
end
function Container:drawFg(x, y, fg)
local w, h = self.get("width"), self.get("height")
if y < 1 or y > h then return self end
local textStart = x < 1 and (2 - x) or 1
local textLen = math.min(#fg - textStart + 1, w - math.max(1, x) + 1)
if textLen <= 0 then return self end
VisualElement.drawFg(self, math.max(1, x), math.max(1, y), fg:sub(textStart, textStart + textLen - 1))
return self
end
function Container:drawBg(x, y, bg)
local w, h = self.get("width"), self.get("height")
if y < 1 or y > h then return self end
local textStart = x < 1 and (2 - x) or 1
local textLen = math.min(#bg - textStart + 1, w - math.max(1, x) + 1)
if textLen <= 0 then return self end
VisualElement.drawBg(self, math.max(1, x), math.max(1, y), bg:sub(textStart, textStart + textLen - 1))
return self
end
--- @shortDescription Draws a line of text and fg and bg as colors
--- @param x number The x position to draw the text
--- @param y number The y position to draw the text
@@ -618,13 +659,15 @@ function Container:render()
end
for _, child in ipairs(self.get("visibleChildren")) do
if child == self then
self.basalt.LOGGER.error("CIRCULAR REFERENCE DETECTED!")
errorManager.error("CIRCULAR REFERENCE DETECTED!")
return
end
child:render()
child:postRender()
end
end
--- @private
function Container:destroy()
for _, child in ipairs(self._values.children) do

View File

@@ -168,6 +168,27 @@ function VisualElement:textBg(x, y, text, bg)
self.parent:textBg(x, y, text, bg)
end
function VisualElement:drawText(x, y, text)
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:drawText(x, y, text)
end
function VisualElement:drawFg(x, y, fg)
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:drawFg(x, y, fg)
end
function VisualElement:drawBg(x, y, bg)
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:drawBg(x, y, bg)
end
--- @shortDescription Draws text with both colors
--- @param x number The x position to draw
--- @param y number The y position to draw
@@ -440,4 +461,9 @@ function VisualElement:render()
self:multiBlit(1, 1, width, height, " ", tHex[self.get("foreground")], tHex[self.get("background")])
end
--- @shortDescription Post-rendering function for the element
--- @protected
function VisualElement:postRender()
end
return VisualElement

View File

@@ -8,7 +8,9 @@ local main = format:gsub("path", basaltPath)
package.path = main.."rom/?"
local function errorHandler(err)
package.path = main.."rom/?"
local errorManager = require("errorManager")
package.path = defaultPath
errorManager.header = "Basalt Loading Error"
errorManager.error(err)
end

View File

@@ -261,6 +261,7 @@ end
local function renderFrames()
for _, frame in pairs(activeFrames)do
frame:render()
frame:postRender()
end
end

282
src/plugins/canvas.lua Normal file
View File

@@ -0,0 +1,282 @@
local tHex = require("libraries/colorHex")
local errorManager = require("errorManager")
local Canvas = {}
Canvas.__index = Canvas
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 = {}
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)
if index > 0 and index <= #self.commands then
self.commands[index] = drawFn
end
return self
end
function Canvas:removeCommand(index)
self.commands[index] = nil
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] or _fg
local __bg = type(_bg) == "number" and tHex[_bg] 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)
if _bg and _fg and _char then
render:multiBlit(_x, _y, _width, _height, textLine, fgLine, bgLine)
return
end
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 ipairs(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 ipairs(canvas.commands.post) do
cmd(self)
end
end
end
return {
VisualElement = VisualElement,
API = Canvas
}

View File

@@ -142,6 +142,48 @@ function Render:textBg(x, y, text, bg)
return self
end
--- Renders the text to the screen
--- @param x number The x position to blit to
--- @param y number The y position to blit to
--- @param text string The text to blit
--- @return Render
function Render:text(x, y, text)
if y < 1 or y > self.height then return self end
self.buffer.text[y] = sub(self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text), 1, self.width)
self:addDirtyRect(x, y, #text, 1)
return self
end
--- Blits a foreground color to the screen
--- @param x number The x position
--- @param y number The y position
--- @param fg string The foreground color to blit
--- @return Render
function Render:fg(x, y, fg)
if y < 1 or y > self.height then return self end
self.buffer.fg[y] = sub(self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg), 1, self.width)
self:addDirtyRect(x, y, #fg, 1)
return self
end
--- Blits a background color to the screen
--- @param x number The x position
--- @param y number The y position
--- @param bg string The background color to blit
--- @return Render
function Render:bg(x, y, bg)
if y < 1 or y > self.height then return self end
self.buffer.bg[y] = sub(self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg), 1, self.width)
self:addDirtyRect(x, y, #bg, 1)
return self
end
--- Blits text to the screen
--- @param x number The x position to blit to
--- @param y number The y position to blit to