- Created Plugin loading system
- Added lazy loading system for elements (optional feature) - Improved rendering performance - Added ID system which is separated from Eement Names - Added Focussystem for container - Improved container performance by only rendering and handling events from visible childrens instead of all - Added label and input - Added animation and xml
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
local PropertySystem = require("propertySystem")
|
||||
local uuid = require("/libraries/utils").uuid
|
||||
|
||||
--- The base class for all UI elements in Basalt
|
||||
--- @class BaseElement : PropertySystem
|
||||
@@ -7,45 +8,28 @@ BaseElement.__index = BaseElement
|
||||
BaseElement._events = {}
|
||||
|
||||
--- @property type string BaseElement The type identifier of the element
|
||||
BaseElement.defineProperty(BaseElement, "type", {default = "BaseElement", type = "string"})
|
||||
BaseElement.defineProperty(BaseElement, "type", {default = {"BaseElement"}, type = "string", setter=function(self, value)
|
||||
if type(value) == "string" then
|
||||
table.insert(self._values.type, 1, value)
|
||||
return self._values.type
|
||||
end
|
||||
return value
|
||||
end, getter = function(self, _, index)
|
||||
if index~= nil and index < 1 then
|
||||
return self._values.type
|
||||
end
|
||||
return self._values.type[index or 1]
|
||||
end})
|
||||
|
||||
--- @property id string BaseElement The unique identifier for the element
|
||||
BaseElement.defineProperty(BaseElement, "id", {default = "", type = "string", readonly = true})
|
||||
|
||||
--- @property name string BaseElement The name of the element
|
||||
BaseElement.defineProperty(BaseElement, "name", {default = "", type = "string"})
|
||||
|
||||
--- @property eventCallbacks table {} Table containing all registered event callbacks
|
||||
BaseElement.defineProperty(BaseElement, "eventCallbacks", {default = {}, type = "table"})
|
||||
|
||||
--- Creates a new BaseElement instance
|
||||
--- @param id string The unique identifier for this element
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return table The newly created BaseElement instance
|
||||
--- @usage local element = BaseElement.new("myId", basalt)
|
||||
function BaseElement.new(id, basalt)
|
||||
local self = setmetatable({}, BaseElement):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "BaseElement")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initializes the BaseElement instance
|
||||
--- @param id string The unique identifier for this element
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return table self The initialized instance
|
||||
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
|
||||
|
||||
--- Registers an event that this class can listen to
|
||||
--- @param class table The class to add the event to
|
||||
--- @param eventName string The name of the event to register
|
||||
@@ -57,6 +41,57 @@ function BaseElement.listenTo(class, eventName)
|
||||
class._events[eventName] = true
|
||||
end
|
||||
|
||||
--- Creates a new BaseElement instance
|
||||
--- @param props table The properties to initialize the element with
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return table The newly created BaseElement instance
|
||||
--- @usage local element = BaseElement.new("myId", basalt)
|
||||
function BaseElement.new(props, basalt)
|
||||
local self = setmetatable({}, BaseElement):__init()
|
||||
self:init(props, basalt)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initializes the BaseElement instance
|
||||
--- @param props table The properties to initialize the element with
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return table self The initialized instance
|
||||
function BaseElement:init(props, basalt)
|
||||
if(type(props) == "table")then
|
||||
for k,v in pairs(props)do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
self._values.id = uuid()
|
||||
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, ...)
|
||||
return self
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Checks if the element is a specific type
|
||||
--- @param type string The type to check for
|
||||
--- @return boolean Whether the element is of the specified type
|
||||
function BaseElement:isType(type)
|
||||
for _, t in ipairs(self._values.type) do
|
||||
if t == type then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Enables or disables event listening for a specific event
|
||||
--- @param eventName string The name of the event to listen for
|
||||
--- @param enable? boolean Whether to enable or disable the event (default: true)
|
||||
@@ -113,6 +148,25 @@ function BaseElement:fireEvent(event, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Handles all events
|
||||
--- @param event string The event to handle
|
||||
--- @vararg any The arguments for the event
|
||||
--- @return boolean? handled Whether the event was handled
|
||||
function BaseElement:dispatchEvent(event, ...)
|
||||
if self[event] then
|
||||
return self[event](self, ...)
|
||||
end
|
||||
return self:handleEvent(event, ...)
|
||||
end
|
||||
|
||||
--- The default event handler for all events
|
||||
--- @param event string The event to handle
|
||||
--- @vararg any The arguments for the event
|
||||
--- @return boolean? handled Whether the event was handled
|
||||
function BaseElement:handleEvent(event, ...)
|
||||
return true
|
||||
end
|
||||
|
||||
--- Requests a render update for this element
|
||||
--- @usage element:updateRender()
|
||||
function BaseElement:updateRender()
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
local Container = require("elements/Container")
|
||||
local elementManager = require("elementManager")
|
||||
local Container = elementManager.getElement("Container")
|
||||
local Render = require("render")
|
||||
|
||||
---@class BaseFrame : Container
|
||||
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)
|
||||
---@property text term term nil text
|
||||
BaseFrame.defineProperty(BaseFrame, "term", {default = nil, type = "table", setter = function(self, value)
|
||||
if value == nil or value.setCursorPos == nil then
|
||||
return value
|
||||
end
|
||||
self._render = Render.new(value)
|
||||
self._renderUpdate = true
|
||||
local width, height = self.terminal.getSize()
|
||||
local width, height = value.getSize()
|
||||
self.set("width", width)
|
||||
self.set("height", height)
|
||||
return value
|
||||
end})
|
||||
|
||||
function BaseFrame.new(props, basalt)
|
||||
local self = setmetatable({}, BaseFrame):__init()
|
||||
self:init(props, basalt)
|
||||
self.set("term", term.current())
|
||||
self.set("background", colors.red)
|
||||
self.set("type", "BaseFrame")
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseFrame:init(props, basalt)
|
||||
Container.init(self, props, basalt)
|
||||
self.set("type", "BaseFrame")
|
||||
end
|
||||
|
||||
function BaseFrame:multiBlit(x, y, width, height, text, fg, bg)
|
||||
self._render:multiBlit(x, y, width, height, text, fg, bg)
|
||||
end
|
||||
@@ -28,12 +40,18 @@ function BaseFrame:textFg(x, y, text, fg)
|
||||
self._render:textFg(x, y, text, fg)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function BaseFrame:setCursor(x, y, blink)
|
||||
local term = self.get("term")
|
||||
self._render:setCursor(x, y, blink)
|
||||
end
|
||||
|
||||
function BaseFrame:render()
|
||||
if(self._renderUpdate) then
|
||||
Container.render(self)
|
||||
self._render:render()
|
||||
self._renderUpdate = false
|
||||
if self._render ~= nil then
|
||||
Container.render(self)
|
||||
self._render:render()
|
||||
self._renderUpdate = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local VisualElement = require("elements/VisualElement")
|
||||
local elementManager = require("elementManager")
|
||||
local VisualElement = elementManager.getElement("VisualElement")
|
||||
local getCenteredPosition = require("libraries/utils").getCenteredPosition
|
||||
|
||||
---@class Button : VisualElement
|
||||
@@ -6,23 +7,25 @@ local Button = setmetatable({}, VisualElement)
|
||||
Button.__index = Button
|
||||
|
||||
---@property text string Button Button text
|
||||
Button.defineProperty(Button, "text", {default = "Button", type = "string"})
|
||||
Button.defineProperty(Button, "text", {default = "Button", type = "string", canTriggerRender = true})
|
||||
|
||||
---@event mouse_click The event that is triggered when the button is clicked
|
||||
Button.listenTo(Button, "mouse_click")
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Button.new(id, basalt)
|
||||
function Button.new(props, basalt)
|
||||
local self = setmetatable({}, Button):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "Button")
|
||||
self:init(props, basalt)
|
||||
self.set("width", 10)
|
||||
self.set("height", 3)
|
||||
self.set("z", 5)
|
||||
return self
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Button:init(props, basalt)
|
||||
VisualElement.init(self, props, basalt)
|
||||
self.set("type", "Button")
|
||||
end
|
||||
|
||||
function Button:render()
|
||||
VisualElement.render(self)
|
||||
local text = self.get("text")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local VisualElement = require("elements/VisualElement")
|
||||
local elementManager = require("elementManager")
|
||||
local VisualElement = elementManager.getElement("VisualElement")
|
||||
local expect = require("libraries/expect")
|
||||
local split = require("libraries/utils").split
|
||||
|
||||
local max = math.max
|
||||
|
||||
@@ -9,65 +10,129 @@ local Container = setmetatable({}, VisualElement)
|
||||
Container.__index = Container
|
||||
|
||||
Container.defineProperty(Container, "children", {default = {}, type = "table"})
|
||||
Container.defineProperty(Container, "childrenSorted", {default = true, type = "boolean"})
|
||||
Container.defineProperty(Container, "childrenEventsSorted", {default = true, type = "boolean"})
|
||||
Container.defineProperty(Container, "childrenEvents", {default = {}, type = "table"})
|
||||
Container.defineProperty(Container, "eventListenerCount", {default = {}, type = "table"})
|
||||
Container.defineProperty(Container, "focusedChild", {default = nil, type = "table", setter = function(self, value, internal)
|
||||
local oldChild = self._values.focusedChild
|
||||
|
||||
if value == oldChild then return value end
|
||||
|
||||
if oldChild then
|
||||
if oldChild:isType("Container") then
|
||||
oldChild.set("focusedChild", nil, true)
|
||||
end
|
||||
oldChild.set("focused", false, true)
|
||||
end
|
||||
|
||||
if value and not internal then
|
||||
value.set("focused", true, true)
|
||||
if self.parent then
|
||||
self.parent:setFocusedChild(self)
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end})
|
||||
|
||||
Container.defineProperty(Container, "visibleChildren", {default = {}, type = "table"})
|
||||
Container.defineProperty(Container, "visibleChildrenEvents", {default = {}, type = "table"})
|
||||
|
||||
function Container:isChildVisible(child)
|
||||
local childX, childY = child.get("x"), child.get("y")
|
||||
local childW, childH = child.get("width"), child.get("height")
|
||||
local containerW, containerH = self.get("width"), self.get("height")
|
||||
|
||||
return childX <= containerW and
|
||||
childY <= containerH and
|
||||
childX + childW > 0 and
|
||||
childY + childH > 0
|
||||
end
|
||||
|
||||
for k, _ in pairs(elementManager:getElementList()) do
|
||||
local capitalizedName = k:sub(1,1):upper() .. k:sub(2)
|
||||
--if not capitalizedName == "BaseFrame" then
|
||||
if 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
|
||||
Container["addDelayed"..capitalizedName] = function(self, prop)
|
||||
expect(1, self, "table")
|
||||
local element = self.basalt.create(k, prop, true, self)
|
||||
return element
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Container.new(id, basalt)
|
||||
function Container.new(props, basalt)
|
||||
local self = setmetatable({}, Container):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("type", "Container")
|
||||
self:init(props, basalt)
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:init(props, basalt)
|
||||
VisualElement.init(self, props, basalt)
|
||||
self.set("type", "Container")
|
||||
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)
|
||||
table.insert(self._values.children, child)
|
||||
child.parent = self
|
||||
self.set("childrenSorted", false)
|
||||
self:registerChildrenEvents(child)
|
||||
return self
|
||||
end
|
||||
|
||||
local function sortAndFilterChildren(self, children)
|
||||
local visibleChildren = {}
|
||||
|
||||
for _, child in ipairs(children) do
|
||||
if self:isChildVisible(child) then
|
||||
table.insert(visibleChildren, child)
|
||||
end
|
||||
end
|
||||
|
||||
for i = 2, #visibleChildren do
|
||||
local current = visibleChildren[i]
|
||||
local currentZ = current.get("z")
|
||||
local j = i - 1
|
||||
|
||||
while j > 0 do
|
||||
local compare = visibleChildren[j].get("z")
|
||||
if compare > currentZ then
|
||||
visibleChildren[j + 1] = visibleChildren[j]
|
||||
j = j - 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
visibleChildren[j + 1] = current
|
||||
end
|
||||
|
||||
return visibleChildren
|
||||
end
|
||||
|
||||
function Container:sortChildren()
|
||||
table.sort(self._values.children, function(a, b)
|
||||
return a.get("z") < b.get("z")
|
||||
end)
|
||||
self.set("visibleChildren", sortAndFilterChildren(self, self._values.children))
|
||||
self.set("childrenSorted", true)
|
||||
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)
|
||||
self._values.visibleChildrenEvents[eventName] = sortAndFilterChildren(self, self._values.childrenEvents[eventName])
|
||||
end
|
||||
self.set("childrenEventsSorted", true)
|
||||
end
|
||||
|
||||
function Container:registerChildrenEvents(child)
|
||||
if(child._registeredEvents == nil)then return end
|
||||
for event in pairs(child._registeredEvents) do
|
||||
self:registerChildEvent(child, event)
|
||||
end
|
||||
@@ -89,20 +154,13 @@ function Container:registerChildEvent(child, eventName)
|
||||
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.set("childrenEventsSorted", false)
|
||||
table.insert(self._values.childrenEvents[eventName], child)
|
||||
self._values.eventListenerCount[eventName] = self._values.eventListenerCount[eventName] + 1
|
||||
end
|
||||
|
||||
function Container:removeChildrenEvents(child)
|
||||
if(child._registeredEvents == nil)then return end
|
||||
for event in pairs(child._registeredEvents) do
|
||||
self:unregisterChildEvent(child, event)
|
||||
end
|
||||
@@ -130,45 +188,98 @@ function Container:unregisterChildEvent(child, eventName)
|
||||
end
|
||||
|
||||
function Container:removeChild(child)
|
||||
for i,v in ipairs(self.children) do
|
||||
for i,v in ipairs(self._values.children) do
|
||||
if v == child then
|
||||
table.remove(self._values.children, i)
|
||||
child.parent = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
self:removeChildrenEvents(child)
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:getChild(path)
|
||||
if type(path) == "string" then
|
||||
local parts = split(path, "/")
|
||||
for _,v in pairs(self._values.children) do
|
||||
if v.get("name") == parts[1] then
|
||||
if #parts == 1 then
|
||||
return v
|
||||
else
|
||||
if(v:isType("Container"))then
|
||||
return v:find(table.concat(parts, "/", 2))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function convertMousePosition(self, event, ...)
|
||||
local args = {...}
|
||||
if event:find("mouse_") then
|
||||
local button, absX, absY = ...
|
||||
local relX, relY = self:getRelativePosition(absX, absY)
|
||||
args = {button, relX, relY}
|
||||
end
|
||||
return args
|
||||
end
|
||||
|
||||
local function callChildrenEvents(self, visibleOnly, event, ...)
|
||||
local children = visibleOnly and self.get("visibleChildrenEvents") or self.get("childrenEvents")
|
||||
if children[event] then
|
||||
local events = children[event]
|
||||
for i = #events, 1, -1 do
|
||||
local child = events[i]
|
||||
if(child:dispatchEvent(event, ...))then
|
||||
return true, child
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
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
|
||||
local args = convertMousePosition(self, event, ...)
|
||||
return callChildrenEvents(self, false, event, table.unpack(args))
|
||||
end
|
||||
end
|
||||
|
||||
--[[function Container:mouse_click(button, x, y)
|
||||
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
|
||||
local args = convertMousePosition(self, "mouse_click", button, x, y)
|
||||
local success, child = callChildrenEvents(self, true, "mouse_click", table.unpack(args))
|
||||
if(success)then
|
||||
self.set("focusedChild", child)
|
||||
return true
|
||||
end
|
||||
self.set("focusedChild", nil)
|
||||
end
|
||||
end]]
|
||||
end
|
||||
|
||||
function Container:key(key)
|
||||
if self.get("focusedChild") then
|
||||
return self.get("focusedChild"):dispatchEvent("key", key)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Container:char(char)
|
||||
if self.get("focusedChild") then
|
||||
return self.get("focusedChild"):dispatchEvent("char", char)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Container:key_up(key)
|
||||
if self.get("focusedChild") then
|
||||
return self.get("focusedChild"):dispatchEvent("key_up", key)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Container:multiBlit(x, y, width, height, text, fg, bg)
|
||||
local w, h = self.get("width"), self.get("height")
|
||||
@@ -194,10 +305,17 @@ function Container:textFg(x, y, text, fg)
|
||||
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 not self.get("childrenSorted")then
|
||||
self:sortChildren()
|
||||
end
|
||||
if not self.get("childrenEventsSorted")then
|
||||
for event in pairs(self._values.childrenEvents) do
|
||||
self:sortChildrenEvents(event)
|
||||
end
|
||||
end
|
||||
for _, child in ipairs(self.get("visibleChildren")) do
|
||||
if child == self then
|
||||
self.basalt.LOGGER.error("CIRCULAR REFERENCE DETECTED!")
|
||||
return
|
||||
|
||||
297
src/elements/Flexbox.lua
Normal file
297
src/elements/Flexbox.lua
Normal file
@@ -0,0 +1,297 @@
|
||||
local elementManager = require("elementManager")
|
||||
local Container = elementManager.getElement("Container")
|
||||
|
||||
---@class Flexbox : Container
|
||||
local Flexbox = setmetatable({}, Container)
|
||||
Flexbox.__index = Flexbox
|
||||
|
||||
Flexbox.defineProperty(Flexbox, "flexDirection", {default = "row", type = "string"})
|
||||
Flexbox.defineProperty(Flexbox, "flexSpacing", {default = 1, type = "number"})
|
||||
Flexbox.defineProperty(Flexbox, "flexJustifyContent", {
|
||||
default = "flex-start",
|
||||
type = "string",
|
||||
setter = function(self, value)
|
||||
if not value:match("^flex%-") then
|
||||
value = "flex-" .. value
|
||||
end
|
||||
return value
|
||||
end
|
||||
})
|
||||
Flexbox.defineProperty(Flexbox, "flexWrap", {default = false, type = "boolean"})
|
||||
Flexbox.defineProperty(Flexbox, "flexUpdateLayout", {default = false, type = "boolean"})
|
||||
|
||||
local lineBreakElement = {
|
||||
getHeight = function(self) return 0 end,
|
||||
getWidth = function(self) return 0 end,
|
||||
getZ = function(self) return 1 end,
|
||||
getPosition = function(self) return 0, 0 end,
|
||||
getSize = function(self) return 0, 0 end,
|
||||
isType = function(self) return false end,
|
||||
getType = function(self) return "lineBreak" end,
|
||||
getName = function(self) return "lineBreak" end,
|
||||
setPosition = function(self) end,
|
||||
setParent = function(self) end,
|
||||
setSize = function(self) end,
|
||||
getFlexGrow = function(self) return 0 end,
|
||||
getFlexShrink = function(self) return 0 end,
|
||||
getFlexBasis = function(self) return 0 end,
|
||||
init = function(self) end,
|
||||
getVisible = function(self) return true end,
|
||||
}
|
||||
|
||||
|
||||
local function sortElements(self, direction, spacing, wrap)
|
||||
local elements = self.get("children")
|
||||
local sortedElements = {}
|
||||
if not(wrap)then
|
||||
local index = 1
|
||||
local lineSize = 1
|
||||
local lineOffset = 1
|
||||
for _,v in pairs(elements)do
|
||||
if(sortedElements[index]==nil)then sortedElements[index]={offset=1} end
|
||||
|
||||
local childHeight = direction == "row" and v.get("height") or v.get("width")
|
||||
if childHeight > lineSize then
|
||||
lineSize = childHeight
|
||||
end
|
||||
if(v == lineBreakElement)then
|
||||
lineOffset = lineOffset + lineSize + spacing
|
||||
lineSize = 1
|
||||
index = index + 1
|
||||
sortedElements[index] = {offset=lineOffset}
|
||||
else
|
||||
table.insert(sortedElements[index], v)
|
||||
end
|
||||
end
|
||||
elseif(wrap)then
|
||||
local lineSize = 1
|
||||
local lineOffset = 1
|
||||
|
||||
local maxSize = direction == "row" and self.get("width") or self.get("height")
|
||||
local usedSize = 0
|
||||
local index = 1
|
||||
|
||||
for _,v in pairs(elements) do
|
||||
if(sortedElements[index]==nil) then sortedElements[index]={offset=1} end
|
||||
|
||||
if v:getType() == "lineBreak" then
|
||||
lineOffset = lineOffset + lineSize + spacing
|
||||
usedSize = 0
|
||||
lineSize = 1
|
||||
index = index + 1
|
||||
sortedElements[index] = {offset=lineOffset}
|
||||
else
|
||||
local objSize = direction == "row" and v.get("width") or v.get("height")
|
||||
if(objSize+usedSize<=maxSize) then
|
||||
table.insert(sortedElements[index], v)
|
||||
usedSize = usedSize + objSize + spacing
|
||||
else
|
||||
lineOffset = lineOffset + lineSize + spacing
|
||||
lineSize = direction == "row" and v.get("height") or v.get("width")
|
||||
index = index + 1
|
||||
usedSize = objSize + spacing
|
||||
sortedElements[index] = {offset=lineOffset, v}
|
||||
end
|
||||
|
||||
local childHeight = direction == "row" and v.get("height") or v.get("width")
|
||||
if childHeight > lineSize then
|
||||
lineSize = childHeight
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return sortedElements
|
||||
end
|
||||
|
||||
local function calculateRow(self, children, spacing, justifyContent)
|
||||
local containerWidth = self.get("width")
|
||||
|
||||
local usedSpace = spacing * (#children - 1)
|
||||
local totalFlexGrow = 0
|
||||
|
||||
for _, child in ipairs(children) do
|
||||
if child ~= lineBreakElement then
|
||||
usedSpace = usedSpace + child.get("width")
|
||||
totalFlexGrow = totalFlexGrow + child.get("flexGrow")
|
||||
end
|
||||
end
|
||||
|
||||
local remainingSpace = containerWidth - usedSpace
|
||||
local extraSpacePerUnit = totalFlexGrow > 0 and (remainingSpace / totalFlexGrow) or 0
|
||||
local distributedSpace = 0
|
||||
|
||||
local currentX = 1
|
||||
for i, child in ipairs(children) do
|
||||
if child ~= lineBreakElement then
|
||||
local childWidth = child.get("width")
|
||||
|
||||
if child.get("flexGrow") > 0 then
|
||||
|
||||
if i == #children then
|
||||
local extraSpace = remainingSpace - distributedSpace
|
||||
childWidth = childWidth + extraSpace
|
||||
else
|
||||
local extraSpace = math.floor(extraSpacePerUnit * child.get("flexGrow"))
|
||||
childWidth = childWidth + extraSpace
|
||||
distributedSpace = distributedSpace + extraSpace
|
||||
end
|
||||
end
|
||||
|
||||
child.set("x", currentX)
|
||||
child.set("y", children.offset or 1)
|
||||
child.set("width", childWidth)
|
||||
currentX = currentX + childWidth + spacing
|
||||
end
|
||||
end
|
||||
|
||||
if justifyContent == "flex-end" then
|
||||
local offset = containerWidth - (currentX - spacing - 1)
|
||||
for _, child in ipairs(children) do
|
||||
child.set("x", child.get("x") + offset)
|
||||
end
|
||||
elseif justifyContent == "flex-center" or justifyContent == "center" then -- Akzeptiere beide Formate
|
||||
local offset = math.floor((containerWidth - (currentX - spacing - 1)) / 2)
|
||||
for _, child in ipairs(children) do
|
||||
child.set("x", child.get("x") + offset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function calculateColumn(self, children, spacing, justifyContent)
|
||||
local containerHeight = self.get("height")
|
||||
|
||||
local usedSpace = spacing * (#children - 1)
|
||||
local totalFlexGrow = 0
|
||||
|
||||
for _, child in ipairs(children) do
|
||||
if child ~= lineBreakElement then
|
||||
usedSpace = usedSpace + child.get("height")
|
||||
totalFlexGrow = totalFlexGrow + child.get("flexGrow")
|
||||
end
|
||||
end
|
||||
|
||||
local remainingSpace = containerHeight - usedSpace
|
||||
local extraSpacePerUnit = totalFlexGrow > 0 and (remainingSpace / totalFlexGrow) or 0
|
||||
local distributedSpace = 0
|
||||
|
||||
local currentY = 1
|
||||
for i, child in ipairs(children) do
|
||||
if child ~= lineBreakElement then
|
||||
local childHeight = child.get("height")
|
||||
|
||||
if child.get("flexGrow") > 0 then
|
||||
|
||||
if i == #children then
|
||||
local extraSpace = remainingSpace - distributedSpace
|
||||
childHeight = childHeight + extraSpace
|
||||
else
|
||||
local extraSpace = math.floor(extraSpacePerUnit * child.get("flexGrow"))
|
||||
childHeight = childHeight + extraSpace
|
||||
distributedSpace = distributedSpace + extraSpace
|
||||
end
|
||||
end
|
||||
|
||||
child.set("x", children.offset or 1)
|
||||
child.set("y", currentY)
|
||||
child.set("height", childHeight)
|
||||
currentY = currentY + childHeight + spacing
|
||||
end
|
||||
end
|
||||
|
||||
if justifyContent == "flex-end" then
|
||||
local offset = containerHeight - (currentY - spacing - 1)
|
||||
for _, child in ipairs(children) do
|
||||
child.set("y", child.get("y") + offset)
|
||||
end
|
||||
elseif justifyContent == "flex-center" or justifyContent == "center" then -- Akzeptiere beide Formate
|
||||
local offset = math.floor((containerHeight - (currentY - spacing - 1)) / 2)
|
||||
for _, child in ipairs(children) do
|
||||
child.set("y", child.get("y") + offset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function updateLayout(self, direction, spacing, justifyContent, wrap)
|
||||
local elements = sortElements(self, direction, spacing, wrap)
|
||||
if direction == "row" then
|
||||
for _,v in pairs(elements)do
|
||||
calculateRow(self, v, spacing, justifyContent)
|
||||
end
|
||||
else
|
||||
for _,v in pairs(elements)do
|
||||
calculateColumn(self, v, spacing, justifyContent)
|
||||
end
|
||||
end
|
||||
self.set("flexUpdateLayout", false)
|
||||
end
|
||||
|
||||
--- Creates a new Flexbox instance
|
||||
--- @param props table The properties to initialize the element with
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return Flexbox object The newly created Flexbox instance
|
||||
--- @usage local element = Flexbox.new("myId", basalt)
|
||||
function Flexbox.new(props, basalt)
|
||||
local self = setmetatable({}, Flexbox):__init()
|
||||
self:init(props, basalt)
|
||||
self.set("width", 12)
|
||||
self.set("height", 6)
|
||||
self.set("background", colors.blue)
|
||||
self.set("z", 10)
|
||||
self:observe("width", function() self.set("flexUpdateLayout", true) end)
|
||||
self:observe("height", function() self.set("flexUpdateLayout", true) end)
|
||||
return self
|
||||
end
|
||||
|
||||
function Flexbox:init(props, basalt)
|
||||
Container.init(self, props, basalt)
|
||||
self.set("type", "Flexbox")
|
||||
end
|
||||
|
||||
function Flexbox:addChild(element)
|
||||
Container.addChild(self, element)
|
||||
|
||||
if(element~=lineBreakElement)then
|
||||
element:instanceProperty("flexGrow", {default = 0, type = "number"})
|
||||
element:instanceProperty("flexShrink", {default = 0, type = "number"})
|
||||
element:instanceProperty("flexBasis", {default = 0, type = "number"})
|
||||
end
|
||||
|
||||
self.set("flexUpdateLayout", true)
|
||||
return self
|
||||
end
|
||||
|
||||
function Flexbox:removeChild(element)
|
||||
Container.removeChild(self, element)
|
||||
|
||||
if(element~=lineBreakElement)then
|
||||
element.setFlexGrow = nil
|
||||
element.setFlexShrink = nil
|
||||
element.setFlexBasis = nil
|
||||
element.getFlexGrow = nil
|
||||
element.getFlexShrink = nil
|
||||
element.getFlexBasis = nil
|
||||
element.set("flexGrow", nil)
|
||||
element.set("flexShrink", nil)
|
||||
element.set("flexBasis", nil)
|
||||
end
|
||||
|
||||
self.set("flexUpdateLayout", true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds a new line break to the flexbox.
|
||||
---@param self Flexbox The element itself
|
||||
---@return Flexbox
|
||||
function Flexbox:addLineBreak()
|
||||
self:addChild(lineBreakElement)
|
||||
return self
|
||||
end
|
||||
|
||||
function Flexbox:render()
|
||||
if(self.get("flexUpdateLayout"))then
|
||||
updateLayout(self, self.get("flexDirection"), self.get("flexSpacing"), self.get("flexJustifyContent"), self.get("flexWrap"))
|
||||
end
|
||||
Container.render(self)
|
||||
end
|
||||
|
||||
return Flexbox
|
||||
@@ -1,18 +1,28 @@
|
||||
local Container = require("elements/Container")
|
||||
local elementManager = require("elementManager")
|
||||
local Container = elementManager.getElement("Container")
|
||||
|
||||
---@class Frame : Container
|
||||
local Frame = setmetatable({}, Container)
|
||||
Frame.__index = Frame
|
||||
|
||||
function Frame.new(id, basalt)
|
||||
--- Creates a new Frame instance
|
||||
--- @param props table The properties to initialize the element with
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return Frame object The newly created Frame instance
|
||||
--- @usage local element = Frame.new("myId", basalt)
|
||||
function Frame.new(props, basalt)
|
||||
local self = setmetatable({}, Frame):__init()
|
||||
self:init(id, basalt)
|
||||
self:init(props, 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
|
||||
|
||||
function Frame:init(props, basalt)
|
||||
Container.init(self, props, basalt)
|
||||
self.set("type", "Frame")
|
||||
end
|
||||
|
||||
return Frame
|
||||
111
src/elements/Input.lua
Normal file
111
src/elements/Input.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
local VisualElement = require("elements/VisualElement")
|
||||
|
||||
---@class Input : VisualElement
|
||||
local Input = setmetatable({}, VisualElement)
|
||||
Input.__index = Input
|
||||
|
||||
---@property text string Input - text to be displayed
|
||||
Input.defineProperty(Input, "text", {default = "", type = "string", canTriggerRender = true})
|
||||
|
||||
---@property cursorPos number Input - current cursor position
|
||||
Input.defineProperty(Input, "cursorPos", {default = 1, type = "number"})
|
||||
|
||||
---@property viewOffset number Input - offset für Text-Viewport
|
||||
Input.defineProperty(Input, "viewOffset", {default = 0, type = "number"})
|
||||
|
||||
Input.listenTo(Input, "mouse_click")
|
||||
Input.listenTo(Input, "key")
|
||||
Input.listenTo(Input, "char")
|
||||
|
||||
--- Creates a new Input instance
|
||||
--- @param id string The unique identifier for this element
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return Input object The newly created Input instance
|
||||
--- @usage local element = Input.new("myId", basalt)
|
||||
function Input.new(id, basalt)
|
||||
local self = setmetatable({}, Input):__init()
|
||||
self:init(id, basalt)
|
||||
self.set("width", 8)
|
||||
self.set("z", 3)
|
||||
return self
|
||||
end
|
||||
|
||||
function Input:init(id, basalt)
|
||||
VisualElement.init(self, id, basalt)
|
||||
self.set("type", "Input")
|
||||
end
|
||||
|
||||
function Input:char(char)
|
||||
if not self.get("focused") then return end
|
||||
local text = self.get("text")
|
||||
local pos = self.get("cursorPos")
|
||||
self.set("text", text:sub(1, pos-1) .. char .. text:sub(pos))
|
||||
self.set("cursorPos", pos + 1)
|
||||
self:updateViewport()
|
||||
end
|
||||
|
||||
function Input:key(key)
|
||||
if not self.get("focused") then return end
|
||||
local pos = self.get("cursorPos")
|
||||
local text = self.get("text")
|
||||
|
||||
if key == keys.left and pos > 1 then
|
||||
self.set("cursorPos", pos - 1)
|
||||
self:setCursor(pos - 1,1, true)
|
||||
elseif key == keys.right and pos <= #text then
|
||||
self.set("cursorPos", pos + 1)
|
||||
self:setCursor(pos + 1,1, true)
|
||||
elseif key == keys.backspace and pos > 1 then
|
||||
self.set("text", text:sub(1, pos-2) .. text:sub(pos))
|
||||
self.set("cursorPos", pos - 1)
|
||||
self:setCursor(pos - 1,1, true)
|
||||
end
|
||||
self:updateViewport()
|
||||
end
|
||||
|
||||
function Input:focus()
|
||||
VisualElement.focus(self)
|
||||
self.set("background", colors.blue)
|
||||
self:setCursor(1,1, true)
|
||||
end
|
||||
|
||||
function Input:blur()
|
||||
VisualElement.blur(self)
|
||||
self.set("background", colors.green)
|
||||
end
|
||||
|
||||
function Input:updateViewport()
|
||||
local width = self.get("width")
|
||||
local text = self.get("text")
|
||||
local cursorPos = self.get("cursorPos")
|
||||
local viewOffset = self.get("viewOffset")
|
||||
|
||||
-- Wenn Cursor außerhalb des sichtbaren Bereichs nach rechts
|
||||
if cursorPos - viewOffset > width then
|
||||
self.set("viewOffset", cursorPos - width)
|
||||
end
|
||||
|
||||
-- Wenn Cursor außerhalb des sichtbaren Bereichs nach links
|
||||
if cursorPos <= viewOffset then
|
||||
self.set("viewOffset", cursorPos - 1)
|
||||
end
|
||||
end
|
||||
|
||||
function Input:render()
|
||||
VisualElement.render(self)
|
||||
local text = self.get("text")
|
||||
local viewOffset = self.get("viewOffset")
|
||||
local width = self.get("width")
|
||||
|
||||
-- Nur den sichtbaren Teil des Textes rendern
|
||||
local visibleText = text:sub(viewOffset + 1, viewOffset + width)
|
||||
self:textFg(1, 1, visibleText, self.get("foreground"))
|
||||
|
||||
if self.get("focused") then
|
||||
local cursorPos = self.get("cursorPos")
|
||||
-- Cursor relativ zum Viewport positionieren
|
||||
self:setCursor(cursorPos - viewOffset, 1, true)
|
||||
end
|
||||
end
|
||||
|
||||
return Input
|
||||
38
src/elements/Label.lua
Normal file
38
src/elements/Label.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
local elementManager = require("elementManager")
|
||||
local VisualElement = elementManager.getElement("VisualElement")
|
||||
|
||||
---@class Label : VisualElement
|
||||
local Label = setmetatable({}, VisualElement)
|
||||
Label.__index = Label
|
||||
|
||||
---@property text string Label Label text to be displayed
|
||||
Label.defineProperty(Label, "text", {default = "Label", type = "string", setter = function(self, value)
|
||||
self.set("width", #value)
|
||||
return value
|
||||
end})
|
||||
|
||||
--- Creates a new Label instance
|
||||
--- @param name table The properties to initialize the element with
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return Label object The newly created Label instance
|
||||
--- @usage local element = Label.new("myId", basalt)
|
||||
function Label.new(props, basalt)
|
||||
local self = setmetatable({}, Label):__init()
|
||||
self:init(props, basalt)
|
||||
self.set("z", 3)
|
||||
self.set("backgroundEnabled", false)
|
||||
return self
|
||||
end
|
||||
|
||||
function Label:init(props, basalt)
|
||||
VisualElement.init(self, props, basalt)
|
||||
self.set("type", "Label")
|
||||
end
|
||||
|
||||
function Label:render()
|
||||
VisualElement.render(self)
|
||||
local text = self.get("text")
|
||||
self:textFg(1, 1, text, self.get("foreground"))
|
||||
end
|
||||
|
||||
return Label
|
||||
@@ -1,4 +1,5 @@
|
||||
local BaseElement = require("elements/BaseElement")
|
||||
local elementManager = require("elementManager")
|
||||
local BaseElement = elementManager.getElement("BaseElement")
|
||||
|
||||
---@alias color number
|
||||
|
||||
@@ -8,40 +9,70 @@ VisualElement.__index = VisualElement
|
||||
local tHex = require("libraries/colorHex")
|
||||
|
||||
---@property x number 1 x position of the element
|
||||
BaseElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true})
|
||||
VisualElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true})
|
||||
---@property y number 1 y position of the element
|
||||
BaseElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true})
|
||||
VisualElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true})
|
||||
---@property z number 1 z position of the element
|
||||
BaseElement.defineProperty(VisualElement, "z", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
|
||||
self.basalt.LOGGER.debug("Setting z to " .. value)
|
||||
VisualElement.defineProperty(VisualElement, "z", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
|
||||
if self.parent then
|
||||
self.parent:sortChildren()
|
||||
end
|
||||
return value
|
||||
end})
|
||||
|
||||
---@property width number 1 width of the element
|
||||
BaseElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true})
|
||||
VisualElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true})
|
||||
---@property height number 1 height of the element
|
||||
BaseElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true})
|
||||
VisualElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true})
|
||||
---@property background color black background color of the element
|
||||
BaseElement.defineProperty(VisualElement, "background", {default = colors.black, type = "number", canTriggerRender = true})
|
||||
VisualElement.defineProperty(VisualElement, "background", {default = colors.black, type = "number", canTriggerRender = true})
|
||||
---@property foreground color white foreground color of the element
|
||||
BaseElement.defineProperty(VisualElement, "foreground", {default = colors.white, type = "number", canTriggerRender = true})
|
||||
---@property clicked boolean false element is currently clicked
|
||||
BaseElement.defineProperty(VisualElement, "clicked", {default = false, type = "boolean"})
|
||||
VisualElement.defineProperty(VisualElement, "foreground", {default = colors.white, type = "number", canTriggerRender = true})
|
||||
---@property clicked boole an false element is currently clicked
|
||||
VisualElement.defineProperty(VisualElement, "clicked", {default = false, type = "boolean"})
|
||||
---@property backgroundEnabled boolean true whether the background is enabled
|
||||
VisualElement.defineProperty(VisualElement, "backgroundEnabled", {default = true, type = "boolean", canTriggerRender = true})
|
||||
---@property focused boolean false whether the element is focused
|
||||
VisualElement.defineProperty(VisualElement, "focused", {default = false, type = "boolean", setter = function(self, value, internal)
|
||||
local curValue = self.get("focused")
|
||||
if value == curValue then return value end
|
||||
|
||||
if value then
|
||||
self:focus()
|
||||
else
|
||||
self:blur()
|
||||
end
|
||||
|
||||
if not internal and self.parent then
|
||||
if value then
|
||||
self.parent:setFocusedChild(self)
|
||||
else
|
||||
self.parent:setFocusedChild(nil)
|
||||
end
|
||||
end
|
||||
return value
|
||||
end})
|
||||
|
||||
VisualElement.listenTo(VisualElement, "focus")
|
||||
VisualElement.listenTo(VisualElement, "blur")
|
||||
|
||||
--- Creates a new VisualElement instance
|
||||
--- @param id string The unique identifier for this element
|
||||
--- @param props table The properties to initialize the element with
|
||||
--- @param basalt table The basalt instance
|
||||
--- @return VisualElement object The newly created VisualElement instance
|
||||
--- @usage local element = VisualElement.new("myId", basalt)
|
||||
function VisualElement.new(id, basalt)
|
||||
function VisualElement.new(props, basalt)
|
||||
local self = setmetatable({}, VisualElement):__init()
|
||||
self:init(id, basalt)
|
||||
self:init(props, basalt)
|
||||
self.set("type", "VisualElement")
|
||||
return self
|
||||
end
|
||||
|
||||
function VisualElement:init(props, basalt)
|
||||
BaseElement.init(self, props, basalt)
|
||||
self.set("type", "VisualElement")
|
||||
end
|
||||
|
||||
--- Draws a text character/fg/bg at the specified position with a certain size, used in the rendering system
|
||||
--- @param x number The x position to draw
|
||||
--- @param y number The y position to draw
|
||||
@@ -87,9 +118,10 @@ 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)
|
||||
self:fireEvent("mouse_click", button, self:getRelativePosition(x, y))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function VisualElement:mouse_up(button, x, y)
|
||||
@@ -98,40 +130,43 @@ function VisualElement:mouse_up(button, x, y)
|
||||
self:fireEvent("mouse_up", button, x, y)
|
||||
return true
|
||||
end
|
||||
self:fireEvent("mouse_release", button, x, y)
|
||||
self:fireEvent("mouse_release", button, self:getRelativePosition(x, y))
|
||||
end
|
||||
|
||||
function VisualElement:mouse_release()
|
||||
self.set("clicked", false)
|
||||
end
|
||||
|
||||
--- Handles all events
|
||||
--- @param event string The event to handle
|
||||
--- @vararg any The arguments for the event
|
||||
--- @return boolean? handled Whether the event was handled
|
||||
function VisualElement:handleEvent(event, ...)
|
||||
if(self[event])then
|
||||
return self[event](self, ...)
|
||||
end
|
||||
function VisualElement:focus()
|
||||
self:fireEvent("focus")
|
||||
end
|
||||
|
||||
function VisualElement:blur()
|
||||
self:fireEvent("blur")
|
||||
self:setCursor(1,1, false)
|
||||
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")
|
||||
local xPos, yPos = self.get("x"), self.get("y")
|
||||
if(x ~= nil) then
|
||||
xPos = xPos + x - 1
|
||||
end
|
||||
if(y ~= nil) then
|
||||
yPos = yPos + y - 1
|
||||
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
|
||||
xPos = xPos + px - 1
|
||||
yPos = yPos + py - 1
|
||||
parent = parent.parent
|
||||
end
|
||||
|
||||
return x, y
|
||||
return xPos, yPos
|
||||
end
|
||||
|
||||
--- Returns the relative position of the element or the given coordinates.
|
||||
@@ -155,10 +190,19 @@ function VisualElement:getRelativePosition(x, y)
|
||||
y - (elementY - 1) - (parentY - 1)
|
||||
end
|
||||
|
||||
function VisualElement:setCursor(x, y, blink)
|
||||
if self.parent then
|
||||
local absX, absY = self:getAbsolutePosition(x, y)
|
||||
return self.parent:setCursor(absX, absY, blink)
|
||||
end
|
||||
end
|
||||
|
||||
--- Renders the element
|
||||
--- @usage element:render()
|
||||
function VisualElement:render()
|
||||
if(not self.get("backgroundEnabled"))then
|
||||
return
|
||||
end
|
||||
local width, height = self.get("width"), self.get("height")
|
||||
self:multiBlit(1, 1, width, height, " ", tHex[self.get("foreground")], tHex[self.get("background")])
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user