Update with some fixxes and improvements

This commit is contained in:
Robert Jelic
2025-02-20 11:32:33 +01:00
parent 2dfb2443cd
commit 6393198552
14 changed files with 446 additions and 154 deletions

View File

@@ -1,100 +1,167 @@
local basalt = http.get("https://raw.githubusercontent.com/Pyroxenium/Basalt2/master/release/basalt.lua")
local basalt = require("src")
local configPath = "https://raw.githubusercontent.com/Pyroxenium/Basalt2/refs/heads/main/config.lua"
local coloring = {foreground=colors.black, background=colors.white}
local currentScreen = 1
local screens = {}
local main = basalt.getMainFrame():setBackground(colors.black)
local config
local REPO_URL = "https://raw.githubusercontent.com/Pyroxenium/Basalt2/master/src"
local function getConfig()
if not config then
local request = http.get(configPath)
if request then
local content = request.readAll()
config = load(content)()
request.close()
else
error("Failed to fetch config")
end
end
return config
end
-- Basis-Komponenten die immer installiert werden müssen
local REQUIRED_FILES = {
"init.lua",
"render.lua",
"elementManager.lua",
"propertySystem.lua",
"elements/BaseElement.lua",
"elements/VisualElement.lua",
"elements/Container.lua",
"elements/BaseFrame.lua"
}
local function getChildrenHeight(container)
local height = 0
for _, child in ipairs(container:getChildren()) do
local newHeight = child.get("y") + child.get("height")
if newHeight > height then
height = newHeight
end
end
return height
end
-- Optionale Komponenten
local OPTIONAL_ELEMENTS = {
"Button",
"Input",
"Label",
"List",
"Menu",
"Table",
"Tree",
"Dropdown"
}
local function createScreen(index)
local screen = main:addFrame(coloring)
:onMouseScroll(function(self, direction)
local height = getChildrenHeight(self)
local scrollOffset = self:getOffsetY()
local maxScroll = height - self:getHeight()
scrollOffset = math.max(0, math.min(maxScroll, scrollOffset + direction))
self:setOffsetY(scrollOffset)
end)
:setSize("{parent.width - 2}", "{parent.height - 4}")
if(index==1)then
screen:setPosition(2, 2)
else
screen:setPosition("{parent.width * "..(index-1).." + 2}", 2)
end
screens[index] = screen
return screen
end
local OPTIONAL_PLUGINS = {
"animation",
"theme",
"xml",
"state"
}
local function switchScreen(direction)
local newScreen = currentScreen + direction
if screens[newScreen] then
--main:setOffsetX((newScreen - 1) * main:getWidth())
main:animate():moveOffset((newScreen - 1) * main:getWidth(), 0, 0.5):start()
currentScreen = newScreen
end
end
local main = basalt.createFrame()
main:addButton()
:setBackground("{self.clicked and colors.black or colors.white}")
:setForeground("{self.clicked and colors.white or colors.black}")
:setSize(8, 1)
:setText("Next")
:setPosition("{parent.width - 9}", "{parent.height - 1}")
:setIgnoreOffset(true)
:onMouseClick(function() switchScreen(1) end)
main:addButton()
:setBackground("{self.clicked and colors.black or colors.white}")
:setForeground("{self.clicked and colors.white or colors.black}")
:setSize(8, 1)
:setText("Back")
:setPosition(2, "{parent.height - 1}")
:setIgnoreOffset(true)
:onMouseClick(function() switchScreen(-1) end)
-- Screen 1: Welcome
local welcomeScreen = createScreen(1)
welcomeScreen:addLabel(coloring)
:setText("Welcome to Basalt!")
:setPosition(2, 2)
welcomeScreen:addLabel(coloring)
:setWidth("{parent.width - 2}")
:setAutoSize(false)
:setText([[Basalt is an open-source project created with passion for the ComputerCraft community. It provides you with all the tools you need to create beautiful and interactive user interfaces.
The project is actively maintained and continuously improving thanks to our amazing community. Whether you're a beginner or an experienced developer, you'll find Basalt easy to use yet powerful enough for complex applications.
Have ideas or want to get involved? Join our friendly community on Discord or GitHub - we'd love to hear from you and welcome contributions of any kind!
Let's start creating something awesome together!]])
:setPosition(2, 4)
-- Screen 2: Installation
local installScreen = createScreen(2)
installScreen:addLabel(coloring)
:setText("Choose Your Installation")
:setPosition(2, 2)
installScreen:addLabel(coloring)
:setText("Select Version:")
:setPosition(2, 4)
local versionDropdown = installScreen:addDropdown()
:setPosition("{parent.width - self.width - 1}", 4)
:setSize(15, 1)
:setBackground(colors.black)
:setForeground(colors.white)
:addItem("Release")
:addItem("Dev")
:addItem("Custom")
local versionDesc = installScreen:addLabel("versionDesc")
:setWidth("{parent.width - 2}")
:setAutoSize(false)
:setText("The Release version is the most stable and tested version of Basalt. It is recommended for production use.")
:setPosition(2, 7)
:setSize("{parent.width - 4}", 3)
:setBackground(colors.lightGray)
-- Header
main:addLabel()
:setText("Basalt2 Installer")
:setPosition(2,2)
:setForeground(colors.black)
versionDropdown:onSelect(function(self, index, value)
if(value == "Release") then
versionDesc:setText("The Release version is the most stable and tested version of Basalt. It is recommended for production use.")
elseif(value == "Custom") then
versionDesc:setText("The Custom version allows you to specify which elements or plugins you want to install.")
else
versionDesc:setText("The Dev version is the latest development version of Basalt. It may contain new features and improvements, but it may also have bugs and issues.")
end
end)
-- Element Selection
local elementList = main:addList()
:setPosition(2,4)
:setSize(20,8)
installScreen:addLabel(coloring)
:setText("Additional Components:")
:setPosition(2, "{versionDesc.y + versionDesc.height + 1}")
local luaLSCheckbox = installScreen:addCheckbox()
:setPosition(2, 12)
:setText("[ ] LLS definitions")
:setCheckedText("[x] LLS definitions")
:setBackground(colors.white)
:setForeground(colors.black)
for _, element in ipairs(OPTIONAL_ELEMENTS) do
elementList:addItem({
text = element,
selected = true
})
end
-- Plugin Selection
local pluginList = main:addList()
:setPosition(24,4)
:setSize(20,8)
:setBackground(colors.white)
:setForeground(colors.black)
for _, plugin in ipairs(OPTIONAL_PLUGINS) do
pluginList:addItem({
text = plugin,
selected = true
})
end
-- Labels
main:addLabel()
-- Screen 3: Elements
local elementsScreen = createScreen(3)
elementsScreen:addLabel(coloring)
:setText("Elements:")
:setPosition(2,3)
:setForeground(colors.black)
:setPosition(2, 2)
main:addLabel()
:setText("Plugins:")
:setPosition(24,3)
:setForeground(colors.black)
local elementsList = elementsScreen:addList()
:setPosition(2, 4)
:setSize("{parent.width - 2}", "{parent.height - 6}")
-- Install Button
main:addButton()
:setText("Install")
:setPosition(2,13)
:setSize(42,1)
:onClick(function()
-- Installation Logic hier
local selectedElements = {}
local selectedPlugins = {}
-- Sammle ausgewählte Items
-- Download Files
-- Erstelle Ordnerstruktur
end)
local function addElements()
elementsList:clear()
for k,v in pairs(getConfig().files)do
if(k:match("src/elements/"))then
elementsList:addItem(v.name)
end
end
end
addElements()
basalt.autoUpdate()
basalt.run()

View File

@@ -54,6 +54,8 @@ end
--- @param fg string The foreground color
--- @param bg string The background color
function BaseFrame:multiBlit(x, y, width, height, text, fg, bg)
if(x<1)then width = width + x - 1; x = 1 end
if(y<1)then height = height + y - 1; y = 1 end
self._render:multiBlit(x, y, width, height, text, fg, bg)
end
@@ -64,6 +66,7 @@ end
--- @param text string The text to render
--- @param fg colors The foreground color
function BaseFrame:textFg(x, y, text, fg)
if x < 1 then text = string.sub(text, 1 - x); x = 1 end
self._render:textFg(x, y, text, fg)
end
@@ -74,6 +77,7 @@ end
--- @param text string The text to render
--- @param bg colors The background color
function BaseFrame:textBg(x, y, text, bg)
if x < 1 then text = string.sub(text, 1 - x); x = 1 end
self._render:textBg(x, y, text, bg)
end
@@ -85,6 +89,11 @@ end
--- @param fg string The foreground color
--- @param bg string The background color
function BaseFrame:blit(x, y, text, fg, bg)
if x < 1 then
text = string.sub(text, 1 - x)
fg = string.sub(fg, 1 - x)
bg = string.sub(bg, 1 - x)
x = 1 end
self._render:blit(x, y, text, fg, bg)
end

View File

@@ -2,7 +2,6 @@ local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement")
local getCenteredPosition = require("libraries/utils").getCenteredPosition
--- This is the button class. It is a visual element that can be clicked.
---@class Button : VisualElement
local Button = setmetatable({}, VisualElement)
Button.__index = Button
@@ -12,30 +11,21 @@ Button.defineProperty(Button, "text", {default = "Button", type = "string", canT
---@event mouse_click The event that is triggered when the button is clicked
Button.listenTo(Button, "mouse_click")
Button.listenTo(Button, "mouse_up")
--- Creates a new Button instance
--- @shortDescription Creates a new Button instance
--- @return table self The created instance
function Button.new()
function Button.new(props, basalt)
local self = setmetatable({}, Button):__init()
self:init(props, basalt)
self.set("width", 10)
self.set("height", 3)
self.set("z", 5)
return self
end
--- Initializes the Button instance
--- @shortDescription Initializes the Button instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
function Button:init(props, basalt)
VisualElement.init(self, props, basalt)
self.set("type", "Button")
end
--- Renders the Button
--- @shortDescription Renders the Button
function Button:render()
VisualElement.render(self)
local text = self.get("text")

View File

@@ -7,10 +7,26 @@ Checkbox.__index = Checkbox
---@property checked boolean Whether checkbox is checked
Checkbox.defineProperty(Checkbox, "checked", {default = false, type = "boolean", canTriggerRender = true})
---@property text string Label text
Checkbox.defineProperty(Checkbox, "text", {default = "", type = "string", canTriggerRender = true})
---@property symbol string Check symbol
Checkbox.defineProperty(Checkbox, "symbol", {default = "x", type = "string"})
---@property text string empty Text to display
Checkbox.defineProperty(Checkbox, "text", {default = " ", type = "string", canTriggerRender = true, setter=function(self, value)
local checkedText = self.get("checkedText")
local width = math.max(#value, #checkedText)
if(self.get("autoSize"))then
self.set("width", width)
end
return value
end})
---@property checkedText string Text when checked
Checkbox.defineProperty(Checkbox, "checkedText", {default = "x", type = "string", canTriggerRender = true, setter=function(self, value)
local text = self.get("text")
local width = math.max(#value, #text)
if(self.get("autoSize"))then
self.set("width", width)
end
return value
end})
---@property autoSize boolean true Whether to automatically size the checkbox
Checkbox.defineProperty(Checkbox, "autoSize", {default = true, type = "boolean"})
Checkbox.listenTo(Checkbox, "mouse_click")
@@ -19,8 +35,6 @@ Checkbox.listenTo(Checkbox, "mouse_click")
--- @return Checkbox self The created instance
function Checkbox.new()
local self = setmetatable({}, Checkbox):__init()
self.set("width", 1)
self.set("height", 1)
return self
end
@@ -53,13 +67,12 @@ end
function Checkbox:render()
VisualElement.render(self)
local text = self.get("checked") and self.get("symbol") or " "
self:textFg(1, 1, "["..text.."]", self.get("foreground"))
local checked = self.get("checked")
local defaultText = self.get("text")
local checkedText = self.get("checkedText")
local text = string.sub(checked and checkedText or defaultText, 1, self.get("width"))
local label = self.get("text")
if #label > 0 then
self:textFg(4, 1, label, self.get("foreground"))
end
self:textFg(1, 1, text, self.get("foreground"))
end
return Checkbox

View File

@@ -47,6 +47,19 @@ Container.defineProperty(Container, "visibleChildren", {default = {}, type = "ta
---@property visibleChildrenEvents table {} The visible children events of the container
Container.defineProperty(Container, "visibleChildrenEvents", {default = {}, type = "table"})
---@property offsetX number 0 Horizontal content offset
Container.defineProperty(Container, "offsetX", {default = 0, type = "number", canTriggerRender = true, setter=function(self, value)
self.set("childrenSorted", false)
self.set("childrenEventsSorted", false)
return value
end})
---@property offsetY number 0 Vertical content offset
Container.defineProperty(Container, "offsetY", {default = 0, type = "number", canTriggerRender = true, setter=function(self, value)
self.set("childrenSorted", false)
self.set("childrenEventsSorted", false)
return value
end})
for k, _ in pairs(elementManager:getElementList()) do
local capitalizedName = k:sub(1,1):upper() .. k:sub(2)
if capitalizedName ~= "BaseFrame" then
@@ -87,14 +100,26 @@ end
--- @param child table The child to check
--- @return boolean boolean the child is visible
function Container:isChildVisible(child)
local containerW, containerH = self.get("width"), self.get("height")
local offsetX, offsetY = self.get("offsetX"), self.get("offsetY")
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
local relativeX
local relativeY
if(child.get("ignoreOffset"))then
relativeX = childX
relativeY = childY
else
relativeX = childX - offsetX
relativeY = childY - offsetY
end
return (relativeX + childW > 0) and
(relativeX <= containerW) and
(relativeY + childH > 0) and
(relativeY <= containerH)
end
--- Adds a child to the container
@@ -115,7 +140,7 @@ end
local function sortAndFilterChildren(self, children)
local visibleChildren = {}
for _, child in ipairs(children) do
if self:isChildVisible(child) and child.get("visible") then
table.insert(visibleChildren, child)
@@ -295,7 +320,8 @@ local function convertMousePosition(self, event, ...)
local args = {...}
if event:find("mouse_") then
local button, absX, absY = ...
local relX, relY = self:getRelativePosition(absX, absY)
local xOffset, yOffset = self.get("offsetX"), self.get("offsetY")
local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset)
args = {button, relX, relY}
end
return args
@@ -308,7 +334,7 @@ function Container:callChildrenEvents(visibleOnly, event, ...)
for i = #events, 1, -1 do
local child = events[i]
if(child:dispatchEvent(event, ...))then
return true, child
return true, child
end
end
end
@@ -447,7 +473,7 @@ end
--- @return Container self The container instance
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))

View File

@@ -1,5 +1,6 @@
local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement")
local wrapText = require("libraries/utils").wrapText
--- This is the label class. It provides a simple text display element that automatically
--- resizes its width based on the text content.
@@ -8,9 +9,23 @@ local Label = setmetatable({}, VisualElement)
Label.__index = Label
---@property text string Label The text content to display. Can be a string or a function that returns a string
Label.defineProperty(Label, "text", {default = "Label", type = "string", setter = function(self, value)
Label.defineProperty(Label, "text", {default = "Label", type = "string", canTriggerRender = true, setter = function(self, value)
if(type(value)=="function")then value = value() end
self.set("width", #value)
if(self.get("autoSize"))then
self.set("width", #value)
else
self.set("height", #wrapText(value, self.get("width")))
end
return value
end})
---@property autoSize boolean true Whether the label should automatically resize its width based on the text content
Label.defineProperty(Label, "autoSize", {default = true, type = "boolean", canTriggerRender = true, setter = function(self, value)
if(value)then
self.set("width", #self.get("text"))
else
self.set("height", #wrapText(self.get("text"), self.get("width")))
end
return value
end})
@@ -37,12 +52,28 @@ function Label:init(props, basalt)
return self
end
--- Gets the wrapped lines of the Label
--- @shortDescription Gets the wrapped lines of the Label
--- @return table wrappedText The wrapped lines of the Label
function Label:getWrappedText()
local text = self.get("text")
local wrappedText = wrapText(text, self.get("width"))
return wrappedText
end
--- Renders the Label
--- @shortDescription Renders the Label by drawing its text content
function Label:render()
VisualElement.render(self)
local text = self.get("text")
self:textFg(1, 1, text, self.get("foreground"))
if(self.get("autoSize"))then
self:textFg(1, 1, text, self.get("foreground"))
else
local wrappedText = wrapText(text, self.get("width"))
for i, line in ipairs(wrappedText) do
self:textFg(1, i, line, self.get("foreground"))
end
end
end
return Label

View File

@@ -29,6 +29,7 @@ function List.new()
local self = setmetatable({}, List):__init()
self.set("width", 16)
self.set("height", 8)
self.set("z", 5)
self.set("background", colors.gray)
return self
end
@@ -41,6 +42,7 @@ end
function List:init(props, basalt)
VisualElement.init(self, props, basalt)
self.set("type", "List")
return self
end
--- Adds an item to the list

View File

@@ -64,6 +64,9 @@ VisualElement.defineProperty(VisualElement, "visible", {default = true, type = "
return value
end})
---@property ignoreOffset boolean false Whether to ignore the parent's offset
VisualElement.defineProperty(VisualElement, "ignoreOffset", {default = false, type = "boolean"})
---@combinedProperty position {x y} Combined x, y position
VisualElement.combineProperties(VisualElement, "position", "x", "y")
---@combinedProperty size {width height} Combined width, height
@@ -85,6 +88,7 @@ VisualElement.listenTo(VisualElement, "focus")
VisualElement.listenTo(VisualElement, "blur")
VisualElement.listenTo(VisualElement, "mouse_enter", "mouse_move")
VisualElement.listenTo(VisualElement, "mouse_leave", "mouse_move")
VisualElement.listenTo(VisualElement, "mouse_scroll")
local max, min = math.max, math.min
@@ -112,8 +116,9 @@ end
--- @shortDescription Multi-character drawing with colors
---@protected
function VisualElement:multiBlit(x, y, width, height, text, fg, bg)
x = x + self.get("x") - 1
y = y + self.get("y") - 1
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:multiBlit(x, y, width, height, text, fg, bg)
end
@@ -124,8 +129,9 @@ end
--- @param text string The text char to draw
--- @param fg color The foreground color
function VisualElement:textFg(x, y, text, fg)
x = x + self.get("x") - 1
y = y + self.get("y") - 1
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:textFg(x, y, text, fg)
end
@@ -136,8 +142,9 @@ end
--- @param text string The text char to draw
--- @param bg color The background color
function VisualElement:textBg(x, y, text, bg)
x = x + self.get("x") - 1
y = y + self.get("y") - 1
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:textBg(x, y, text, bg)
end
@@ -149,8 +156,9 @@ end
--- @param fg string The foreground color
--- @param bg string The background color
function VisualElement:blit(x, y, text, fg, bg)
x = x + self.get("x") - 1
y = y + self.get("y") - 1
local xElement, yElement = self:calculatePosition()
x = x + xElement - 1
y = y + yElement - 1
self.parent:blit(x, y, text, fg, bg)
end
@@ -162,6 +170,12 @@ end
function VisualElement:isInBounds(x, y)
local xPos, yPos = self.get("x"), self.get("y")
local width, height = self.get("width"), self.get("height")
if(self.get("ignoreOffset"))then
if(self.parent)then
x = x - self.parent.get("offsetX")
y = y - self.parent.get("offsetY")
end
end
return x >= xPos and x <= xPos + width - 1 and
y >= yPos and y <= yPos + height - 1
@@ -256,6 +270,18 @@ function VisualElement:blur()
self:setCursor(1,1, false)
end
function VisualElement:calculatePosition()
local x, y = self.get("x"), self.get("y")
if not self.get("ignoreOffset") then
if self.parent ~= nil then
local xO, yO = self.parent.get("offsetX"), self.parent.get("offsetY")
x = x - xO
y = y - yO
end
end
return x, y
end
--- Returns the absolute position of the element or the given coordinates.
--- @shortDescription Returns the absolute position of the element
---@param x? number x position
@@ -295,9 +321,7 @@ function VisualElement:getRelativePosition(x, y)
parentX, parentY = self.parent:getRelativePosition()
end
local elementX = self.get("x")
local elementY = self.get("y")
local elementX, elementY = self.get("x"), self.get("y")
return x - (elementX - 1) - (parentX - 1),
y - (elementY - 1) - (parentY - 1)
end

View File

@@ -47,25 +47,61 @@ function utils.uuid()
math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff))
end
function utils.split(str, sep)
local parts = {}
local start = 1
local len = len(str)
local splitIndex = 1
function utils.split(str, delimiter)
local result = {}
for match in (str..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match)
end
return result
end
while true do
local index = str:find(sep, start, true)
if not index then
parts[splitIndex] = str:sub(start, len)
break
function utils.removeTags(input)
return input:gsub("{[^}]+}", "")
end
function utils.wrapText(str, width)
if str == nil then return {} end
str = utils.removeTags(str)
local lines = {}
local paragraphs = utils.split(str, "\n\n")
for i, paragraph in ipairs(paragraphs) do
if #paragraph == 0 then
table.insert(lines, "")
if i < #paragraphs then
table.insert(lines, "")
end
else
local textLines = utils.split(paragraph, "\n")
for _, line in ipairs(textLines) do
local words = utils.split(line, " ")
local currentLine = ""
for _, word in ipairs(words) do
if #currentLine == 0 then
currentLine = word
elseif #currentLine + #word + 1 <= width then
currentLine = currentLine .. " " .. word
else
table.insert(lines, currentLine)
currentLine = word
end
end
if #currentLine > 0 then
table.insert(lines, currentLine)
end
end
if i < #paragraphs then
table.insert(lines, "")
end
end
parts[splitIndex] = str:sub(start, index - 1)
start = index + 1
splitIndex = splitIndex + 1
end
return parts
return lines
end
return utils

View File

@@ -48,7 +48,7 @@ function AnimationInstance.new(element, animType, args, duration, easing)
self.element = element
self.type = animType
self.args = args
self.duration = duration
self.duration = duration or 1
self.startTime = 0
self.isPaused = false
self.handlers = registeredAnimations[animType]
@@ -288,6 +288,26 @@ Animation.registerAnimation("move", {
end
})
Animation.registerAnimation("moveOffset", {
start = function(anim)
anim.startX = anim.element.get("offsetX")
anim.startY = anim.element.get("offsetY")
end,
update = function(anim, progress)
local x = anim.startX + (anim.args[1] - anim.startX) * progress
local y = anim.startY + (anim.args[2] - anim.startY) * progress
anim.element.set("offsetX", math.floor(x))
anim.element.set("offsetY", math.floor(y))
return progress >= 1
end,
complete = function(anim)
anim.element.set("offsetX", anim.args[1])
anim.element.set("offsetY", anim.args[2])
end
})
Animation.registerAnimation("morphText", {
start = function(anim)
local startText = anim.element.get(anim.args[1])
@@ -384,12 +404,12 @@ function VisualElement.hooks.dispatchEvent(self, event, ...)
if animation then
animation:event(event, ...)
end
return true
end
end
---@private
function VisualElement.setup(element)
VisualElementBaseDispatchEvent = element.dispatchEvent
element.defineProperty(element, "animation", {default = nil, type = "table"})
element.listenTo(element, "timer")
end

View File

@@ -313,6 +313,11 @@ function API.start(name, options)
profile.name = name
profile.startTime = os.clock() * 1000
profile.custom = true
profile.calls = 0
profile.totalTime = 0
profile.minTime = math.huge
profile.maxTime = 0
profile.lastTime = 0
activeProfiles[name] = profile
end

View File

@@ -64,7 +64,7 @@ local function parseExpression(expr, element, propName)
elseif objName == "parent" then
return element.parent.get(propName)
else
local target = element:getBaseFrame():getChild(objName)
local target = element.parent:getChild(objName)
if not target then
errorManager.header = "Reactive evaluation error"
errorManager.error("Could not find element: " .. objName)
@@ -103,7 +103,7 @@ local function validateReferences(expr, element)
return false
end
else
local target = element:getBaseFrame():getChild(ref)
local target = element.parent:getChild(ref)
if not target then
errorManager.header = "Reactive evaluation error"
errorManager.error("Referenced element not found: " .. ref)

View File

@@ -91,7 +91,6 @@ function Render:multiBlit(x, y, width, height, text, fg, bg)
if(#text ~= #fg or #text ~= #bg)then
error("Text, fg, and bg must be the same length")
end
text = text:rep(width)
fg = fg:rep(width)
bg = bg:rep(width)
@@ -119,7 +118,6 @@ function Render:textFg(x, y, text, fg)
if y < 1 or y > self.height then return self end
fg = colorChars[fg] or "0"
fg = fg:rep(#text)
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.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, #text, 1)

View File

@@ -1,3 +1,6 @@
local customElements = {}
local customPlugins = {}
local function serialize(t, indent)
indent = indent or ""
local result = "{\n"
@@ -22,14 +25,76 @@ local function serialize(t, indent)
return result .. indent .. "}"
end
local function extractConfigDescription(filePath)
local f = io.open(filePath, "r")
if not f then return nil end
local content = f:read("*all")
f:close()
return content:match("%-%-%-@configDescription%s*(.-)%s*[\n\r]") or "No description available"
end
local function categorizeFile(path)
if path:match("^src/elements/") then
return "elements", "UI Elements"
elseif path:match("^src/plugins/") then
return "plugins", "Plugins and Extensions"
elseif path:match("^src/libraries/") then
return "libraries", "Utility Libraries"
elseif path:match("^src/[^/]+%.lua$") then
return "core", "Core Framework Files"
else
return "other", "Other Files"
end
end
local function sortFiles(files)
local categories = {}
for path, info in pairs(files) do
local category, categoryDesc = categorizeFile(path)
if not categories[category] then
categories[category] = {
description = categoryDesc,
files = {}
}
end
table.insert(categories[category].files, {
path = path,
name = info.name,
description = info.description
})
end
for _, cat in pairs(categories) do
table.sort(cat.files, function(a, b)
return a.name < b.name
end)
end
return categories
end
local function scanDir(dir)
local files = {}
for file in io.popen('find "'..dir..'" -type f -name "*.lua"'):lines() do
for file in io.popen('find "'..dir..'" -maxdepth 1 -type f -name "*.lua"'):lines() do
local name = file:match("([^/]+)%.lua$")
if name then
files[file] = {
name = name,
path = file:gsub("^src/", ""),
description = extractConfigDescription(file)
}
end
end
for file in io.popen('find "'..dir..'/elements" "'..dir..'/plugins" "'..dir..'/libraries" -type f -name "*.lua"'):lines() do
local name = file:match("([^/]+)%.lua$")
if name then
files[file] = {
name = name,
path = file:gsub("^src/", ""),
description = extractConfigDescription(file)
}
end
end
@@ -37,8 +102,14 @@ local function scanDir(dir)
end
local sourceFiles = scanDir("src")
local categories = sortFiles(sourceFiles)
local config = {
files = sourceFiles,
categories = categories,
metadata = {
generated = os.date(),
version = "2.0"
}
}
local f = io.open("config.lua", "w")