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 function getChildrenHeight(container)
local REQUIRED_FILES = { local height = 0
"init.lua", for _, child in ipairs(container:getChildren()) do
"render.lua", local newHeight = child.get("y") + child.get("height")
"elementManager.lua", if newHeight > height then
"propertySystem.lua", height = newHeight
"elements/BaseElement.lua", end
"elements/VisualElement.lua", end
"elements/Container.lua", return height
"elements/BaseFrame.lua" end
}
-- Optionale Komponenten local function createScreen(index)
local OPTIONAL_ELEMENTS = { local screen = main:addFrame(coloring)
"Button", :onMouseScroll(function(self, direction)
"Input", local height = getChildrenHeight(self)
"Label", local scrollOffset = self:getOffsetY()
"List", local maxScroll = height - self:getHeight()
"Menu", scrollOffset = math.max(0, math.min(maxScroll, scrollOffset + direction))
"Table", self:setOffsetY(scrollOffset)
"Tree", end)
"Dropdown" :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 = { local function switchScreen(direction)
"animation", local newScreen = currentScreen + direction
"theme", if screens[newScreen] then
"xml", --main:setOffsetX((newScreen - 1) * main:getWidth())
"state" 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) :setBackground(colors.lightGray)
-- Header versionDropdown:onSelect(function(self, index, value)
main:addLabel() if(value == "Release") then
:setText("Basalt2 Installer") versionDesc:setText("The Release version is the most stable and tested version of Basalt. It is recommended for production use.")
:setPosition(2,2) elseif(value == "Custom") then
:setForeground(colors.black) 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 installScreen:addLabel(coloring)
local elementList = main:addList() :setText("Additional Components:")
:setPosition(2,4) :setPosition(2, "{versionDesc.y + versionDesc.height + 1}")
:setSize(20,8)
local luaLSCheckbox = installScreen:addCheckbox()
:setPosition(2, 12)
:setText("[ ] LLS definitions")
:setCheckedText("[x] LLS definitions")
:setBackground(colors.white) :setBackground(colors.white)
:setForeground(colors.black) :setForeground(colors.black)
for _, element in ipairs(OPTIONAL_ELEMENTS) do -- Screen 3: Elements
elementList:addItem({ local elementsScreen = createScreen(3)
text = element, elementsScreen:addLabel(coloring)
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()
:setText("Elements:") :setText("Elements:")
:setPosition(2,3) :setPosition(2, 2)
:setForeground(colors.black)
main:addLabel() local elementsList = elementsScreen:addList()
:setText("Plugins:") :setPosition(2, 4)
:setPosition(24,3) :setSize("{parent.width - 2}", "{parent.height - 6}")
:setForeground(colors.black)
-- Install Button local function addElements()
main:addButton() elementsList:clear()
:setText("Install") for k,v in pairs(getConfig().files)do
:setPosition(2,13) if(k:match("src/elements/"))then
:setSize(42,1) elementsList:addItem(v.name)
:onClick(function() end
-- Installation Logic hier end
local selectedElements = {} end
local selectedPlugins = {} addElements()
-- Sammle ausgewählte Items basalt.run()
-- Download Files
-- Erstelle Ordnerstruktur
end)
basalt.autoUpdate()

View File

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

View File

@@ -2,7 +2,6 @@ local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement") local VisualElement = elementManager.getElement("VisualElement")
local getCenteredPosition = require("libraries/utils").getCenteredPosition local getCenteredPosition = require("libraries/utils").getCenteredPosition
--- This is the button class. It is a visual element that can be clicked.
---@class Button : VisualElement ---@class Button : VisualElement
local Button = setmetatable({}, VisualElement) local Button = setmetatable({}, VisualElement)
Button.__index = Button 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 ---@event mouse_click The event that is triggered when the button is clicked
Button.listenTo(Button, "mouse_click") Button.listenTo(Button, "mouse_click")
Button.listenTo(Button, "mouse_up")
--- Creates a new Button instance function Button.new(props, basalt)
--- @shortDescription Creates a new Button instance
--- @return table self The created instance
function Button.new()
local self = setmetatable({}, Button):__init() local self = setmetatable({}, Button):__init()
self:init(props, basalt)
self.set("width", 10) self.set("width", 10)
self.set("height", 3) self.set("height", 3)
self.set("z", 5) self.set("z", 5)
return self return self
end 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) function Button:init(props, basalt)
VisualElement.init(self, props, basalt) VisualElement.init(self, props, basalt)
self.set("type", "Button") self.set("type", "Button")
end end
--- Renders the Button
--- @shortDescription Renders the Button
function Button:render() function Button:render()
VisualElement.render(self) VisualElement.render(self)
local text = self.get("text") local text = self.get("text")

View File

@@ -7,10 +7,26 @@ Checkbox.__index = Checkbox
---@property checked boolean Whether checkbox is checked ---@property checked boolean Whether checkbox is checked
Checkbox.defineProperty(Checkbox, "checked", {default = false, type = "boolean", canTriggerRender = true}) Checkbox.defineProperty(Checkbox, "checked", {default = false, type = "boolean", canTriggerRender = true})
---@property text string Label text ---@property text string empty Text to display
Checkbox.defineProperty(Checkbox, "text", {default = "", type = "string", canTriggerRender = true}) Checkbox.defineProperty(Checkbox, "text", {default = " ", type = "string", canTriggerRender = true, setter=function(self, value)
---@property symbol string Check symbol local checkedText = self.get("checkedText")
Checkbox.defineProperty(Checkbox, "symbol", {default = "x", type = "string"}) 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") Checkbox.listenTo(Checkbox, "mouse_click")
@@ -19,8 +35,6 @@ Checkbox.listenTo(Checkbox, "mouse_click")
--- @return Checkbox self The created instance --- @return Checkbox self The created instance
function Checkbox.new() function Checkbox.new()
local self = setmetatable({}, Checkbox):__init() local self = setmetatable({}, Checkbox):__init()
self.set("width", 1)
self.set("height", 1)
return self return self
end end
@@ -53,13 +67,12 @@ end
function Checkbox:render() function Checkbox:render()
VisualElement.render(self) VisualElement.render(self)
local text = self.get("checked") and self.get("symbol") or " " local checked = self.get("checked")
self:textFg(1, 1, "["..text.."]", self.get("foreground")) 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") self:textFg(1, 1, text, self.get("foreground"))
if #label > 0 then
self:textFg(4, 1, label, self.get("foreground"))
end
end end
return Checkbox 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 ---@property visibleChildrenEvents table {} The visible children events of the container
Container.defineProperty(Container, "visibleChildrenEvents", {default = {}, type = "table"}) 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 for k, _ in pairs(elementManager:getElementList()) do
local capitalizedName = k:sub(1,1):upper() .. k:sub(2) local capitalizedName = k:sub(1,1):upper() .. k:sub(2)
if capitalizedName ~= "BaseFrame" then if capitalizedName ~= "BaseFrame" then
@@ -87,14 +100,26 @@ end
--- @param child table The child to check --- @param child table The child to check
--- @return boolean boolean the child is visible --- @return boolean boolean the child is visible
function Container:isChildVisible(child) 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 childX, childY = child.get("x"), child.get("y")
local childW, childH = child.get("width"), child.get("height") local childW, childH = child.get("width"), child.get("height")
local containerW, containerH = self.get("width"), self.get("height")
return childX <= containerW and local relativeX
childY <= containerH and local relativeY
childX + childW > 0 and if(child.get("ignoreOffset"))then
childY + childH > 0 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 end
--- Adds a child to the container --- Adds a child to the container
@@ -295,7 +320,8 @@ local function convertMousePosition(self, event, ...)
local args = {...} local args = {...}
if event:find("mouse_") then if event:find("mouse_") then
local button, absX, absY = ... 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} args = {button, relX, relY}
end end
return args return args
@@ -308,7 +334,7 @@ function Container:callChildrenEvents(visibleOnly, event, ...)
for i = #events, 1, -1 do for i = #events, 1, -1 do
local child = events[i] local child = events[i]
if(child:dispatchEvent(event, ...))then if(child:dispatchEvent(event, ...))then
return true, child return true, child
end end
end end
end end

View File

@@ -1,5 +1,6 @@
local elementManager = require("elementManager") local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement") 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 --- This is the label class. It provides a simple text display element that automatically
--- resizes its width based on the text content. --- resizes its width based on the text content.
@@ -8,9 +9,23 @@ local Label = setmetatable({}, VisualElement)
Label.__index = Label Label.__index = Label
---@property text string Label The text content to display. Can be a string or a function that returns a string ---@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 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 return value
end}) end})
@@ -37,12 +52,28 @@ function Label:init(props, basalt)
return self return self
end 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 --- Renders the Label
--- @shortDescription Renders the Label by drawing its text content --- @shortDescription Renders the Label by drawing its text content
function Label:render() function Label:render()
VisualElement.render(self) VisualElement.render(self)
local text = self.get("text") 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 end
return Label return Label

