Add core library and element classes for Basalt framework
This commit is contained in:
91
src/elements/BaseElement.lua
Normal file
91
src/elements/BaseElement.lua
Normal file
@@ -0,0 +1,91 @@
|
||||
local PropertySystem = require("propertySystem") -- muss geändert werden.
|
||||
|
||||
local BaseElement = setmetatable({}, PropertySystem)
|
||||
BaseElement.__index = BaseElement
|
||||
BaseElement._events = {}
|
||||
|
||||
BaseElement.defineProperty(BaseElement, "type", {default = "BaseElement", type = "string"})
|
||||
BaseElement.defineProperty(BaseElement, "eventCallbacks", {default = {}, type = "table"})
|
||||
|
||||
function BaseElement.new(id, basalt)
|
||||
local self = setmetatable({}, BaseElement):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "BaseElement")
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:init(id, basalt)
|
||||
self.id = id
|
||||
self.basalt = basalt
|
||||
self._registeredEvents = {}
|
||||
if BaseElement._events then
|
||||
for event in pairs(BaseElement._events) do
|
||||
self._registeredEvents[event] = true
|
||||
local handlerName = "on" .. event:gsub("_(%l)", function(c)
|
||||
return c:upper()
|
||||
end):gsub("^%l", string.upper)
|
||||
self[handlerName] = function(self, ...)
|
||||
self:registerCallback(event, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement.listenTo(class, eventName)
|
||||
if not class._events then
|
||||
class._events = {}
|
||||
end
|
||||
class._events[eventName] = true
|
||||
end
|
||||
|
||||
function BaseElement:listenEvent(eventName, enable)
|
||||
enable = enable ~= false
|
||||
if enable ~= (self._registeredEvents[eventName] or false) then
|
||||
if enable then
|
||||
self._registeredEvents[eventName] = true
|
||||
if self.parent then
|
||||
self.parent:registerChildEvent(self, eventName)
|
||||
end
|
||||
else
|
||||
self._registeredEvents[eventName] = nil
|
||||
if self.parent then
|
||||
self.parent:unregisterChildEvent(self, eventName)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:registerCallback(event, callback)
|
||||
if not self._registeredEvents[event] then
|
||||
self:listenEvent(event, true)
|
||||
end
|
||||
|
||||
if not self._values.eventCallbacks[event] then
|
||||
self._values.eventCallbacks[event] = {}
|
||||
end
|
||||
|
||||
table.insert(self._values.eventCallbacks[event], callback)
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:fireEvent(event, ...)
|
||||
if self._values.eventCallbacks[event] then
|
||||
for _, callback in ipairs(self._values.eventCallbacks[event]) do
|
||||
local result = callback(self, ...)
|
||||
return result
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:updateRender()
|
||||
if(self.parent) then
|
||||
self.parent:updateRender()
|
||||
else
|
||||
self._renderUpdate = true
|
||||
end
|
||||
end
|
||||
|
||||
return BaseElement
|
||||
39
src/elements/BaseFrame.lua
Normal file
39
src/elements/BaseFrame.lua
Normal file
@@ -0,0 +1,39 @@
|
||||
local Container = require("elements/Container")
|
||||
local Render = require("render")
|
||||
|
||||
local BaseFrame = setmetatable({}, Container)
|
||||
BaseFrame.__index = BaseFrame
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function BaseFrame.new(id, basalt)
|
||||
local self = setmetatable({}, BaseFrame):__init()
|
||||
self:init(id, basalt)
|
||||
self.terminal = term.current() -- change to :setTerm later!!
|
||||
self._render = Render.new(self.terminal)
|
||||
self._renderUpdate = true
|
||||
local width, height = self.terminal.getSize()
|
||||
self.set("width", width)
|
||||
self.set("height", height)
|
||||
self.set("background", colors.red)
|
||||
self.set("type", "BaseFrame")
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseFrame:multiBlit(x, y, width, height, text, fg, bg)
|
||||
self._render:multiBlit(x, y, width, height, text, fg, bg)
|
||||
end
|
||||
|
||||
function BaseFrame:textFg(x, y, text, fg)
|
||||
self._render:textFg(x, y, text, fg)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function BaseFrame:render()
|
||||
if(self._renderUpdate) then
|
||||
Container.render(self)
|
||||
self._render:render()
|
||||
self._renderUpdate = false
|
||||
end
|
||||
end
|
||||
|
||||
return BaseFrame
|
||||
29
src/elements/Button.lua
Normal file
29
src/elements/Button.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
local VisualElement = require("elements/VisualElement")
|
||||
local getCenteredPosition = require("libraries/utils").getCenteredPosition
|
||||
|
||||
local Button = setmetatable({}, VisualElement)
|
||||
Button.__index = Button
|
||||
|
||||
Button.defineProperty(Button, "text", {default = "Button", type = "string"})
|
||||
Button.listenTo(Button, "mouse_click")
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Button.new(id, basalt)
|
||||
local self = setmetatable({}, Button):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "Button")
|
||||
self.set("width", 10)
|
||||
self.set("height", 3)
|
||||
self.set("z", 5)
|
||||
return self
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Button:render()
|
||||
VisualElement.render(self)
|
||||
local text = self.get("text")
|
||||
local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height"))
|
||||
self:textFg(xO, yO, text, self.get("foreground"))
|
||||
end
|
||||
|
||||
return Button
|
||||
208
src/elements/Container.lua
Normal file
208
src/elements/Container.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
local VisualElement = require("elements/VisualElement")
|
||||
local elementManager = require("elementManager")
|
||||
local expect = require("libraries/expect")
|
||||
|
||||
local max = math.max
|
||||
|
||||
local Container = setmetatable({}, VisualElement)
|
||||
Container.__index = Container
|
||||
|
||||
Container.defineProperty(Container, "children", {default = {}, type = "table"})
|
||||
Container.defineProperty(Container, "childrenEvents", {default = {}, type = "table"})
|
||||
Container.defineProperty(Container, "eventListenerCount", {default = {}, type = "table"})
|
||||
|
||||
for k, _ in pairs(elementManager:getElementList()) do
|
||||
local capitalizedName = k:sub(1,1):upper() .. k:sub(2)
|
||||
--if not capitalizedName == "BaseFrame" then
|
||||
Container["add"..capitalizedName] = function(self, ...)
|
||||
expect(1, self, "table")
|
||||
local element = self.basalt.create(k, ...)
|
||||
self.basalt.LOGGER.debug(capitalizedName.." created with ID: " .. element.id)
|
||||
self:addChild(element)
|
||||
return element
|
||||
end
|
||||
--end
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Container.new(id, basalt)
|
||||
local self = setmetatable({}, Container):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "Container")
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:addChild(child)
|
||||
if child == self then
|
||||
error("Cannot add container to itself")
|
||||
end
|
||||
|
||||
local childZ = child.get("z")
|
||||
local pos = 1
|
||||
for i, existing in ipairs(self._values.children) do
|
||||
if existing.get("z") > childZ then
|
||||
break
|
||||
end
|
||||
pos = i + 1
|
||||
end
|
||||
|
||||
table.insert(self._values.children, pos, child)
|
||||
child.parent = self
|
||||
self:registerChildrenEvents(child)
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:sortChildren()
|
||||
table.sort(self._values.children, function(a, b)
|
||||
return a.get("z") < b.get("z")
|
||||
end)
|
||||
end
|
||||
|
||||
function Container:sortChildrenEvents(eventName)
|
||||
if self._values.childrenEvents[eventName] then
|
||||
table.sort(self._values.childrenEvents[eventName], function(a, b)
|
||||
return a.get("z") > b.get("z")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function Container:registerChildrenEvents(child)
|
||||
for event in pairs(child._registeredEvents) do
|
||||
self:registerChildEvent(child, event)
|
||||
end
|
||||
end
|
||||
|
||||
function Container:registerChildEvent(child, eventName)
|
||||
if not self._values.childrenEvents[eventName] then
|
||||
self._values.childrenEvents[eventName] = {}
|
||||
self._values.eventListenerCount[eventName] = 0
|
||||
|
||||
if self.parent then
|
||||
self.parent:registerChildEvent(self, eventName)
|
||||
end
|
||||
end
|
||||
|
||||
for _, registeredChild in ipairs(self._values.childrenEvents[eventName]) do
|
||||
if registeredChild == child then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local childZ = child.get("z")
|
||||
local pos = 1
|
||||
for i, existing in ipairs(self._values.childrenEvents[eventName]) do
|
||||
if existing.get("z") < childZ then
|
||||
break
|
||||
end
|
||||
pos = i + 1
|
||||
end
|
||||
|
||||
table.insert(self._values.childrenEvents[eventName], pos, child)
|
||||
self._values.eventListenerCount[eventName] = self._values.eventListenerCount[eventName] + 1
|
||||
end
|
||||
|
||||
function Container:removeChildrenEvents(child)
|
||||
for event in pairs(child._registeredEvents) do
|
||||
self:unregisterChildEvent(child, event)
|
||||
end
|
||||
end
|
||||
|
||||
function Container:unregisterChildEvent(child, eventName)
|
||||
if self._values.childrenEvents[eventName] then
|
||||
for i, listener in ipairs(self._values.childrenEvents[eventName]) do
|
||||
if listener == child then
|
||||
table.remove(self._values.childrenEvents[eventName], i)
|
||||
self._values.eventListenerCount[eventName] = self._values.eventListenerCount[eventName] - 1
|
||||
|
||||
if self._values.eventListenerCount[eventName] <= 0 then
|
||||
self._values.childrenEvents[eventName] = nil
|
||||
self._values.eventListenerCount[eventName] = nil
|
||||
|
||||
if self.parent then
|
||||
self.parent:unregisterChildEvent(self, eventName)
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Container:removeChild(child)
|
||||
for i,v in ipairs(self.children) do
|
||||
if v == child then
|
||||
table.remove(self._values.children, i)
|
||||
child.parent = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:handleEvent(event, ...)
|
||||
if(VisualElement.handleEvent(self, event, ...))then
|
||||
local args = {...}
|
||||
if event:find("mouse_") then
|
||||
local button, absX, absY = ...
|
||||
local relX, relY = self:getRelativePosition(absX, absY)
|
||||
args = {button, relX, relY}
|
||||
end
|
||||
if self._values.childrenEvents[event] then
|
||||
for _, child in ipairs(self._values.childrenEvents[event]) do
|
||||
if(child:handleEvent(event, table.unpack(args)))then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[function Container:mouse_click(button, x, y)
|
||||
if VisualElement.mouse_click(self, button, x, y) then
|
||||
if self._values.childrenEvents.mouse_click then
|
||||
for _, child in ipairs(self._values.childrenEvents.mouse_click) do
|
||||
if child:mouse_click(button, x, y) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end]]
|
||||
|
||||
function Container:multiBlit(x, y, width, height, text, fg, bg)
|
||||
local w, h = self.get("width"), self.get("height")
|
||||
|
||||
width = x < 1 and math.min(width + x - 1, w) or math.min(width, math.max(0, w - x + 1))
|
||||
height = y < 1 and math.min(height + y - 1, h) or math.min(height, math.max(0, h - y + 1))
|
||||
|
||||
if width <= 0 or height <= 0 then return end
|
||||
|
||||
VisualElement.multiBlit(self, math.max(1, x), math.max(1, y), width, height, text, fg, bg)
|
||||
end
|
||||
|
||||
function Container:textFg(x, y, text, fg)
|
||||
local w, h = self.get("width"), self.get("height")
|
||||
|
||||
if y < 1 or y > h then return 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 end
|
||||
|
||||
VisualElement.textFg(self, math.max(1, x), math.max(1, y), text:sub(textStart, textStart + textLen - 1), fg)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Container:render()
|
||||
VisualElement.render(self)
|
||||
for _, child in ipairs(self._values.children) do
|
||||
if child == self then
|
||||
self.basalt.LOGGER.error("CIRCULAR REFERENCE DETECTED!")
|
||||
return
|
||||
end
|
||||
child:render()
|
||||
end
|
||||
end
|
||||
|
||||
return Container
|
||||
18
src/elements/Frame.lua
Normal file
18
src/elements/Frame.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
local Container = require("elements/Container")
|
||||
|
||||
local Frame = setmetatable({}, Container)
|
||||
Frame.__index = Frame
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Frame.new(id, basalt)
|
||||
local self = setmetatable({}, Frame):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("width", 12)
|
||||
self.set("height", 6)
|
||||
self.set("background", colors.blue)
|
||||
self.set("type", "Frame")
|
||||
self.set("z", 10)
|
||||
return self
|
||||
end
|
||||
|
||||
return Frame
|
||||
123
src/elements/VisualElement.lua
Normal file
123
src/elements/VisualElement.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
local BaseElement = require("elements/BaseElement")
|
||||
local VisualElement = setmetatable({}, BaseElement)
|
||||
VisualElement.__index = VisualElement
|
||||
local tHex = require("libraries/colorHex")
|
||||
|
||||
BaseElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true})
|
||||
BaseElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true})
|
||||
BaseElement.defineProperty(VisualElement, "z", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
|
||||
self.basalt.LOGGER.debug("Setting z to " .. value)
|
||||
if self.parent then
|
||||
self.parent:sortChildren()
|
||||
end
|
||||
return value
|
||||
end})
|
||||
BaseElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true})
|
||||
BaseElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true})
|
||||
BaseElement.defineProperty(VisualElement, "background", {default = colors.black, type = "number", canTriggerRender = true})
|
||||
BaseElement.defineProperty(VisualElement, "foreground", {default = colors.white, type = "number", canTriggerRender = true})
|
||||
BaseElement.defineProperty(VisualElement, "clicked", {default = false, type = "boolean"})
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function VisualElement.new(id, basalt)
|
||||
local self = setmetatable({}, VisualElement):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "VisualElement")
|
||||
return self
|
||||
end
|
||||
|
||||
function VisualElement:multiBlit(x, y, width, height, text, fg, bg)
|
||||
x = x + self.get("x") - 1
|
||||
y = y + self.get("y") - 1
|
||||
self.parent:multiBlit(x, y, width, height, text, fg, bg)
|
||||
end
|
||||
|
||||
function VisualElement:textFg(x, y, text, fg)
|
||||
x = x + self.get("x") - 1
|
||||
y = y + self.get("y") - 1
|
||||
self.parent:textFg(x, y, text, fg)
|
||||
end
|
||||
|
||||
function VisualElement:isInBounds(x, y)
|
||||
local xPos, yPos = self.get("x"), self.get("y")
|
||||
local width, height = self.get("width"), self.get("height")
|
||||
|
||||
return x >= xPos and x <= xPos + width - 1 and
|
||||
y >= yPos and y <= yPos + height - 1
|
||||
end
|
||||
|
||||
function VisualElement:mouse_click(button, x, y)
|
||||
if self:isInBounds(x, y) then
|
||||
self.set("clicked", true)
|
||||
self:fireEvent("mouse_click", button, x, y)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function VisualElement:mouse_up(button, x, y)
|
||||
if self:isInBounds(x, y) then
|
||||
self.set("clicked", false)
|
||||
self:fireEvent("mouse_up", button, x, y)
|
||||
return true
|
||||
end
|
||||
self:fireEvent("mouse_release", button, x, y)
|
||||
end
|
||||
|
||||
function VisualElement:mouse_release()
|
||||
self.set("clicked", false)
|
||||
end
|
||||
|
||||
function VisualElement:handleEvent(event, ...)
|
||||
if(self[event])then
|
||||
return self[event](self, ...)
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the absolute position of the element or the given coordinates.
|
||||
---@param x? number -- x position
|
||||
---@param y? number -- y position
|
||||
function VisualElement:getAbsolutePosition(x, y)
|
||||
if (x == nil) or (y == nil) then
|
||||
x, y = self.get("x"), self.get("y")
|
||||
end
|
||||
|
||||
local parent = self.parent
|
||||
while parent do
|
||||
local px, py = parent.get("x"), parent.get("y")
|
||||
x = x + px - 1
|
||||
y = y + py - 1
|
||||
parent = parent.parent
|
||||
end
|
||||
|
||||
return x, y
|
||||
end
|
||||
|
||||
--- Returns the relative position of the element or the given coordinates.
|
||||
---@param x? number -- x position
|
||||
---@param y? number -- y position
|
||||
---@return number, number
|
||||
function VisualElement:getRelativePosition(x, y)
|
||||
if (x == nil) or (y == nil) then
|
||||
x, y = self.get("x"), self.get("y")
|
||||
end
|
||||
|
||||
local parentX, parentY = 1, 1
|
||||
if self.parent then
|
||||
parentX, parentY = self.parent:getRelativePosition()
|
||||
end
|
||||
|
||||
local elementX = self.get("x")
|
||||
local elementY = self.get("y")
|
||||
|
||||
return x - (elementX - 1) - (parentX - 1),
|
||||
y - (elementY - 1) - (parentY - 1)
|
||||
end
|
||||
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function VisualElement:render()
|
||||
local width, height = self.get("width"), self.get("height")
|
||||
self:multiBlit(1, 1, width, height, " ", tHex[self.get("foreground")], tHex[self.get("background")])
|
||||
end
|
||||
|
||||
return VisualElement
|
||||
Reference in New Issue
Block a user