diff --git a/src/elements/BaseFrame.lua b/src/elements/BaseFrame.lua index 52667d5..091525c 100644 --- a/src/elements/BaseFrame.lua +++ b/src/elements/BaseFrame.lua @@ -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 diff --git a/src/elements/Container.lua b/src/elements/Container.lua index bf3c39d..23678af 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -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 diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index 2140852..fb20123 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -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 \ No newline at end of file diff --git a/src/init.lua b/src/init.lua index 791dad7..a530f46 100644 --- a/src/init.lua +++ b/src/init.lua @@ -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 diff --git a/src/main.lua b/src/main.lua index 2909c68..ab0ba6a 100644 --- a/src/main.lua +++ b/src/main.lua @@ -261,6 +261,7 @@ end local function renderFrames() for _, frame in pairs(activeFrames)do frame:render() + frame:postRender() end end diff --git a/src/plugins/canvas.lua b/src/plugins/canvas.lua new file mode 100644 index 0000000..37eae17 --- /dev/null +++ b/src/plugins/canvas.lua @@ -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 +} \ No newline at end of file diff --git a/src/render.lua b/src/render.lua index 95b2da1..503c910 100644 --- a/src/render.lua +++ b/src/render.lua @@ -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