From da6325e080418ca28170862f9d3391e4c2a3e54c Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Tue, 1 Oct 2024 14:34:56 +0000 Subject: [PATCH] Implement basic graphical elements --- gfx/element.lua | 170 ++++++++++++++++++++++++++++++++++++++++++++++++ gfx/event.lua | 27 ++++++++ gfx/list.lua | 108 ++++++++++++++++++++++++++++++ gfx/padding.lua | 70 ++++++++++++++++++++ gfx/text.lua | 9 +++ 5 files changed, 384 insertions(+) create mode 100644 gfx/element.lua create mode 100644 gfx/event.lua create mode 100644 gfx/list.lua create mode 100644 gfx/padding.lua create mode 100644 gfx/text.lua diff --git a/gfx/element.lua b/gfx/element.lua new file mode 100644 index 0000000..6319fdc --- /dev/null +++ b/gfx/element.lua @@ -0,0 +1,170 @@ +local Event = require("event") + +local Element = { + x = 0, + y = 0, + width = 1, + height = 1, + color = { + bg = colors.black, + fg = colors.white + }, + render = { + window = nil, + parent = nil + }, + visible = true, + dirty = true, + id = "", + onClick = nil +} + +local function createWindow(element) + return window.create(element.render.parent, element:getX(), element:getY(), element:getWidth(), element:getHeight(), element:isVisible()) +end + + +function Element:new(o) + local obj = o or {} + + setmetatable(obj, self) + obj.__index = self + + return obj +end + +function Element:draw() + self.dirty = false +end + +function Element:getX() + return self.x +end + +function Element:getY() + return self.y +end + +function Element:setPos(x, y) + if (x ~= nil and self.x ~= x) or (y ~= nil and self.y ~= y) then + self:setDirty() + self.x = x or self.x + self.y = y or self.y + + self.render.window = createWindow(self) + end +end + +function Element:setX(x) + if self.x ~= x then + self:setDirty() + self.x = x + self.render.window = createWindow(self) + end +end + +function Element:setY(y) + if self.y ~= y then + self:setDirty() + self.y = y + self.render.window = createWindow(self) + end +end + +function Element:setParent(parent) + if self.parent ~= parent then + self:setDirty() + self.parent = parent + end +end + +function Element:setFgColor(color) + if color ~= self.color.fg then + self:setDirty() + self.color.fg = color + self.render.window.setTextColor(color) + end +end + +function Element:setBgColor(color) + if color ~= self.color.bg then + self:setDirty() + self.color.bg = color + self.render.window.setBackgroundColor(color) + end +end + +function Element:setVisible(visible) + self.render.window.setVisible(visible) +end + +function Element:resize(opts) + if (opts.width ~= nil and self.x ~= opts.width) or (opts.height ~= nil and self.y ~= opts.height) then + self:setDirty() + self.width = opts.width or self.width + self.height = opts.height or self.height + self.render.window = createWindow(self) + end +end + +function Element:setWidth(width) + if width ~= self.width then + self:setDirty() + self.width = width + self.render.window = createWindow(self) + end +end + +function Element:setHeight(height) + if height ~= self.height then + self:setDirty() + self.height = height + self.render.window = createWindow(self) + end +end + +function Element:getWidth() + return self.width +end + +function Element:getHeight() + return self.height +end + +function Element:getPos() + return self.x, self.y +end + +function Element:isVisible() + return self.visible +end + +function Element:setDirty() + self.dirty = true +end + +function Element:findById(id) + if self:getId() == id then + return self + else + return nil + end +end + +function Element:getId() + return self.id +end + +function Element:redraw() + self.render.window.redraw() +end + +function Element:handleEvent(evt) + if Event.isClickEvent(evt) and self.onClick ~= nil then + local x, y, source = Event.getClickParams(evt) + return not not self.onClick(self, x, y, source) + end + return false +end + +return Element \ No newline at end of file diff --git a/gfx/event.lua b/gfx/event.lua new file mode 100644 index 0000000..9b01359 --- /dev/null +++ b/gfx/event.lua @@ -0,0 +1,27 @@ +local Event = {} + +function Event.isClickEvent(evt) + return evt[1] == "monitor_touch" or evt[1] == "mouse_click" +end + +function Event.getClickParams(evt) + return evt[3], evt[4], evt[2] +end + +function Event.repositionEvent(evt, dX, dY) + if Event.isClickEvent(evt) then + return evt[1], evt[2], evt[3] + dX, evt[4] + dY + else + return evt + end +end + +function Event.containsClick(element, evt, dX, dY) + local x, y = Event.getClickParams(evt) + x = x + dX + y = y + dY + local eX, eY = element:getPos() + return x >= eX and x < (eX + element:getWidth()) and y >= eY and y < (eY + element:getHeight()) +end + +return Event \ No newline at end of file diff --git a/gfx/list.lua b/gfx/list.lua new file mode 100644 index 0000000..1bf0634 --- /dev/null +++ b/gfx/list.lua @@ -0,0 +1,108 @@ +local Element = require("element") +local List = Element:new{ + children = {}, + vertical = false +} + +local function adjustPositions(elements, vertical, from) + local newDims = 0 + local getDim = vertical and function(e) return e:getHeight() end or function(e) return e:getWidth() end + for i=1,from-1 do + newDims = newDims + getDim(elements[i]) + end + + local setDim = vertical and function(e, dim) e:setPos(0, dim) end or function(e, dim) e:setPos(dim, 0) end + for i=from,#elements do + setDim(elements[i]) + newDims = newDims + getDim(elements[i]) + end +end + +function List:insertChild(child, atIndex) + local index = math.min(math.max(1, atIndex or #self.children), #self.children) + table.insert(self.children, index, child) + adjustPositions(self.children, self:isVertical(), index) +end + +function List:removeChild(child) + local index + local searchType = type(child) + if searchType == "string" then + for i,v in ipairs(self.children) do + if v:getId() == child then + index = i + break + end + end + return false, nil + elseif searchType == "table" then + for i,v in ipairs(self.children) do + if v == child then + index = i + end + end + return false, nil + else + index = child + end + + if index <= 0 or index > #self.children then + return false, nil + end + + local removed = table.remove(self.children, index) + if index <= #self.children then + adjustPositions(self.children, self:isVertical(), index) + end + + return true, removed +end + +function List:isVertical() + return self.vertical +end + +function List:isHorizontal() + return not self:isVertical() +end + +function List:draw() + Element.draw(self) + self.render.window.clear() + for _,v in ipairs(self.children) do + v:draw() + end +end + +function List:getHeight() + local h = 0 + for _,v in ipairs(self.children) do + h = h + v:getHeight() + end + return h +end + +function List:getWidth() + local h = 0 + for _,v in ipairs(self.children) do + h = h + v:getHeight() + end + return h +end + +function List:findById(id) + local find = Element.findById(self, id) + if find then + return find + end + + for _,v in ipairs(self.children) do + local result = v:findById(id) + if result then + return result + end + end + return nil +end + +return List \ No newline at end of file diff --git a/gfx/padding.lua b/gfx/padding.lua new file mode 100644 index 0000000..6cc3c1f --- /dev/null +++ b/gfx/padding.lua @@ -0,0 +1,70 @@ +local Event = require("event") +local Element = require("element") +local Padding = Element:new{ + left = 0, + right = 0, + top = 0, + bottom = 0, + element = nil +} + +function Padding:resize(opts) + -- Un-pad dimensions and pass to child element + return self.element:resize{ + width = (opts.width and opts.width - self:getPaddingLeft() - self:getPaddingRight()) or self:getWidth(), + height = (opts.height and opts.height - self:getPaddingTop() - self:getPaddingBottom()) or self:getHeight() + } +end + +function Padding:getPaddingLeft() + return self.left +end + +function Padding:getPaddingRight() + return self.right +end + +function Padding:getPaddingTop() + return self.top +end + +function Padding:getPaddingBottom() + return self.bottom +end + +function Padding:getInnerWidth() + return self.element:getWidth() +end + +function Padding:getInnerHeight() + return self.element:getHeight() +end + +function Padding:getWidth() + return self:getInnerWidth() + self:getPaddingLeft() + self:getPaddingRight() +end + +function Padding:getHeight() + return self:getInnerHeight() + self:getPaddingTop() + self:getPaddingBottom() +end + +function Padding:findById(id) + return Element.findById(self, id) or self.element:findById(id) +end + +function Padding:handleEvent(evt) + if Element.handleEvent(self, evt) then + return true + end + + if Event.isClickEvent(evt) then + if Event.containsClick(self.element, evt, -self:getPaddingLeft(), -self:getPaddingTop()) then + self.element:handleEvent({Event.repositionEvent(evt, -self:getPaddingLeft(), -self:getPaddingTop())}) + end + else + return self.element:handleEvent(evt) + end + return false +end + +return Padding \ No newline at end of file diff --git a/gfx/text.lua b/gfx/text.lua new file mode 100644 index 0000000..9c5eee7 --- /dev/null +++ b/gfx/text.lua @@ -0,0 +1,9 @@ +local Element = require("element") +local Text = Element:new{ text = "" } + +function Text:draw() + Element.draw(self) + self.render.window.write(self.text) +end + +return Text \ No newline at end of file