From 6393198552b4d5c993f37cd63032e6b032b808b9 Mon Sep 17 00:00:00 2001 From: Robert Jelic <36573031+NoryiE@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:32:33 +0100 Subject: [PATCH] Update with some fixxes and improvements --- install.lua | 235 +++++++++++++++++++++------------ src/elements/BaseFrame.lua | 9 ++ src/elements/Button.lua | 14 +- src/elements/Checkbox.lua | 37 ++++-- src/elements/Container.lua | 44 ++++-- src/elements/Label.lua | 37 +++++- src/elements/List.lua | 2 + src/elements/VisualElement.lua | 46 +++++-- src/libraries/utils.lua | 66 ++++++--- src/plugins/animation.lua | 24 +++- src/plugins/benchmark.lua | 5 + src/plugins/reactive.lua | 4 +- src/render.lua | 2 - tools/generate-config.lua | 75 ++++++++++- 14 files changed, 446 insertions(+), 154 deletions(-) diff --git a/install.lua b/install.lua index 7469bff..a523d85 100644 --- a/install.lua +++ b/install.lua @@ -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() \ No newline at end of file diff --git a/src/elements/BaseFrame.lua b/src/elements/BaseFrame.lua index 820c854..e805793 100644 --- a/src/elements/BaseFrame.lua +++ b/src/elements/BaseFrame.lua @@ -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 diff --git a/src/elements/Button.lua b/src/elements/Button.lua index 3bdca1c..9d06724 100644 --- a/src/elements/Button.lua +++ b/src/elements/Button.lua @@ -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") diff --git a/src/elements/Checkbox.lua b/src/elements/Checkbox.lua index c7201a7..53db063 100644 --- a/src/elements/Checkbox.lua +++ b/src/elements/Checkbox.lua @@ -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 \ No newline at end of file diff --git a/src/elements/Container.lua b/src/elements/Container.lua index fe88780..cc4f749 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -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)) diff --git a/src/elements/Label.lua b/src/elements/Label.lua index 63d6a15..79911f8 100644 --- a/src/elements/Label.lua +++ b/src/elements/Label.lua @@ -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 \ No newline at end of file diff --git a/src/elements/List.lua b/src/elements/List.lua index 7e3823b..124335b 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -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 diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index 46c1b92..44ad798 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -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 diff --git a/src/libraries/utils.lua b/src/libraries/utils.lua index ff7d726..ec183ff 100644 --- a/src/libraries/utils.lua +++ b/src/libraries/utils.lua @@ -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 \ No newline at end of file diff --git a/src/plugins/animation.lua b/src/plugins/animation.lua index c3be480..576bb6a 100644 --- a/src/plugins/animation.lua +++ b/src/plugins/animation.lua @@ -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 diff --git a/src/plugins/benchmark.lua b/src/plugins/benchmark.lua index 5f7fbe0..a4dad25 100644 --- a/src/plugins/benchmark.lua +++ b/src/plugins/benchmark.lua @@ -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 diff --git a/src/plugins/reactive.lua b/src/plugins/reactive.lua index bdc22fa..b2ed866 100644 --- a/src/plugins/reactive.lua +++ b/src/plugins/reactive.lua @@ -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) diff --git a/src/render.lua b/src/render.lua index bd5bc9c..9f59962 100644 --- a/src/render.lua +++ b/src/render.lua @@ -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) diff --git a/tools/generate-config.lua b/tools/generate-config.lua index 1c813b1..22094b4 100644 --- a/tools/generate-config.lua +++ b/tools/generate-config.lua @@ -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")