View File

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

View File

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

View File

@@ -47,25 +47,61 @@ function utils.uuid()
math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff)) math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff))
end end
function utils.split(str, sep) function utils.split(str, delimiter)
local parts = {} local result = {}
local start = 1 for match in (str..delimiter):gmatch("(.-)"..delimiter) do
local len = len(str) table.insert(result, match)
local splitIndex = 1 end
return result
end
while true do function utils.removeTags(input)
local index = str:find(sep, start, true) return input:gsub("{[^}]+}", "")
if not index then end
parts[splitIndex] = str:sub(start, len)
break 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 end
parts[splitIndex] = str:sub(start, index - 1)
start = index + 1
splitIndex = splitIndex + 1
end end
return parts return lines
end end
return utils return utils

View File

@@ -48,7 +48,7 @@ function AnimationInstance.new(element, animType, args, duration, easing)
self.element = element self.element = element
self.type = animType self.type = animType
self.args = args self.args = args
self.duration = duration self.duration = duration or 1
self.startTime = 0 self.startTime = 0
self.isPaused = false self.isPaused = false
self.handlers = registeredAnimations[animType] self.handlers = registeredAnimations[animType]
@@ -288,6 +288,26 @@ Animation.registerAnimation("move", {
end 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", { Animation.registerAnimation("morphText", {
start = function(anim) start = function(anim)
local startText = anim.element.get(anim.args[1]) local startText = anim.element.get(anim.args[1])
@@ -384,12 +404,12 @@ function VisualElement.hooks.dispatchEvent(self, event, ...)
if animation then if animation then
animation:event(event, ...) animation:event(event, ...)
end end
return true
end end
end end
---@private ---@private
function VisualElement.setup(element) function VisualElement.setup(element)
VisualElementBaseDispatchEvent = element.dispatchEvent
element.defineProperty(element, "animation", {default = nil, type = "table"}) element.defineProperty(element, "animation", {default = nil, type = "table"})
element.listenTo(element, "timer") element.listenTo(element, "timer")
end end

View File

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

View File

@@ -64,7 +64,7 @@ local function parseExpression(expr, element, propName)
elseif objName == "parent" then elseif objName == "parent" then
return element.parent.get(propName) return element.parent.get(propName)
else else
local target = element:getBaseFrame():getChild(objName) local target = element.parent:getChild(objName)
if not target then if not target then
errorManager.header = "Reactive evaluation error" errorManager.header = "Reactive evaluation error"
errorManager.error("Could not find element: " .. objName) errorManager.error("Could not find element: " .. objName)
@@ -103,7 +103,7 @@ local function validateReferences(expr, element)
return false return false
end end
else else
local target = element:getBaseFrame():getChild(ref) local target = element.parent:getChild(ref)
if not target then if not target then
errorManager.header = "Reactive evaluation error" errorManager.header = "Reactive evaluation error"
errorManager.error("Referenced element not found: " .. ref) 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 if(#text ~= #fg or #text ~= #bg)then
error("Text, fg, and bg must be the same length") error("Text, fg, and bg must be the same length")
end end
text = text:rep(width) text = text:rep(width)
fg = fg:rep(width) fg = fg:rep(width)
bg = bg: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 if y < 1 or y > self.height then return self end
fg = colorChars[fg] or "0" fg = colorChars[fg] or "0"
fg = fg:rep(#text) 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.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.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) self:addDirtyRect(x, y, #text, 1)

View File

@@ -1,3 +1,6 @@
local customElements = {}
local customPlugins = {}
local function serialize(t, indent) local function serialize(t, indent)
indent = indent or "" indent = indent or ""
local result = "{\n" local result = "{\n"
@@ -22,14 +25,76 @@ local function serialize(t, indent)
return result .. indent .. "}" return result .. indent .. "}"
end 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 function scanDir(dir)
local files = {} 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$") local name = file:match("([^/]+)%.lua$")
if name then if name then
files[file] = { files[file] = {
name = name, name = name,
path = file:gsub("^src/", ""), 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
end end
@@ -37,8 +102,14 @@ local function scanDir(dir)
end end
local sourceFiles = scanDir("src") local sourceFiles = scanDir("src")
local categories = sortFiles(sourceFiles)
local config = { local config = {
files = sourceFiles, categories = categories,
metadata = {
generated = os.date(),
version = "2.0"
}
} }
local f = io.open("config.lua", "w") local f = io.open("config.lua", "w")