diff --git a/examples/benchmarks.lua b/examples/benchmarks.lua index efb9165..f562272 100644 --- a/examples/benchmarks.lua +++ b/examples/benchmarks.lua @@ -7,7 +7,6 @@ local btn = main:addButton() :setY(5) :onMouseClick(function() main:logContainerBenchmarks("render") - --main:stopChildrenBenchmark("render") end) local prog = main:addProgram() diff --git a/generate-docs.lua b/generate-docs.lua new file mode 100644 index 0000000..d3a85c6 --- /dev/null +++ b/generate-docs.lua @@ -0,0 +1,39 @@ +local markdown = require("tools/markdown") +local log = require("src/log") + +if not fs.exists("docs/references") then + fs.makeDir("docs/references") +end + +local function processFile(inputFile) + local parsed = markdown.parseFile(inputFile) + local md = markdown.makeMarkdown(parsed) + + local relativePath = inputFile:match("Basalt2/src/(.+)") + if not relativePath then return end + + local outputFile = "docs/references/" .. relativePath:gsub("%.lua$", "") + + local dir = fs.getDir(outputFile) + if not fs.exists(dir) then + fs.makeDir(dir) + end + + --print(string.format("Processing: %s -> %s", inputFile, outputFile)) + + markdown.saveToFile(outputFile, md) +end + + +local function processDirectory(path) + for _, file in ipairs(fs.list(path)) do + local fullPath = fs.combine(path, file) + if fs.isDir(fullPath) then + processDirectory(fullPath) + elseif file:match("%.lua$") and not file:match("LuaLS%.lua$") then + processFile(fullPath) + end + end +end + +processDirectory("Basalt2/src") diff --git a/src/elementManager.lua b/src/elementManager.lua index 5c31e29..b35cc6a 100644 --- a/src/elementManager.lua +++ b/src/elementManager.lua @@ -10,6 +10,9 @@ local defaultPath = package.path local format = "path;/path/?.lua;/path/?/init.lua;" local main = format:gsub("path", dir) +--- This class manages elements and plugins. It loads elements and plugins from the elements and plugins directories +--- and then applies the plugins to the elements. It also provides a way to get elements and APIs. +--- @class ElementManager local ElementManager = {} ElementManager._elements = {} ElementManager._plugins = {} @@ -55,6 +58,9 @@ if fs.exists(pluginsDirectory) then end end +--- Loads an element by name. This will load the element and apply any plugins to it. +--- @param name string The name of the element to load +--- @usage ElementManager.loadElement("Button") function ElementManager.loadElement(name) if not ElementManager._elements[name].loaded then package.path = main.."rom/?" @@ -106,6 +112,9 @@ function ElementManager.loadElement(name) end end +--- Gets an element by name. If the element is not loaded, it will try to load it first. +--- @param name string The name of the element to get +--- @return table Element The element class function ElementManager.getElement(name) if not ElementManager._elements[name].loaded then ElementManager.loadElement(name) @@ -113,10 +122,15 @@ function ElementManager.getElement(name) return ElementManager._elements[name].class end +--- Gets a list of all elements +--- @return table ElementList A list of all elements function ElementManager.getElementList() return ElementManager._elements end +--- Gets an Plugin API by name +--- @param name string The name of the API to get +--- @return table API The API function ElementManager.getAPI(name) return ElementManager._APIs[name] end diff --git a/src/elements/BaseElement.lua b/src/elements/BaseElement.lua index 9749abe..222a591 100644 --- a/src/elements/BaseElement.lua +++ b/src/elements/BaseElement.lua @@ -31,6 +31,7 @@ BaseElement.defineProperty(BaseElement, "name", {default = "", type = "string"}) BaseElement.defineProperty(BaseElement, "eventCallbacks", {default = {}, type = "table"}) --- Registers an event that this class can listen to +--- @shortDescription Registers an event that this class can listen to --- @param class table The class to add the event to --- @param eventName string The name of the event to register --- @usage BaseElement.listenTo(MyClass, "mouse_click") @@ -42,16 +43,18 @@ function BaseElement.listenTo(class, eventName) end --- Creates a new BaseElement instance +--- @shortDescription Creates a new BaseElement instance --- @param props table The properties to initialize the element with --- @param basalt table The basalt instance --- @return table The newly created BaseElement instance ---- @usage local element = BaseElement.new("myId", basalt) +--- @usage local element = BaseElement.new() function BaseElement.new() local self = setmetatable({}, BaseElement):__init() return self end --- Initializes the BaseElement instance +--- @shortDescription Initializes the BaseElement instance --- @param props table The properties to initialize the element with --- @param basalt table The basalt instance --- @return table self The initialized instance @@ -76,6 +79,7 @@ function BaseElement:init(props, basalt) end --- Post initialization +--- @shortDescription Post initialization --- @return table self The BaseElement instance function BaseElement:postInit() if(self._props)then @@ -88,6 +92,7 @@ function BaseElement:postInit() end --- Checks if the element is a specific type +--- @shortDescription Checks if the element is a specific type --- @param type string The type to check for --- @return boolean Whether the element is of the specified type function BaseElement:isType(type) @@ -100,6 +105,7 @@ function BaseElement:isType(type) end --- Enables or disables event listening for a specific event +--- @shortDescription Enables or disables event listening for a specific event --- @param eventName string The name of the event to listen for --- @param enable? boolean Whether to enable or disable the event (default: true) --- @return table self The BaseElement instance @@ -123,6 +129,7 @@ function BaseElement:listenEvent(eventName, enable) end --- Registers a callback function for an event +--- @shortDescription Registers a callback function --- @param event string The event to register the callback for --- @param callback function The callback function to register --- @return table self The BaseElement instance @@ -141,6 +148,7 @@ function BaseElement:registerCallback(event, callback) end --- Triggers an event and calls all registered callbacks +--- @shortDescription Triggers an event and calls all registered callbacks --- @param event string The event to fire --- @param ... any Additional arguments to pass to the callbacks --- @return table self The BaseElement instance @@ -156,6 +164,7 @@ function BaseElement:fireEvent(event, ...) end --- Handles all events +--- @shortDescription Handles all events --- @param event string The event to handle --- @vararg any The arguments for the event --- @return boolean? handled Whether the event was handled @@ -167,6 +176,7 @@ function BaseElement:dispatchEvent(event, ...) end --- The default event handler for all events +--- @shortDescription The default event handler for all events --- @param event string The event to handle --- @vararg any The arguments for the event --- @return boolean? handled Whether the event was handled @@ -175,6 +185,7 @@ function BaseElement:handleEvent(event, ...) end --- Returns the base frame of the element +--- @shortDescription Returns the base frame of the element --- @return table BaseFrame The base frame of the element function BaseElement:getBaseFrame() if self.parent then @@ -184,6 +195,7 @@ function BaseElement:getBaseFrame() end --- Destroys the element and cleans up all references +--- @shortDescription Destroys the element and cleans up all references --- @usage element:destroy() function BaseElement:destroy() -- Remove from parent if exists @@ -206,6 +218,7 @@ function BaseElement:destroy() end --- Requests a render update for this element +--- @shortDescription Requests a render update for this element --- @usage element:updateRender() function BaseElement:updateRender() if(self.parent) then diff --git a/src/elements/BaseFrame.lua b/src/elements/BaseFrame.lua index df999bd..4f84db6 100644 --- a/src/elements/BaseFrame.lua +++ b/src/elements/BaseFrame.lua @@ -2,11 +2,14 @@ local elementManager = require("elementManager") local Container = elementManager.getElement("Container") local Render = require("render") +--- This is the base frame class. It is the root element of all elements and the only element without a parent. ---@class BaseFrame : Container +---@field _render Render The render object +---@field _renderUpdate boolean Whether the render object needs to be updated local BaseFrame = setmetatable({}, Container) BaseFrame.__index = BaseFrame ----@property text term term nil text +---@property text term nil The terminal object to render to BaseFrame.defineProperty(BaseFrame, "term", {default = nil, type = "table", setter = function(self, value) if value == nil or value.setCursorPos == nil then return value @@ -19,6 +22,10 @@ BaseFrame.defineProperty(BaseFrame, "term", {default = nil, type = "table", sett return value end}) +--- Creates a new Frame instance +--- @shortDescription Creates a new Frame instance +--- @return BaseFrame object The newly created Frame instance +--- @usage local element = BaseFrame.new() function BaseFrame.new() local self = setmetatable({}, BaseFrame):__init() self.set("term", term.current()) @@ -26,32 +33,73 @@ function BaseFrame.new() return self end +--- Initializes the Frame instance +--- @shortDescription Initializes the Frame instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return table self The initialized instance function BaseFrame:init(props, basalt) Container.init(self, props, basalt) self.set("type", "BaseFrame") + return self end +--- Renders a multiBlit to the render Object +--- @shortDescription Renders a multiBlit to the render Object +--- @param x number The x position to render to +--- @param y number The y position to render to +--- @param width number The width of the text +--- @param height number The height of the text +--- @param text string The text to render +--- @param fg string The foreground color +--- @param bg string The background color function BaseFrame:multiBlit(x, y, width, height, text, fg, bg) self._render:multiBlit(x, y, width, height, text, fg, bg) end +--- Renders a text with a foreground color to the render Object +--- @shortDescription Renders a text with a foreground color to the render Object +--- @param x number The x position to render to +--- @param y number The y position to render to +--- @param text string The text to render +--- @param fg colors The foreground color function BaseFrame:textFg(x, y, text, fg) self._render:textFg(x, y, text, fg) end +--- Renders a text with a background color to the render Object +--- @shortDescription Renders a text with a background color to the render Object +--- @param x number The x position to render to +--- @param y number The y position to render to +--- @param text string The text to render +--- @param bg colors The background color function BaseFrame:textBg(x, y, text, bg) self._render:textBg(x, y, text, bg) end +--- Renders a text with a foreground and background color to the render Object +--- @shortDescription Renders a text with a foreground and background color to the render Object +--- @param x number The x position to render to +--- @param y number The y position to render to +--- @param text string The text to render +--- @param fg string The foreground color +--- @param bg string The background color function BaseFrame:blit(x, y, text, fg, bg) self._render:blit(x, y, text, fg, bg) end +--- Sets the cursor position +--- @shortDescription Sets the cursor position +--- @param x number The x position to set the cursor to +--- @param y number The y position to set the cursor to +--- @param blink boolean Whether the cursor should blink function BaseFrame:setCursor(x, y, blink) local term = self.get("term") self._render:setCursor(x, y, blink) end +--- Renders the Frame +--- @shortDescription Renders the Frame function BaseFrame:render() if(self._renderUpdate) then if self._render ~= nil then diff --git a/src/elements/Button.lua b/src/elements/Button.lua index 1ca18aa..3bdca1c 100644 --- a/src/elements/Button.lua +++ b/src/elements/Button.lua @@ -2,6 +2,7 @@ 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 @@ -14,6 +15,7 @@ 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() local self = setmetatable({}, Button):__init() @@ -24,6 +26,7 @@ function Button.new() 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) @@ -32,6 +35,7 @@ function Button:init(props, basalt) 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 3e9d984..c7201a7 100644 --- a/src/elements/Checkbox.lua +++ b/src/elements/Checkbox.lua @@ -1,5 +1,6 @@ local VisualElement = require("elements/VisualElement") +--- This is the checkbox class. It is a visual element that can be checked. ---@class Checkbox : VisualElement local Checkbox = setmetatable({}, VisualElement) Checkbox.__index = Checkbox @@ -14,6 +15,7 @@ Checkbox.defineProperty(Checkbox, "symbol", {default = "x", type = "string"}) Checkbox.listenTo(Checkbox, "mouse_click") --- Creates a new Checkbox instance +--- @shortDescription Creates a new Checkbox instance --- @return Checkbox self The created instance function Checkbox.new() local self = setmetatable({}, Checkbox):__init() @@ -23,6 +25,7 @@ function Checkbox.new() end --- Initializes the Checkbox instance +--- @shortDescription Initializes the Checkbox instance --- @param props table The properties to initialize the element with --- @param basalt table The basalt instance function Checkbox:init(props, basalt) @@ -31,10 +34,11 @@ function Checkbox:init(props, basalt) end --- Handles mouse click events +--- @shortDescription Handles mouse click events --- @param button number The button that was clicked --- @param x number The x position of the click --- @param y number The y position of the click ---- @return boolean Whether the event was handled +--- @return boolean Clicked Whether the event was handled function Checkbox:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then self.set("checked", not self.get("checked")) @@ -45,6 +49,7 @@ function Checkbox:mouse_click(button, x, y) end --- Renders the Checkbox +--- @shortDescription Renders the Checkbox function Checkbox:render() VisualElement.render(self) diff --git a/src/elements/Container.lua b/src/elements/Container.lua index a7df34d..2d7a717 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -5,6 +5,8 @@ local split = require("libraries/utils").split local max = math.max +--- The container class. It is a visual element that can contain other elements. It is the base class for all containers, +--- like Frames, BaseFrames, and more. ---@class Container : VisualElement local Container = setmetatable({}, VisualElement) Container.__index = Container @@ -66,6 +68,7 @@ for k, _ in pairs(elementManager:getElementList()) do end --- Creates a new Container instance +--- @shortDescription Creates a new Container instance --- @return Container self The new container instance function Container.new() local self = setmetatable({}, Container):__init() @@ -73,6 +76,7 @@ function Container.new() end --- Initializes the Container instance +--- @shortDescription Initializes the Container instance --- @param props table The properties to initialize the element with --- @param basalt table The basalt instance function Container:init(props, basalt) @@ -81,6 +85,7 @@ function Container:init(props, basalt) end --- Returns whether a child is visible +--- @shortDescription Returns whether a child is visible --- @param child table The child to check --- @return boolean boolean the child is visible function Container:isChildVisible(child) @@ -95,6 +100,7 @@ function Container:isChildVisible(child) end --- Adds a child to the container +--- @shortDescription Adds a child to the container --- @param child table The child to add --- @return Container self The container instance function Container:addChild(child) @@ -138,6 +144,7 @@ local function sortAndFilterChildren(self, children) end --- Clears the container +--- @shortDescription Clears the container --- @return Container self The container instance function Container:clear() self.set("children", {}) @@ -150,6 +157,7 @@ function Container:clear() end --- Sorts the children of the container +--- @shortDescription Sorts the children of the container --- @return Container self The container instance function Container:sortChildren() self.set("visibleChildren", sortAndFilterChildren(self, self._values.children)) @@ -158,6 +166,7 @@ function Container:sortChildren() end --- Sorts the children events of the container +--- @shortDescription Sorts the children events of the container --- @param eventName string The event name to sort --- @return Container self The container instance function Container:sortChildrenEvents(eventName) @@ -169,6 +178,7 @@ function Container:sortChildrenEvents(eventName) end --- Registers the children events of the container +--- @shortDescription Registers the children events of the container --- @param child table The child to register events for --- @return Container self The container instance function Container:registerChildrenEvents(child) @@ -180,6 +190,7 @@ function Container:registerChildrenEvents(child) end --- Registers the children events of the container +--- @shortDescription Registers the children events of the container --- @param child table The child to register events for --- @param eventName string The event name to register --- @return Container self The container instance @@ -206,6 +217,7 @@ function Container:registerChildEvent(child, eventName) end --- Unregisters the children events of the container +--- @shortDescription Unregisters the children events of the container --- @param child table The child to unregister events for --- @return Container self The container instance function Container:removeChildrenEvents(child) @@ -217,6 +229,7 @@ function Container:removeChildrenEvents(child) end --- Unregisters the children events of the container +--- @shortDescription Unregisters the children events of the container --- @param child table The child to unregister events for --- @param eventName string The event name to unregister --- @return Container self The container instance @@ -243,6 +256,7 @@ function Container:unregisterChildEvent(child, eventName) end --- Removes a child from the container +--- @shortDescription Removes a child from the container --- @param child table The child to remove --- @return Container self The container instance function Container:removeChild(child) @@ -258,6 +272,7 @@ function Container:removeChild(child) end --- Removes a child from the container +--- @shortDescription Removes a child from the container --- @param path string The path to the child to remove --- @return Container? self The container instance function Container:getChild(path) @@ -303,9 +318,10 @@ local function callChildrenEvents(self, visibleOnly, event, ...) end --- Default handler for events +--- @shortDescription Default handler for events --- @param event string The event to handle --- @vararg any The event arguments ---- @return boolean Whether the event was handled +--- @return boolean handled Whether the event was handled function Container:handleEvent(event, ...) VisualElement.handleEvent(self, event, ...) local args = convertMousePosition(self, event, ...) @@ -313,10 +329,11 @@ function Container:handleEvent(event, ...) end --- Handles mouse click events +--- @shortDescription Handles mouse click events --- @param button number The button that was clicked --- @param x number The x position of the click --- @param y number The y position of the click ---- @return boolean Whether the event was handled +--- @return boolean handled Whether the event was handled function Container:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local args = convertMousePosition(self, "mouse_click", button, x, y) @@ -332,10 +349,11 @@ function Container:mouse_click(button, x, y) end --- Handles mouse up events +--- @shortDescription Handles mouse up events --- @param button number The button that was clicked --- @param x number The x position of the click --- @param y number The y position of the click ---- @return boolean Whether the event was handled +--- @return boolean handled Whether the event was handled function Container:mouse_up(button, x, y) if VisualElement.mouse_up(self, button, x, y) then local args = convertMousePosition(self, "mouse_up", button, x, y) @@ -348,8 +366,9 @@ function Container:mouse_up(button, x, y) end --- Handles key events +--- @shortDescription Handles key events --- @param key number The key that was pressed ---- @return boolean Whether the event was handled +--- @return boolean handled Whether the event was handled function Container:key(key) if self.get("focusedChild") then return self.get("focusedChild"):dispatchEvent("key", key) @@ -358,8 +377,9 @@ function Container:key(key) end --- Handles char events +--- @shortDescription Handles char events --- @param char string The character that was pressed ---- @return boolean Whether the event was handled +--- @return boolean handled Whether the event was handled function Container:char(char) if self.get("focusedChild") then return self.get("focusedChild"):dispatchEvent("char", char) @@ -368,8 +388,9 @@ function Container:char(char) end --- Handles key up events +--- @shortDescription Handles key up events --- @param key number The key that was released ---- @return boolean Whether the event was handled +--- @return boolean handled Whether the event was handled function Container:key_up(key) if self.get("focusedChild") then return self.get("focusedChild"):dispatchEvent("key_up", key) @@ -378,6 +399,7 @@ function Container:key_up(key) end --- Draws multiple lines of text, fg and bg strings, it is usually used in the render loop +--- @shortDescription Draws multiple lines of text, fg and bg strings --- @param x number The x position to draw the text --- @param y number The y position to draw the text --- @param width number The width of the text @@ -399,6 +421,7 @@ function Container:multiBlit(x, y, width, height, text, fg, bg) end --- Draws a line of text and fg as color, it is usually used in the render loop +--- @shortDescription Draws a line of text and fg as color --- @param x number The x position to draw the text --- @param y number The y position to draw the text --- @param text string The text to draw @@ -418,6 +441,7 @@ function Container:textFg(x, y, text, fg) end --- Draws a line of text and fg and bg as colors, it is usually used in the render loop +--- @shortDescription Draws a line of text and fg and bg as colors --- @param x number The x position to draw the text --- @param y number The y position to draw the text --- @param text string The text to draw @@ -445,6 +469,7 @@ function Container:blit(x, y, text, fg, bg) end --- Renders the container +--- @shortDescription Renders the container function Container:render() VisualElement.render(self) if not self.get("childrenSorted")then @@ -464,6 +489,9 @@ function Container:render() end end +--- Destroys the container and its children +--- @shortDescription Destroys the container and its children +--- @return Container self The container instance function Container:destroy() for _, child in ipairs(self._values.children) do child:destroy() diff --git a/src/elements/Dropdown.lua b/src/elements/Dropdown.lua index c041256..10bcad8 100644 --- a/src/elements/Dropdown.lua +++ b/src/elements/Dropdown.lua @@ -2,15 +2,24 @@ local VisualElement = require("elements/VisualElement") local List = require("elements/List") local tHex = require("libraries/colorHex") +--- This is the dropdown class. It is a visual element that can show a list of selectable items in a dropdown menu. ---@class Dropdown : List local Dropdown = setmetatable({}, List) Dropdown.__index = Dropdown +---@property isOpen boolean false Whether the dropdown menu is currently open Dropdown.defineProperty(Dropdown, "isOpen", {default = false, type = "boolean", canTriggerRender = true}) +---@property dropdownHeight number 5 Maximum height of the dropdown menu when open Dropdown.defineProperty(Dropdown, "dropdownHeight", {default = 5, type = "number"}) +---@property selectedText string "" The text to show when no item is selected Dropdown.defineProperty(Dropdown, "selectedText", {default = "", type = "string"}) -Dropdown.defineProperty(Dropdown, "dropSymbol", {default = "\31", type = "string"}) -- ▼ Symbol +---@property dropSymbol string "\31" The symbol to show for dropdown indication +Dropdown.defineProperty(Dropdown, "dropSymbol", {default = "\31", type = "string"}) +--- Creates a new Dropdown instance +--- @shortDescription Creates a new Dropdown instance +--- @return Dropdown self The newly created Dropdown instance +--- @usage local dropdown = Dropdown.new() function Dropdown.new() local self = setmetatable({}, Dropdown):__init() self.set("width", 16) @@ -19,17 +28,29 @@ function Dropdown.new() return self end +--- Initializes the Dropdown instance +--- @shortDescription Initializes the Dropdown instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Dropdown self The initialized instance function Dropdown:init(props, basalt) List.init(self, props, basalt) self.set("type", "Dropdown") + return self end +--- Handles mouse click events +--- @shortDescription Handles mouse click events +--- @param button number The button that was clicked +--- @param x number The x position of the click +--- @param y number The y position of the click +--- @return boolean handled Whether the event was handled function Dropdown:mouse_click(button, x, y) if not VisualElement.mouse_click(self, button, x, y) then return false end - + local relX, relY = self:getRelativePosition(x, y) - - if relY == 1 then -- Klick auf Header + + if relY == 1 then self.set("isOpen", not self.get("isOpen")) if not self.get("isOpen") then self.set("height", 1) @@ -38,7 +59,6 @@ function Dropdown:mouse_click(button, x, y) end return true elseif self.get("isOpen") and relY > 1 then - -- Offset für die Liste korrigieren (relY - 1 wegen Header) local index = relY - 1 + self.get("offset") local items = self.get("items") @@ -47,7 +67,7 @@ function Dropdown:mouse_click(button, x, y) if type(item) == "table" and item.separator then return false end - + self.set("selectedIndex", index) self.set("isOpen", false) self.set("height", 1) @@ -63,30 +83,29 @@ function Dropdown:mouse_click(button, x, y) return false end +--- Renders the Dropdown +--- @shortDescription Renders the Dropdown function Dropdown:render() VisualElement.render(self) - - -- Header rendern + local text = self.get("selectedText") if #text == 0 and self.get("selectedIndex") > 0 then local item = self.get("items")[self.get("selectedIndex")] text = type(item) == "table" and item.text or tostring(item) end - + self:blit(1, 1, text .. string.rep(" ", self.get("width") - #text - 1) .. (self.get("isOpen") and "\31" or "\17"), string.rep(tHex[self.get("foreground")], self.get("width")), string.rep(tHex[self.get("background")], self.get("width"))) - -- Items nur rendern wenn offen if self.get("isOpen") then local items = self.get("items") local offset = self.get("offset") local selected = self.get("selectedIndex") local width = self.get("width") - -- Liste ab Zeile 2 rendern (unterhalb des Headers) for i = 2, self.get("height") do - local itemIndex = i - 1 + offset -- -1 wegen Header + local itemIndex = i - 1 + offset local item = items[itemIndex] if item then @@ -101,7 +120,7 @@ function Dropdown:render() else local itemText = type(item) == "table" and item.text or tostring(item) local isSelected = itemIndex == selected - + local bg = isSelected and (item.selectedBackground or self.get("selectedColor")) or (item.background or self.get("background")) diff --git a/src/elements/Frame.lua b/src/elements/Frame.lua index f8664b5..829c3b8 100644 --- a/src/elements/Frame.lua +++ b/src/elements/Frame.lua @@ -1,13 +1,18 @@ local elementManager = require("elementManager") local Container = elementManager.getElement("Container") +--- This is the frame class. It serves as a grouping container for other elements. ---@class Frame : Container local Frame = setmetatable({}, Container) Frame.__index = Frame +---@event onResize {width number, height number} Fired when the frame is resized +Frame.listenTo(Frame, "resize") + --- Creates a new Frame instance ---- @return Frame object The newly created Frame instance ---- @usage local element = Frame.new("myId", basalt) +--- @shortDescription Creates a new Frame instance +--- @return Frame self The newly created Frame instance +--- @usage local frame = Frame.new() function Frame.new() local self = setmetatable({}, Frame):__init() self.set("width", 12) @@ -18,9 +23,14 @@ function Frame.new() end --- Initializes the Frame instance +--- @shortDescription Initializes the Frame instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Frame self The initialized instance function Frame:init(props, basalt) Container.init(self, props, basalt) self.set("type", "Frame") + return self end return Frame \ No newline at end of file diff --git a/src/elements/Input.lua b/src/elements/Input.lua index 4954699..3204376 100644 --- a/src/elements/Input.lua +++ b/src/elements/Input.lua @@ -1,24 +1,27 @@ local VisualElement = require("elements/VisualElement") local tHex = require("libraries/colorHex") +--- This is the input class. It provides a text input field that can handle user input with various features like +--- cursor movement, text manipulation, placeholder text, and input validation. ---@class Input : VisualElement local Input = setmetatable({}, VisualElement) Input.__index = Input ----@property text string Input - text to be displayed +---@property text string - The current text content of the input Input.defineProperty(Input, "text", {default = "", type = "string", canTriggerRender = true}) - ----@property cursorPos number Input - current cursor position +---@property cursorPos number 1 The current cursor position in the text Input.defineProperty(Input, "cursorPos", {default = 1, type = "number"}) - ----@property viewOffset number Input - offset of view +---@property viewOffset number 0 The horizontal scroll offset for viewing long text Input.defineProperty(Input, "viewOffset", {default = 0, type = "number", canTriggerRender = true}) - --- Neue Properties +---@property maxLength number? nil Maximum length of input text (optional) Input.defineProperty(Input, "maxLength", {default = nil, type = "number"}) -Input.defineProperty(Input, "placeholder", {default = "asd", type = "string"}) +---@property placeholder string ... Text to display when input is empty +Input.defineProperty(Input, "placeholder", {default = "...", type = "string"}) +---@property placeholderColor color gray Color of the placeholder text Input.defineProperty(Input, "placeholderColor", {default = colors.gray, type = "number"}) +---@property focusedColor color blue Background color when input is focused Input.defineProperty(Input, "focusedColor", {default = colors.blue, type = "number"}) +---@property pattern string? nil Regular expression pattern for input validation Input.defineProperty(Input, "pattern", {default = nil, type = "string"}) Input.listenTo(Input, "mouse_click") @@ -26,6 +29,7 @@ Input.listenTo(Input, "key") Input.listenTo(Input, "char") --- Creates a new Input instance +--- @shortDescription Creates a new Input instance --- @return Input object The newly created Input instance --- @usage local element = Input.new("myId", basalt) function Input.new() @@ -35,13 +39,23 @@ function Input.new() return self end +--- Initializes the Input instance +--- @shortDescription Initializes the Input instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Input self The initialized instance function Input:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Input") + return self end +--- Handles char events +--- @shortDescription Handles char events +--- @param char string The character that was typed +--- @return boolean handled Whether the event was handled function Input:char(char) - if not self.get("focused") then return end + if not self.get("focused") then return false end local text = self.get("text") local pos = self.get("cursorPos") local maxLength = self.get("maxLength") @@ -54,10 +68,15 @@ function Input:char(char) self.set("cursorPos", pos + 1) self:updateRender() self:updateViewport() + return true end +--- Handles key events +--- @shortDescription Handles key events +--- @param key number The key that was pressed +--- @return boolean handled Whether the event was handled function Input:key(key) - if not self.get("focused") then return end + if not self.get("focused") then return false end local pos = self.get("cursorPos") local text = self.get("text") local viewOffset = self.get("viewOffset") @@ -88,18 +107,29 @@ function Input:key(key) local relativePos = self.get("cursorPos") - self.get("viewOffset") self:setCursor(relativePos, 1, true) + return true end +--- Handles focus events +--- @shortDescription Handles focus events function Input:focus() VisualElement.focus(self) self:updateRender() end +--- Handles blur events +--- @shortDescription Handles blur events function Input:blur() VisualElement.blur(self) self:updateRender() end +--- Handles mouse click events +--- @shortDescription Handles mouse click events +--- @param button number The button that was clicked +--- @param x number The x position of the click +--- @param y number The y position of the click +--- @return boolean handled Whether the event was handled function Input:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) @@ -110,6 +140,8 @@ function Input:mouse_click(button, x, y) end end +--- Updates the input's viewport +--- @shortDescription Updates the input's viewport function Input:updateViewport() local width = self.get("width") local cursorPos = self.get("cursorPos") @@ -128,6 +160,8 @@ function Input:updateViewport() end end +--- Renders the input element +--- @shortDescription Renders the input element function Input:render() local text = self.get("text") local viewOffset = self.get("viewOffset") diff --git a/src/elements/Label.lua b/src/elements/Label.lua index e68fca8..63d6a15 100644 --- a/src/elements/Label.lua +++ b/src/elements/Label.lua @@ -1,11 +1,13 @@ local elementManager = require("elementManager") local VisualElement = elementManager.getElement("VisualElement") +--- This is the label class. It provides a simple text display element that automatically +--- resizes its width based on the text content. ---@class Label : VisualElement local Label = setmetatable({}, VisualElement) Label.__index = Label ----@property text string Label Label text to be displayed +---@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) if(type(value)=="function")then value = value() end self.set("width", #value) @@ -13,8 +15,9 @@ Label.defineProperty(Label, "text", {default = "Label", type = "string", setter end}) --- Creates a new Label instance ---- @return Label object The newly created Label instance ---- @usage local element = Label.new("myId", basalt) +--- @shortDescription Creates a new Label instance +--- @return Label self The newly created Label instance +--- @usage local label = Label.new() function Label.new() local self = setmetatable({}, Label):__init() self.set("z", 3) @@ -23,11 +26,19 @@ function Label.new() return self end +--- Initializes the Label instance +--- @shortDescription Initializes the Label instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Label self The initialized instance function Label:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Label") + return self end +--- Renders the Label +--- @shortDescription Renders the Label by drawing its text content function Label:render() VisualElement.render(self) local text = self.get("text") diff --git a/src/elements/List.lua b/src/elements/List.lua index 648ee45..7e3823b 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -1,23 +1,30 @@ local VisualElement = require("elements/VisualElement") +--- This is the list class. It provides a scrollable list of selectable items with support for +--- custom item rendering, separators, and selection handling. ---@class List : VisualElement local List = setmetatable({}, VisualElement) List.__index = List ----@property items table List of items to display +---@property items table {} List of items to display. Items can be strings or tables with properties List.defineProperty(List, "items", {default = {}, type = "table", canTriggerRender = true}) ----@property selectedIndex number Currently selected item index +---@property selectedIndex number 0 Index of the currently selected item (0 means no selection) List.defineProperty(List, "selectedIndex", {default = 0, type = "number", canTriggerRender = true}) ----@property selectable boolean Whether items can be selected +---@property selectable boolean true Whether items in the list can be selected List.defineProperty(List, "selectable", {default = true, type = "boolean"}) ----@property offset number Scrolling offset +---@property offset number 0 Current scroll offset for viewing long lists List.defineProperty(List, "offset", {default = 0, type = "number", canTriggerRender = true}) ----@property selectedColor color Color for selected item +---@property selectedColor color blue Background color for the selected item List.defineProperty(List, "selectedColor", {default = colors.blue, type = "number"}) +---@event onSelect {index number, item any} Fired when an item is selected List.listenTo(List, "mouse_click") List.listenTo(List, "mouse_scroll") +--- Creates a new List instance +--- @shortDescription Creates a new List instance +--- @return List self The newly created List instance +--- @usage local list = List.new() function List.new() local self = setmetatable({}, List):__init() self.set("width", 16) @@ -26,11 +33,22 @@ function List.new() return self end +--- Initializes the List instance +--- @shortDescription Initializes the List instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return List self The initialized instance function List:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "List") end +--- Adds an item to the list +--- @shortDescription Adds an item to the list +--- @param text string|table The item to add (string or item table) +--- @return List self The List instance +--- @usage list:addItem("New Item") +--- @usage list:addItem({text="Item", callback=function() end}) function List:addItem(text) local items = self.get("items") table.insert(items, text) @@ -38,6 +56,11 @@ function List:addItem(text) return self end +--- Removes an item from the list +--- @shortDescription Removes an item from the list +--- @param index number The index of the item to remove +--- @return List self The List instance +--- @usage list:removeItem(1) function List:removeItem(index) local items = self.get("items") table.remove(items, index) @@ -45,6 +68,10 @@ function List:removeItem(index) return self end +--- Clears all items from the list +--- @shortDescription Clears all items from the list +--- @return List self The List instance +--- @usage list:clear() function List:clear() self.set("items", {}) self.set("selectedIndex", 0) @@ -52,6 +79,12 @@ function List:clear() return self end +--- Handles mouse click events +--- @shortDescription Handles mouse click events +--- @param button number The mouse button that was clicked +--- @param x number The x-coordinate of the click +--- @param y number The y-coordinate of the click +--- @return boolean Whether the event was handled function List:mouse_click(button, x, y) if button == 1 and self:isInBounds(x, y) and self.get("selectable") then local _, index = self:getRelativePosition(x, y) @@ -75,6 +108,12 @@ function List:mouse_click(button, x, y) return false end +--- Handles mouse scroll events +--- @shortDescription Handles mouse scroll events +--- @param direction number The direction of the scroll (1 for down, -1 for up) +--- @param x number The x-coordinate of the scroll +--- @param y number The y-coordinate of the scroll +--- @return boolean Whether the event was handled function List:mouse_scroll(direction, x, y) if self:isInBounds(x, y) then local offset = self.get("offset") @@ -84,13 +123,21 @@ function List:mouse_scroll(direction, x, y) self.set("offset", offset) return true end + return false end +--- Registers a callback for the select event +--- @shortDescription Registers a callback for the select event +--- @param callback function The callback function to register +--- @return List self The List instance +--- @usage list:onSelect(function(index, item) print("Selected item:", index, item) end) function List:onSelect(callback) self:registerCallback("select", callback) return self end +--- Renders the list +--- @shortDescription Renders the list function List:render() VisualElement.render(self) diff --git a/src/elements/Menu.lua b/src/elements/Menu.lua index c59dc5d..d6b3d08 100644 --- a/src/elements/Menu.lua +++ b/src/elements/Menu.lua @@ -2,12 +2,19 @@ local VisualElement = require("elements/VisualElement") local List = require("elements/List") local tHex = require("libraries/colorHex") +--- This is the menu class. It provides a horizontal menu bar with selectable items and separators. +--- Menu items are displayed in a single row and can have custom colors and callbacks. ---@class Menu : List local Menu = setmetatable({}, List) Menu.__index = Menu +---@property separatorColor color gray The color used for separator items in the menu Menu.defineProperty(Menu, "separatorColor", {default = colors.gray, type = "number"}) +--- Creates a new Menu instance +--- @shortDescription Creates a new Menu instance +--- @return Menu self The newly created Menu instance +--- @usage local menu = Menu.new() function Menu.new() local self = setmetatable({}, Menu):__init() self.set("width", 30) @@ -16,12 +23,22 @@ function Menu.new() return self end +--- Initializes the Menu instance +--- @shortDescription Initializes the Menu instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Menu self The initialized instance function Menu:init(props, basalt) List.init(self, props, basalt) self.set("type", "Menu") return self end +--- Sets the menu items +--- @shortDescription Sets the menu items and calculates total width +--- @param items table[] List of items with {text, separator, callback, foreground, background} properties +--- @return Menu self The Menu instance +--- @usage menu:setItems({{text="File"}, {separator=true}, {text="Edit"}}) function Menu:setItems(items) local listItems = {} local totalWidth = 0 @@ -40,6 +57,8 @@ function Menu:setItems(items) return List.setItems(self, listItems) end +--- Renders the menu +--- @shortDescription Renders the menu horizontally with proper spacing and colors function Menu:render() VisualElement.render(self) local currentX = 1 @@ -63,6 +82,12 @@ function Menu:render() end end +--- Handles mouse click events +--- @shortDescription Handles mouse click events and item selection +--- @param button number The button that was clicked +--- @param x number The x position of the click +--- @param y number The y position of the click +--- @return boolean Whether the event was handled function Menu:mouse_click(button, x, y) if not VisualElement.mouse_click(self, button, x, y) then return false end if(self.get("selectable") == false) then return false end diff --git a/src/elements/ProgressBar.lua b/src/elements/ProgressBar.lua index 6ded9c6..1d99387 100644 --- a/src/elements/ProgressBar.lua +++ b/src/elements/ProgressBar.lua @@ -1,16 +1,22 @@ local VisualElement = require("elements/VisualElement") +--- This is the progress bar class. It provides a visual representation of progress +--- with optional percentage display and customizable colors. ---@class ProgressBar : VisualElement local ProgressBar = setmetatable({}, VisualElement) ProgressBar.__index = ProgressBar ----@property progress number Current progress (0-100) +---@property progress number 0 Current progress value (0-100) ProgressBar.defineProperty(ProgressBar, "progress", {default = 0, type = "number", canTriggerRender = true}) ----@property showPercentage boolean Show percentage text +---@property showPercentage boolean false Whether to show the percentage text in the center ProgressBar.defineProperty(ProgressBar, "showPercentage", {default = false, type = "boolean"}) ----@property progressColor color Progress bar color +---@property progressColor color lime The color used for the filled portion of the progress bar ProgressBar.defineProperty(ProgressBar, "progressColor", {default = colors.lime, type = "number"}) +--- Creates a new ProgressBar instance +--- @shortDescription Creates a new ProgressBar instance +--- @return ProgressBar self The newly created ProgressBar instance +--- @usage local progressBar = ProgressBar.new() function ProgressBar.new() local self = setmetatable({}, ProgressBar):__init() self.set("width", 10) @@ -18,11 +24,18 @@ function ProgressBar.new() return self end +--- Initializes the ProgressBar instance +--- @shortDescription Initializes the ProgressBar instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return ProgressBar self The initialized instance function ProgressBar:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "ProgressBar") end +--- Renders the ProgressBar +--- @shortDescription Renders the progress bar with filled portion and optional percentage text function ProgressBar:render() VisualElement.render(self) local width = self.get("width") diff --git a/src/elements/Slider.lua b/src/elements/Slider.lua index dd6c925..6efd676 100644 --- a/src/elements/Slider.lua +++ b/src/elements/Slider.lua @@ -1,24 +1,31 @@ local VisualElement = require("elements/VisualElement") +--- This is the slider class. It provides a draggable slider control that can be either horizontal or vertical, +--- with customizable colors and value ranges. ---@class Slider : VisualElement local Slider = setmetatable({}, VisualElement) Slider.__index = Slider ----@property step number 1 Current step position (1 to width/height) +---@property step number 1 Current position of the slider handle (1 to width/height) Slider.defineProperty(Slider, "step", {default = 1, type = "number", canTriggerRender = true}) ----@property max number 100 Maximum value for value conversion +---@property max number 100 Maximum value for value conversion (maps slider position to this range) Slider.defineProperty(Slider, "max", {default = 100, type = "number"}) ----@property horizontal boolean true Whether the slider is horizontal +---@property horizontal boolean true Whether the slider is horizontal (false for vertical) Slider.defineProperty(Slider, "horizontal", {default = true, type = "boolean", canTriggerRender = true}) ----@property barColor color color Colors for the slider bar +---@property barColor color gray Color of the slider track Slider.defineProperty(Slider, "barColor", {default = colors.gray, type = "number", canTriggerRender = true}) ----@property sliderColor color The color of the slider handle +---@property sliderColor color blue Color of the slider handle Slider.defineProperty(Slider, "sliderColor", {default = colors.blue, type = "number", canTriggerRender = true}) +---@event onChange {value number} Fired when the slider value changes Slider.listenTo(Slider, "mouse_click") Slider.listenTo(Slider, "mouse_drag") Slider.listenTo(Slider, "mouse_up") +--- Creates a new Slider instance +--- @shortDescription Creates a new Slider instance +--- @return Slider self The newly created Slider instance +--- @usage local slider = Slider.new() function Slider.new() local self = setmetatable({}, Slider):__init() self.set("width", 8) @@ -27,11 +34,20 @@ function Slider.new() return self end +--- Initializes the Slider instance +--- @shortDescription Initializes the Slider instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Slider self The initialized instance function Slider:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Slider") end +--- Gets the current value of the slider +--- @shortDescription Gets the current value mapped to the max range +--- @return number value The current value (0 to max) +--- @usage local value = slider:getValue() function Slider:getValue() local step = self.get("step") local max = self.get("max") @@ -39,6 +55,12 @@ function Slider:getValue() return math.floor((step - 1) * (max / (maxSteps - 1))) end +--- Handles mouse click events +--- @shortDescription Updates slider position on mouse click +--- @param button number The mouse button that was clicked +--- @param x number The x position of the click +--- @param y number The y position of the click +--- @return boolean handled Whether the event was handled function Slider:mouse_click(button, x, y) if button == 1 and self:isInBounds(x, y) then local relX, relY = self:getRelativePosition(x, y) @@ -62,6 +84,8 @@ function Slider:mouse_scroll(direction, x, y) end end +--- Renders the slider +--- @shortDescription Renders the slider with track and handle function Slider:render() VisualElement.render(self) local width = self.get("width") diff --git a/src/elements/Table.lua b/src/elements/Table.lua index 7c8c44b..82cf951 100644 --- a/src/elements/Table.lua +++ b/src/elements/Table.lua @@ -1,23 +1,38 @@ local VisualElement = require("elements/VisualElement") local tHex = require("libraries/colorHex") +--- This is the table class. It provides a sortable data grid with customizable columns, +--- row selection, and scrolling capabilities. ---@class Table : VisualElement local Table = setmetatable({}, VisualElement) Table.__index = Table +---@property columns table {} List of column definitions with {name, width} properties Table.defineProperty(Table, "columns", {default = {}, type = "table"}) +---@property data table {} The table data as array of row arrays Table.defineProperty(Table, "data", {default = {}, type = "table", canTriggerRender = true}) +---@property selectedRow number? nil Currently selected row index Table.defineProperty(Table, "selectedRow", {default = nil, type = "number", canTriggerRender = true}) +---@property headerColor color blue Color of the column headers Table.defineProperty(Table, "headerColor", {default = colors.blue, type = "number"}) +---@property selectedColor color lightBlue Background color of selected row Table.defineProperty(Table, "selectedColor", {default = colors.lightBlue, type = "number"}) +---@property gridColor color gray Color of grid lines Table.defineProperty(Table, "gridColor", {default = colors.gray, type = "number"}) +---@property sortColumn number? nil Currently sorted column index Table.defineProperty(Table, "sortColumn", {default = nil, type = "number"}) +---@property sortDirection string "asc" Sort direction ("asc" or "desc") Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string"}) +---@property scrollOffset number 0 Current scroll position Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true}) Table.listenTo(Table, "mouse_click") Table.listenTo(Table, "mouse_scroll") +--- Creates a new Table instance +--- @shortDescription Creates a new Table instance +--- @return Table self The newly created Table instance +--- @usage local table = Table.new() function Table.new() local self = setmetatable({}, Table):__init() self.set("width", 30) @@ -26,24 +41,43 @@ function Table.new() return self end +--- Initializes the Table instance +--- @shortDescription Initializes the Table instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Table self The initialized instance function Table:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Table") return self end +--- Sets the table columns +--- @shortDescription Sets the table columns configuration +--- @param columns table[] Array of column definitions {name="Name", width=10} +--- @return Table self The Table instance +--- @usage table:setColumns({{name="ID", width=4}, {name="Name", width=10}}) function Table:setColumns(columns) -- Columns Format: {{name="ID", width=4}, {name="Name", width=10}} self.set("columns", columns) return self end +--- Sets the table data +--- @shortDescription Sets the table data +--- @param data table[] Array of row data arrays +--- @return Table self The Table instance +--- @usage table:setData({{"1", "Item One"}, {"2", "Item Two"}}) function Table:setData(data) -- Data Format: {{"1", "Item One"}, {"2", "Item Two"}} self.set("data", data) return self end +--- Sorts the table data by column +--- @shortDescription Sorts the table data by the specified column +--- @param columnIndex number The index of the column to sort by +--- @return Table self The Table instance function Table:sortData(columnIndex) local data = self.get("data") local direction = self.get("sortDirection") @@ -60,6 +94,12 @@ function Table:sortData(columnIndex) return self end +--- Handles mouse click events +--- @shortDescription Handles header clicks for sorting and row selection +--- @param button number The button that was clicked +--- @param x number The x position of the click +--- @param y number The y position of the click +--- @return boolean handled Whether the event was handled function Table:mouse_click(button, x, y) if not VisualElement.mouse_click(self, button, x, y) then return false end @@ -92,6 +132,12 @@ function Table:mouse_click(button, x, y) return true end +--- Handles mouse scroll events +--- @shortDescription Handles scrolling through the table data +--- @param direction number The scroll direction (-1 up, 1 down) +--- @param x number The x position of the scroll +--- @param y number The y position of the scroll +--- @return boolean handled Whether the event was handled function Table:mouse_scroll(direction, x, y) local data = self.get("data") local height = self.get("height") @@ -103,6 +149,8 @@ function Table:mouse_scroll(direction, x, y) return true end +--- Renders the table +--- @shortDescription Renders the table with headers, data and scrollbar function Table:render() VisualElement.render(self) diff --git a/src/elements/TextBox.lua b/src/elements/TextBox.lua new file mode 100644 index 0000000..33300e2 --- /dev/null +++ b/src/elements/TextBox.lua @@ -0,0 +1,252 @@ +local VisualElement = require("elements/VisualElement") +local tHex = require("libraries/colorHex") + +---A multi-line text editor component with cursor support and text manipulation features +---@class TextBox : VisualElement +local TextBox = setmetatable({}, VisualElement) +TextBox.__index = TextBox + +---@property lines table {} Array of text lines +TextBox.defineProperty(TextBox, "lines", {default = {""}, type = "table", canTriggerRender = true}) +---@property cursorX number 1 Cursor X position +TextBox.defineProperty(TextBox, "cursorX", {default = 1, type = "number"}) +---@property cursorY number 1 Cursor Y position (line number) +TextBox.defineProperty(TextBox, "cursorY", {default = 1, type = "number"}) +---@property scrollX number 0 Horizontal scroll offset +TextBox.defineProperty(TextBox, "scrollX", {default = 0, type = "number", canTriggerRender = true}) +---@property scrollY number 0 Vertical scroll offset +TextBox.defineProperty(TextBox, "scrollY", {default = 0, type = "number", canTriggerRender = true}) +---@property editable boolean true Whether text can be edited +TextBox.defineProperty(TextBox, "editable", {default = true, type = "boolean"}) +---@property syntaxPatterns table {} Syntax highlighting patterns +TextBox.defineProperty(TextBox, "syntaxPatterns", {default = {}, type = "table"}) + +TextBox.listenTo(TextBox, "mouse_click") +TextBox.listenTo(TextBox, "key") +TextBox.listenTo(TextBox, "char") +TextBox.listenTo(TextBox, "mouse_scroll") + +function TextBox.new() + local self = setmetatable({}, TextBox):__init() + self.set("width", 20) + self.set("height", 10) + return self +end + +function TextBox:init(props, basalt) + VisualElement.init(self, props, basalt) + self.set("type", "TextBox") + return self +end + +--- Adds a new syntax highlighting pattern +--- @param pattern string The regex pattern to match +--- @param color color The color to apply +function TextBox:addSyntaxPattern(pattern, color) + table.insert(self.get("syntaxPatterns"), {pattern = pattern, color = color}) + return self +end + +local function insertChar(self, char) + local lines = self.get("lines") + local cursorX = self.get("cursorX") + local cursorY = self.get("cursorY") + local currentLine = lines[cursorY] + lines[cursorY] = currentLine:sub(1, cursorX-1) .. char .. currentLine:sub(cursorX) + self.set("cursorX", cursorX + 1) + self:updateViewport() + self:updateRender() +end + +local function newLine(self) + local lines = self.get("lines") + local cursorX = self.get("cursorX") + local cursorY = self.get("cursorY") + local currentLine = lines[cursorY] + + local restOfLine = currentLine:sub(cursorX) + lines[cursorY] = currentLine:sub(1, cursorX-1) + table.insert(lines, cursorY + 1, restOfLine) + + self.set("cursorX", 1) + self.set("cursorY", cursorY + 1) + self:updateViewport() + self:updateRender() +end + +local function backspace(self) + local lines = self.get("lines") + local cursorX = self.get("cursorX") + local cursorY = self.get("cursorY") + local currentLine = lines[cursorY] + + if cursorX > 1 then + lines[cursorY] = currentLine:sub(1, cursorX-2) .. currentLine:sub(cursorX) + self.set("cursorX", cursorX - 1) + elseif cursorY > 1 then + local previousLine = lines[cursorY-1] + self.set("cursorX", #previousLine + 1) + self.set("cursorY", cursorY - 1) + lines[cursorY-1] = previousLine .. currentLine + table.remove(lines, cursorY) + end + self:updateViewport() + self:updateRender() +end + +function TextBox:updateViewport() + local cursorX = self.get("cursorX") + local cursorY = self.get("cursorY") + local scrollX = self.get("scrollX") + local scrollY = self.get("scrollY") + local width = self.get("width") + local height = self.get("height") + + -- Horizontal scrolling + if cursorX - scrollX > width then + self.set("scrollX", cursorX - width) + elseif cursorX - scrollX < 1 then + self.set("scrollX", cursorX - 1) + end + + -- Vertical scrolling + if cursorY - scrollY > height then + self.set("scrollY", cursorY - height) + elseif cursorY - scrollY < 1 then + self.set("scrollY", cursorY - 1) + end +end + +function TextBox:char(char) + if not self.get("editable") or not self.get("focused") then return false end + insertChar(self, char) + return true +end + +function TextBox:key(key) + if not self.get("editable") or not self.get("focused") then return false end + local lines = self.get("lines") + local cursorX = self.get("cursorX") + local cursorY = self.get("cursorY") + + if key == keys.enter then + newLine(self) + elseif key == keys.backspace then + backspace(self) + elseif key == keys.left then + if cursorX > 1 then + self.set("cursorX", cursorX - 1) + elseif cursorY > 1 then + self.set("cursorY", cursorY - 1) + self.set("cursorX", #lines[cursorY-1] + 1) + end + elseif key == keys.right then + if cursorX <= #lines[cursorY] then + self.set("cursorX", cursorX + 1) + elseif cursorY < #lines then + self.set("cursorY", cursorY + 1) + self.set("cursorX", 1) + end + elseif key == keys.up and cursorY > 1 then + self.set("cursorY", cursorY - 1) + self.set("cursorX", math.min(cursorX, #lines[cursorY-1] + 1)) + elseif key == keys.down and cursorY < #lines then + self.set("cursorY", cursorY + 1) + self.set("cursorX", math.min(cursorX, #lines[cursorY+1] + 1)) + end + self:updateRender() + self:updateViewport() + return true +end + +function TextBox:mouse_scroll(direction, x, y) + if self:isInBounds(x, y) then + local scrollY = self.get("scrollY") + self.set("scrollY", math.max(0, scrollY + direction)) + self:updateRender() + return true + end + return false +end + +function TextBox:mouse_click(button, x, y) + if VisualElement.mouse_click(self, button, x, y) then + local relX, relY = self:getRelativePosition(x, y) + local scrollX = self.get("scrollX") + local scrollY = self.get("scrollY") + + local targetY = relY + scrollY + local lines = self.get("lines") + + if targetY <= #lines then + self.set("cursorY", targetY) + self.set("cursorX", math.min(relX + scrollX, #lines[targetY] + 1)) + end + return true + end + return false +end + +function TextBox:setText(text) + self.set("lines", {""}) + for line in text:gmatch("[^\n]+") do + table.insert(self.get("lines"), line) + end + return self +end + +function TextBox:getText() + return table.concat(self.get("lines"), "\n") +end + +local function applySyntaxHighlighting(self, line) + local text = line + local colors = string.rep(tHex[self.get("foreground")], #text) + local patterns = self.get("syntaxPatterns") + + for _, syntax in ipairs(patterns) do + local start = 1 + while true do + local s, e = text:find(syntax.pattern, start) + if not s then break end + colors = colors:sub(1, s-1) .. string.rep(tHex[syntax.color], e-s+1) .. colors:sub(e+1) + start = e + 1 + end + end + + return text, colors +end + +function TextBox:render() + VisualElement.render(self) + + local lines = self.get("lines") + local scrollX = self.get("scrollX") + local scrollY = self.get("scrollY") + local width = self.get("width") + local height = self.get("height") + local fg = tHex[self.get("foreground")] + local bg = tHex[self.get("background")] + + for y = 1, height do + local lineNum = y + scrollY + local line = lines[lineNum] or "" + local visibleText = line:sub(scrollX + 1, scrollX + width) + if #visibleText < width then + visibleText = visibleText .. string.rep(" ", width - #visibleText) + end + + local text, colors = applySyntaxHighlighting(self, visibleText) + self:blit(1, y, text, colors, string.rep(bg, #visibleText)) + end + + if self.get("focused") then + local relativeX = self.get("cursorX") - scrollX + local relativeY = self.get("cursorY") - scrollY + if relativeX >= 1 and relativeX <= width and relativeY >= 1 and relativeY <= height then + self:setCursor(relativeX, relativeY, true) + end + end +end + +return TextBox diff --git a/src/elements/Tree.lua b/src/elements/Tree.lua index 4d39a61..c7f066a 100644 --- a/src/elements/Tree.lua +++ b/src/elements/Tree.lua @@ -1,20 +1,32 @@ local VisualElement = require("elements/VisualElement") -local tHex = require("libraries/colorHex") +local sub = string.sub +--- This is the tree class. It provides a hierarchical view of nodes that can be expanded and collapsed, +--- with support for selection and scrolling. ---@class Tree : VisualElement local Tree = setmetatable({}, VisualElement) Tree.__index = Tree +---@property nodes table {} The tree structure containing node objects with {text, children} properties Tree.defineProperty(Tree, "nodes", {default = {}, type = "table", canTriggerRender = true}) +---@property selectedNode table? nil Currently selected node Tree.defineProperty(Tree, "selectedNode", {default = nil, type = "table", canTriggerRender = true}) +---@property expandedNodes table {} Table of nodes that are currently expanded Tree.defineProperty(Tree, "expandedNodes", {default = {}, type = "table", canTriggerRender = true}) +---@property scrollOffset number 0 Current scroll position Tree.defineProperty(Tree, "scrollOffset", {default = 0, type = "number", canTriggerRender = true}) +---@property nodeColor color white Color of unselected nodes Tree.defineProperty(Tree, "nodeColor", {default = colors.white, type = "number"}) +---@property selectedColor color lightBlue Background color of selected node Tree.defineProperty(Tree, "selectedColor", {default = colors.lightBlue, type = "number"}) Tree.listenTo(Tree, "mouse_click") Tree.listenTo(Tree, "mouse_scroll") +--- Creates a new Tree instance +--- @shortDescription Creates a new Tree instance +--- @return Tree self The newly created Tree instance +--- @usage local tree = Tree.new() function Tree.new() local self = setmetatable({}, Tree):__init() self.set("width", 30) @@ -23,12 +35,22 @@ function Tree.new() return self end +--- Initializes the Tree instance +--- @shortDescription Initializes the Tree instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Tree self The initialized instance function Tree:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Tree") return self end +--- Sets the tree nodes +--- @shortDescription Sets the tree nodes and expands the root node +--- @param nodes table[] Array of node objects +--- @return Tree self The Tree instance +--- @usage tree:setNodes({{text="Root", children={{text="Child"}}}}) function Tree:setNodes(nodes) self.set("nodes", nodes) if #nodes > 0 then @@ -37,18 +59,30 @@ function Tree:setNodes(nodes) return self end +--- Expands a node +--- @shortDescription Expands a node to show its children +--- @param node table The node to expand +--- @return Tree self The Tree instance function Tree:expandNode(node) self.get("expandedNodes")[node] = true self:updateRender() return self end +--- Collapses a node +--- @shortDescription Collapses a node to hide its children +--- @param node table The node to collapse +--- @return Tree self The Tree instance function Tree:collapseNode(node) self.get("expandedNodes")[node] = nil self:updateRender() return self end +--- Toggles a node's expanded state +--- @shortDescription Toggles between expanded and collapsed state +--- @param node table The node to toggle +--- @return Tree self The Tree instance function Tree:toggleNode(node) if self.get("expandedNodes")[node] then self:collapseNode(node) @@ -101,14 +135,14 @@ function Tree:mouse_scroll(direction) local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) local maxScroll = math.max(0, #flatNodes - self.get("height")) local newScroll = math.min(maxScroll, math.max(0, self.get("scrollOffset") + direction)) - + self.set("scrollOffset", newScroll) return true end function Tree:render() VisualElement.render(self) - + local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) local height = self.get("height") local selectedNode = self.get("selectedNode") @@ -121,8 +155,7 @@ function Tree:render() local node = nodeInfo.node local level = nodeInfo.level local indent = string.rep(" ", level) - - -- Expand/Collapse Symbol + local symbol = " " if node.children and #node.children > 0 then symbol = expandedNodes[node] and "\31" or "\16" @@ -130,15 +163,12 @@ function Tree:render() local bg = node == selectedNode and self.get("selectedColor") or self.get("background") local text = indent .. symbol .." " .. (node.text or "Node") - - self:blit(1, y, text .. string.rep(" ", self.get("width") - #text), - string.rep(tHex[self.get("nodeColor")], self.get("width")), - string.rep(tHex[bg], self.get("width"))) + text = sub(text, 1, self.get("width")) + + self:textFg(1, y, text .. string.rep(" ", self.get("width") - #text), self.get("foreground")) + else - -- Leere Zeile - self:blit(1, y, string.rep(" ", self.get("width")), - string.rep(tHex[self.get("foreground")], self.get("width")), - string.rep(tHex[self.get("background")], self.get("width"))) + self:textFg(1, y, string.rep(" ", self.get("width")), self.get("foreground"), self.get("background")) end end end diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index 36c5d1d..7a9ba68 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -2,15 +2,17 @@ local elementManager = require("elementManager") local BaseElement = elementManager.getElement("BaseElement") local tHex = require("libraries/colorHex") +--- This is the visual element class. It serves as the base class for all visual UI elements +--- and provides core functionality for positioning, sizing, colors, and rendering. ---@class VisualElement : BaseElement local VisualElement = setmetatable({}, BaseElement) VisualElement.__index = VisualElement ----@property x number 1 x position of the element +---@property x number 1 The horizontal position relative to parent VisualElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true}) ----@property y number 1 y position of the element +---@property y number 1 The vertical position relative to parent VisualElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true}) ----@property z number 1 z position of the element +---@property z number 1 The z-index for layering elements VisualElement.defineProperty(VisualElement, "z", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value) if self.parent then self.parent:sortChildren() @@ -18,19 +20,19 @@ VisualElement.defineProperty(VisualElement, "z", {default = 1, type = "number", return value end}) ----@property width number 1 width of the element +---@property width number 1 The width of the element VisualElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true}) ----@property height number 1 height of the element +---@property height number 1 The height of the element VisualElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true}) ----@property background color black background color of the element +---@property background color black The background color VisualElement.defineProperty(VisualElement, "background", {default = colors.black, type = "number", canTriggerRender = true}) ----@property foreground color white foreground color of the element +---@property foreground color white The text/foreground color VisualElement.defineProperty(VisualElement, "foreground", {default = colors.white, type = "number", canTriggerRender = true}) ----@property clicked boolean an false element is currently clicked +---@property clicked boolean false Whether the element is currently clicked VisualElement.defineProperty(VisualElement, "clicked", {default = false, type = "boolean"}) ----@property backgroundEnabled boolean true whether the background is enabled +---@property backgroundEnabled boolean true Whether to render the background VisualElement.defineProperty(VisualElement, "backgroundEnabled", {default = true, type = "boolean", canTriggerRender = true}) ----@property focused boolean false whether the element is focused +---@property focused boolean false Whether the element has input focus VisualElement.defineProperty(VisualElement, "focused", {default = false, type = "boolean", setter = function(self, value, internal) local curValue = self.get("focused") if value == curValue then return value end @@ -51,7 +53,7 @@ VisualElement.defineProperty(VisualElement, "focused", {default = false, type = return value end}) ----@property visible boolean true whether the element is visible +---@property visible boolean true Whether the element is visible VisualElement.defineProperty(VisualElement, "visible", {default = true, type = "boolean", canTriggerRender = true, setter=function(self, value) if(self.parent~=nil)then self.parent.set("childrenSorted", false) @@ -60,23 +62,22 @@ VisualElement.defineProperty(VisualElement, "visible", {default = true, type = " return value end}) ----@combinedProperty position {x y} Position of the element +---@combinedProperty position {x y} Combined x, y position VisualElement.combineProperties(VisualElement, "position", "x", "y") ----@combinedProperty size {width height} Size of the element +---@combinedProperty size {width height} Combined width, height VisualElement.combineProperties(VisualElement, "size", "width", "height") ----@combinedProperty color {foreground background} Color of the element +---@combinedProperty color {foreground background} Combined foreground, background colors VisualElement.combineProperties(VisualElement, "color", "foreground", "background") ----@event onMouseClick {button number, x number, y number} Fired when the element is clicked ----@event onMouseUp {button number, x number, y number} Fired when the mouse is released ----@event onMouseRelease {button number, x number, y number} Fired when the mouse is released ----@event onMouseDrag {button number, x number, y number} Fired when the mouse is dragged ----@event onFocus {-} Fired when the element is focused ----@event onBlur {-} Fired when the element is blurred ----@event onKey {key number, code number, isRepeat boolean} Fired when a key is pressed ----@event onKeyUp {key number, code number} Fired when a key is released ----@event onChar {char string} Fired when a key is pressed - +---@event onMouseClick {button number, x number, y number} Fired on mouse click +---@event onMouseUp {button number, x number, y number} Fired on mouse button release +---@event onMouseRelease {button number, x number, y number} Fired when mouse leaves while clicked +---@event onMouseDrag {button number, x number, y number} Fired when mouse moves while clicked +---@event onFocus {-} Fired when element receives focus +---@event onBlur {-} Fired when element loses focus +---@event onKey {key number, code number, isRepeat boolean} Fired on key press +---@event onKeyUp {key number, code number} Fired on key release +---@event onChar {char string} Fired on character input VisualElement.listenTo(VisualElement, "focus") VisualElement.listenTo(VisualElement, "blur") @@ -84,6 +85,7 @@ VisualElement.listenTo(VisualElement, "blur") local max, min = math.max, math.min --- Creates a new VisualElement instance +--- @shortDescription Creates a new visual element --- @param props table The properties to initialize the element with --- @param basalt table The basalt instance --- @return VisualElement object The newly created VisualElement instance @@ -94,11 +96,16 @@ function VisualElement.new() end --- Initializes the VisualElement instance +--- @shortDescription Initializes a new visual element with properties +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance function VisualElement:init(props, basalt) BaseElement.init(self, props, basalt) self.set("type", "VisualElement") end +--- Draws multiple characters at once with colors +--- @shortDescription Multi-character drawing with colors ---@protected function VisualElement:multiBlit(x, y, width, height, text, fg, bg) x = x + self.get("x") - 1 @@ -106,7 +113,8 @@ function VisualElement:multiBlit(x, y, width, height, text, fg, bg) self.parent:multiBlit(x, y, width, height, text, fg, bg) end ---- Draws a text character at the specified position, used in the rendering system +--- Draws text with foreground color +--- @shortDescription Draws text with foreground color --- @param x number The x position to draw --- @param y number The y position to draw --- @param text string The text char to draw @@ -117,7 +125,8 @@ function VisualElement:textFg(x, y, text, fg) self.parent:textFg(x, y, text, fg) end ---- Draws a text character with a background at the specified position, used in the rendering system +--- Draws text with background color +--- @shortDescription Draws text with background color --- @param x number The x position to draw --- @param y number The y position to draw --- @param text string The text char to draw @@ -128,7 +137,8 @@ function VisualElement:textBg(x, y, text, bg) self.parent:textBg(x, y, text, bg) end ---- Draws a text character with a foreground and background at the specified position, used in the rendering system +--- Draws text with both foreground and background colors +--- @shortDescription Draws text with both colors --- @param x number The x position to draw --- @param y number The y position to draw --- @param text string The text char to draw @@ -141,6 +151,7 @@ function VisualElement:blit(x, y, text, fg, bg) end --- Checks if the specified coordinates are within the bounds of the element +--- @shortDescription Checks if point is within bounds --- @param x number The x position to check --- @param y number The y position to check --- @return boolean isInBounds Whether the coordinates are within the bounds of the element @@ -153,6 +164,7 @@ function VisualElement:isInBounds(x, y) end --- Handles a mouse click event +--- @shortDescription Handles a mouse click event --- @param button number The button that was clicked --- @param x number The x position of the click --- @param y number The y position of the click @@ -167,6 +179,7 @@ function VisualElement:mouse_click(button, x, y) end --- Handles a mouse up event +--- @shortDescription Handles a mouse up event --- @param button number The button that was released --- @param x number The x position of the release --- @param y number The y position of the release @@ -181,6 +194,7 @@ function VisualElement:mouse_up(button, x, y) end --- Handles a mouse release event +--- @shortDescription Handles a mouse release event --- @param button number The button that was released --- @param x number The x position of the release --- @param y number The y position of the release @@ -195,17 +209,20 @@ function VisualElement:mouse_release(button, x, y) end --- Handles a focus event +--- @shortDescription Handles a focus event function VisualElement:focus() self:fireEvent("focus") end --- Handles a blur event +--- @shortDescription Handles a blur event function VisualElement:blur() self:fireEvent("blur") self:setCursor(1,1, false) 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 ---@param y? number y position function VisualElement:getAbsolutePosition(x, y) @@ -229,6 +246,7 @@ function VisualElement:getAbsolutePosition(x, y) end --- Returns the relative position of the element or the given coordinates. +--- @shortDescription Returns the relative position of the element ---@param x? number x position ---@param y? number y position ---@return number, number @@ -250,6 +268,7 @@ function VisualElement:getRelativePosition(x, y) end --- Sets the cursor position +--- @shortDescription Sets the cursor position --- @param x number The x position of the cursor --- @param y number The y position of the cursor --- @param blink boolean Whether the cursor should blink @@ -262,6 +281,7 @@ function VisualElement:setCursor(x, y, blink) end --- Renders the element +--- @shortDescription Renders the element function VisualElement:render() if(not self.get("backgroundEnabled"))then return diff --git a/src/errorManager.lua b/src/errorManager.lua index 8e5d1f3..042d256 100644 --- a/src/errorManager.lua +++ b/src/errorManager.lua @@ -1,5 +1,9 @@ local LOGGER = require("log") +--- This is Basalt's error handler. All the errors are handled by this module. +--- @class ErrorHandler +--- @field tracebackEnabled boolean If the error handler should print a stack trace +--- @field header string The header of the error message local errorHandler = { tracebackEnabled = true, header = "Basalt Error" @@ -11,6 +15,9 @@ local function coloredPrint(message, color) term.setTextColor(colors.white) end +--- Handles an error +--- @param errMsg string The error message +--- @usage errorHandler.error("An error occurred") function errorHandler.error(errMsg) if errorHandler.errorHandled then error() diff --git a/src/log.lua b/src/log.lua index 9d133f9..60aa5a5 100644 --- a/src/log.lua +++ b/src/log.lua @@ -1,3 +1,4 @@ +--- Logger module for Basalt. Logs messages to the console and optionally to a file. --- @class Log --- @field _logs table The complete log history --- @field _enabled boolean If the logger is enabled @@ -87,10 +88,9 @@ end --- Sends a debug message to the logger. --- @shortDescription Sends a debug message +--- @vararg string The message to log --- @usage Log.debug("This is a debug message") -function Log.debug(...) - log(Log.LEVEL.DEBUG, ...) -end +function Log.debug(...) log(Log.LEVEL.DEBUG, ...) end --- Sends an info message to the logger. --- @shortDescription Sends an info message @@ -110,6 +110,4 @@ function Log.warn(...) log(Log.LEVEL.WARN, ...) end --- @usage Log.error("This is an error message") function Log.error(...) log(Log.LEVEL.ERROR, ...) end -Log.info("Logger initialized") - return Log \ No newline at end of file diff --git a/src/main.lua b/src/main.lua index 8f78ef6..8fef606 100644 --- a/src/main.lua +++ b/src/main.lua @@ -8,7 +8,6 @@ local propertySystem = require("propertySystem") --- Before you can access Basalt, you need to add the following code on top of your file: --- @usage local basalt = require("basalt") --- What this code does is it loads basalt into the project, and you can access it by using the variable defined as "basalt". - --- @class Basalt --- @field traceback boolean Whether to show a traceback on errors --- @field _events table A table of events and their callbacks diff --git a/src/plugins/animation.lua b/src/plugins/animation.lua index 0602eae..c3be480 100644 --- a/src/plugins/animation.lua +++ b/src/plugins/animation.lua @@ -1,22 +1,4 @@ -local Animation = {} -Animation.__index = Animation - local registeredAnimations = {} - -function Animation.registerAnimation(name, handlers) - registeredAnimations[name] = handlers - - Animation[name] = function(self, ...) - local args = {...} - local easing = "linear" - if(type(args[#args]) == "string") then - easing = table.remove(args, #args) - end - local duration = table.remove(args, #args) - return self:addAnimation(name, args, duration, easing) - end -end - local easings = { linear = function(progress) return progress @@ -38,13 +20,29 @@ local easings = { end } -function Animation.registerEasing(name, func) - easings[name] = func -end +---@splitClass +--- This is the AnimationInstance class. It represents a single animation instance +---@class AnimationInstance +---@field element VisualElement The element being animated +---@field type string The type of animation +---@field args table The animation arguments +---@field duration number The duration in seconds +---@field startTime number The animation start time +---@field isPaused boolean Whether the animation is paused +---@field handlers table The animation handlers +---@field easing string The easing function name local AnimationInstance = {} AnimationInstance.__index = AnimationInstance +--- Creates a new AnimationInstance +--- @shortDescription Creates a new animation instance +--- @param element VisualElement The element to animate +--- @param animType string The type of animation +--- @param args table The animation arguments +--- @param duration number Duration in seconds +--- @param easing string The easing function name +--- @return AnimationInstance The new animation instance function AnimationInstance.new(element, animType, args, duration, easing) local self = setmetatable({}, AnimationInstance) self.element = element @@ -58,25 +56,72 @@ function AnimationInstance.new(element, animType, args, duration, easing) return self end +--- Starts the animation +--- @shortDescription Starts the animation +--- @return AnimationInstance self The animation instance function AnimationInstance:start() self.startTime = os.epoch("local") / 1000 if self.handlers.start then self.handlers.start(self) end + return self end +--- Updates the animation +--- @shortDescription Updates the animation +--- @param elapsed number The elapsed time in seconds +--- @return boolean Whether the animation is finished function AnimationInstance:update(elapsed) local rawProgress = math.min(1, elapsed / self.duration) local progress = easings[self.easing](rawProgress) return self.handlers.update(self, progress) end +--- Gets called when the animation is completed +--- @shortDescription Called when the animation is completed function AnimationInstance:complete() if self.handlers.complete then self.handlers.complete(self) end end +--- This is the animation plugin. It provides a animation system for visual elements +--- with support for sequences, easing functions, and multiple animation types. +---@class Animation +local Animation = {} +Animation.__index = Animation + +--- Registers a new animation type +--- @shortDescription Registers a custom animation type +--- @param name string The name of the animation +--- @param handlers table Table containing start, update and complete handlers +--- @usage Animation.registerAnimation("fade", {start=function(anim) end, update=function(anim,progress) end}) +function Animation.registerAnimation(name, handlers) + registeredAnimations[name] = handlers + + Animation[name] = function(self, ...) + local args = {...} + local easing = "linear" + if(type(args[#args]) == "string") then + easing = table.remove(args, #args) + end + local duration = table.remove(args, #args) + return self:addAnimation(name, args, duration, easing) + end +end + +--- Registers a new easing function +--- @shortDescription Adds a custom easing function +--- @param name string The name of the easing function +--- @param func function The easing function (takes progress 0-1, returns modified progress) +function Animation.registerEasing(name, func) + easings[name] = func +end + +--- Creates a new Animation +--- @shortDescription Creates a new animation +--- @param element VisualElement The element to animate +--- @return Animation The new animation function Animation.new(element) local self = {} self.element = element @@ -88,6 +133,9 @@ function Animation.new(element) return self end +--- Creates a new sequence +--- @shortDescription Creates a new sequence +--- @return Animation self The animation instance function Animation:sequence() table.insert(self.sequences, {}) self.currentSequence = #self.sequences @@ -99,6 +147,9 @@ function Animation:sequence() return self end +--- Registers a callback for the start event +--- @shortDescription Registers a callback for the start event +--- @param callback function The callback function to register function Animation:onStart(callback) if not self.sequenceCallbacks[self.currentSequence] then self.sequenceCallbacks[self.currentSequence] = {} @@ -107,6 +158,10 @@ function Animation:onStart(callback) return self end +--- Registers a callback for the update event +--- @shortDescription Registers a callback for the update event +--- @param callback function The callback function to register +--- @return Animation self The animation instance function Animation:onUpdate(callback) if not self.sequenceCallbacks[self.currentSequence] then self.sequenceCallbacks[self.currentSequence] = {} @@ -115,6 +170,10 @@ function Animation:onUpdate(callback) return self end +--- Registers a callback for the complete event +--- @shortDescription Registers a callback for the complete event +--- @param callback function The callback function to register +--- @return Animation self The animation instance function Animation:onComplete(callback) if not self.sequenceCallbacks[self.currentSequence] then self.sequenceCallbacks[self.currentSequence] = {} @@ -123,14 +182,22 @@ function Animation:onComplete(callback) return self end +--- Adds a new animation to the sequence +--- @shortDescription Adds a new animation to the sequence +--- @param type string The type of animation +--- @param args table The animation arguments +--- @param duration number The duration in seconds +--- @param easing string The easing function name function Animation:addAnimation(type, args, duration, easing) local anim = AnimationInstance.new(self.element, type, args, duration, easing) table.insert(self.sequences[self.currentSequence], anim) return self end +--- Starts the animation +--- @shortDescription Starts the animation +--- @return Animation self The animation instance function Animation:start() - self.currentSequence = 1 if(self.sequenceCallbacks[self.currentSequence])then if(self.sequenceCallbacks[self.currentSequence].start) then @@ -146,6 +213,10 @@ function Animation:start() return self end +--- The event handler for the animation (listens to timer events) +--- @shortDescription The event handler for the animation +--- @param event string The event type +--- @param timerId number The timer ID function Animation:event(event, timerId) if event == "timer" and timerId == self.timer then local currentTime = os.epoch("local") / 1000 @@ -300,8 +371,13 @@ Animation.registerAnimation("scrollText", { end }) +---@splitClass + +--- Adds additional methods for VisualElement when adding animation plugin +--- @class VisualElement local VisualElement = {hooks={}} +---@private function VisualElement.hooks.dispatchEvent(self, event, ...) if event == "timer" then local animation = self.get("animation") @@ -311,12 +387,16 @@ function VisualElement.hooks.dispatchEvent(self, event, ...) end end +---@private function VisualElement.setup(element) VisualElementBaseDispatchEvent = element.dispatchEvent element.defineProperty(element, "animation", {default = nil, type = "table"}) element.listenTo(element, "timer") end +--- Creates a new Animation Object +--- @shortDescription Creates a new animation +--- @return Animation animation The new animation function VisualElement:animate() local animation = Animation.new(self) self.set("animation", animation) diff --git a/src/plugins/benchmark.lua b/src/plugins/benchmark.lua index ea6b219..5f7fbe0 100644 --- a/src/plugins/benchmark.lua +++ b/src/plugins/benchmark.lua @@ -1,5 +1,6 @@ local log = require("log") + local activeProfiles = setmetatable({}, {__mode = "k"}) local function createProfile() @@ -36,8 +37,18 @@ local function wrapMethod(element, methodName) end end +---@splitClass + +--- This is the benchmark plugin. It provides performance measurement tools for elements and methods, +--- with support for hierarchical profiling and detailed statistics. +--- The following methods are available for BaseElement +---@class BaseElement local BaseElement = {} +--- Starts profiling a method +--- @shortDescription Starts timing a method call +--- @param methodName string The name of the method to profile +--- @return BaseElement self The element instance function BaseElement:startProfile(methodName) local profile = activeProfiles[self] if not profile then @@ -70,6 +81,10 @@ function BaseElement:startProfile(methodName) return self end +--- Ends profiling a method +--- @shortDescription Ends timing a method call and records statistics +--- @param methodName string The name of the method to stop profiling +--- @return BaseElement self The element instance function BaseElement:endProfile(methodName) local profile = activeProfiles[self] if not profile or not profile.methods[methodName] then return self end @@ -87,6 +102,11 @@ function BaseElement:endProfile(methodName) return self end +--- Enables benchmarking for a method +--- @shortDescription Enables performance measurement for a method +--- @param methodName string The name of the method to benchmark +--- @return BaseElement self The element instance +--- @usage element:benchmark("render") function BaseElement:benchmark(methodName) if not self[methodName] then log.error("Method " .. methodName .. " does not exist") @@ -101,6 +121,10 @@ function BaseElement:benchmark(methodName) return self end +--- Logs benchmark statistics for a method +--- @shortDescription Logs benchmark statistics for a method +--- @param methodName string The name of the method to log +--- @return BaseElement self The element instance function BaseElement:logBenchmark(methodName) local profile = activeProfiles[self] if not profile or not profile.methods[methodName] then return self end @@ -131,6 +155,10 @@ function BaseElement:logBenchmark(methodName) return self end +--- Stops benchmarking for a method +--- @shortDescription Disables performance measurement for a method +--- @param methodName string The name of the method to stop benchmarking +--- @return BaseElement self The element instance function BaseElement:stopBenchmark(methodName) local profile = activeProfiles[self] if not profile or not profile.methods[methodName] then return self end @@ -147,6 +175,10 @@ function BaseElement:stopBenchmark(methodName) return self end +--- Gets benchmark statistics for a method +--- @shortDescription Retrieves benchmark statistics for a method +--- @param methodName string The name of the method to get statistics for +--- @return table? stats The benchmark statistics or nil function BaseElement:getBenchmarkStats(methodName) local profile = activeProfiles[self] if not profile or not profile.methods[methodName] then return nil end @@ -162,8 +194,17 @@ function BaseElement:getBenchmarkStats(methodName) } end +---@splitClass + +--- Container benchmarking methods +---@class Container local Container = {} +--- Enables benchmarking for a container and all its children +--- @shortDescription Recursively enables benchmarking +--- @param methodName string The method to benchmark +--- @return Container self The container instance +--- @usage container:benchmarkContainer("render") function Container:benchmarkContainer(methodName) self:benchmark(methodName) @@ -177,6 +218,10 @@ function Container:benchmarkContainer(methodName) return self end +--- Logs benchmark statistics for a container and all its children +--- @shortDescription Recursively logs benchmark statistics +--- @param methodName string The method to log +--- @return Container self The container instance function Container:logContainerBenchmarks(methodName, depth) depth = depth or 0 local indent = string.rep(" ", depth) @@ -237,6 +282,10 @@ function Container:logContainerBenchmarks(methodName, depth) return self end +--- Stops benchmarking for a container and all its children +--- @shortDescription Recursively stops benchmarking +--- @param methodName string The method to stop benchmarking +--- @return Container self The container instance function Container:stopContainerBenchmark(methodName) for _, child in pairs(self.get("children")) do if child:isType("Container") then @@ -250,73 +299,91 @@ function Container:stopContainerBenchmark(methodName) return self end -local API = { - start = function(name, options) - options = options or {} - local profile = createProfile() - profile.name = name - profile.startTime = os.clock() * 1000 - profile.custom = true - activeProfiles[name] = profile - end, +--- Benchmark API methods +---@class BenchmarkAPI +local API = {} - stop = function(name) - local profile = activeProfiles[name] - if not profile or not profile.custom then return end +--- Starts a custom benchmark +--- @shortDescription Starts timing a custom operation +--- @param name string The name of the benchmark +--- @param options? table Optional configuration +function API.start(name, options) + options = options or {} + local profile = createProfile() + profile.name = name + profile.startTime = os.clock() * 1000 + profile.custom = true + activeProfiles[name] = profile +end - local endTime = os.clock() * 1000 - local duration = endTime - profile.startTime +--- Stops a custom benchmark +--- @shortDescription Stops timing and logs results +--- @param name string The name of the benchmark to stop +function API.stop(name) + local profile = activeProfiles[name] + if not profile or not profile.custom then return end - profile.calls = profile.calls + 1 - profile.totalTime = profile.totalTime + duration - profile.minTime = math.min(profile.minTime, duration) - profile.maxTime = math.max(profile.maxTime, duration) - profile.lastTime = duration + local endTime = os.clock() * 1000 + local duration = endTime - profile.startTime - log.info(string.format( - "Custom Benchmark '%s': " .. - "Calls: %d " .. - "Average time: %.2fms " .. - "Min time: %.2fms " .. - "Max time: %.2fms " .. - "Last time: %.2fms " .. - "Total time: %.2fms", - name, - profile.calls, - profile.totalTime / profile.calls, - profile.minTime, - profile.maxTime, - profile.lastTime, - profile.totalTime - )) - end, + profile.calls = profile.calls + 1 + profile.totalTime = profile.totalTime + duration + profile.minTime = math.min(profile.minTime, duration) + profile.maxTime = math.max(profile.maxTime, duration) + profile.lastTime = duration - getStats = function(name) - local profile = activeProfiles[name] - if not profile then return nil end + log.info(string.format( + "Custom Benchmark '%s': " .. + "Calls: %d " .. + "Average time: %.2fms " .. + "Min time: %.2fms " .. + "Max time: %.2fms " .. + "Last time: %.2fms " .. + "Total time: %.2fms", + name, + profile.calls, + profile.totalTime / profile.calls, + profile.minTime, + profile.maxTime, + profile.lastTime, + profile.totalTime + )) +end - return { - averageTime = profile.totalTime / profile.calls, - totalTime = profile.totalTime, - calls = profile.calls, - minTime = profile.minTime, - maxTime = profile.maxTime, - lastTime = profile.lastTime - } - end, +--- Gets statistics for a benchmark +--- @shortDescription Retrieves benchmark statistics +--- @param name string The name of the benchmark +--- @return table? stats The benchmark statistics or nil +function API.getStats(name) + local profile = activeProfiles[name] + if not profile then return nil end - clear = function(name) - activeProfiles[name] = nil - end, + return { + averageTime = profile.totalTime / profile.calls, + totalTime = profile.totalTime, + calls = profile.calls, + minTime = profile.minTime, + maxTime = profile.maxTime, + lastTime = profile.lastTime + } +end - clearAll = function() - for k,v in pairs(activeProfiles) do - if v.custom then - activeProfiles[k] = nil - end +--- Clears a specific benchmark +--- @shortDescription Removes a benchmark's data +--- @param name string The name of the benchmark to clear +function API.clear(name) + activeProfiles[name] = nil +end + +--- Clears all custom benchmarks +--- @shortDescription Removes all custom benchmark data +function API.clearAll() + for k,v in pairs(activeProfiles) do + if v.custom then + activeProfiles[k] = nil end end -} +end return { BaseElement = BaseElement, diff --git a/src/plugins/debug.lua b/src/plugins/debug.lua index a83532b..0ce6319 100644 --- a/src/plugins/debug.lua +++ b/src/plugins/debug.lua @@ -37,142 +37,165 @@ local function createDebugger(element) } end -local BaseElement = { - debug = function(self, level) - self._debugger = createDebugger(self) - self._debugLevel = level or DEBUG_LEVELS.INFO - return self - end, +--- No Description +--- @class BaseElement +local BaseElement = {} - dumpDebug = function(self) - if not self._debugger then return end - return self._debugger.dump() - end -} +--- Enables debugging for this element +--- @shortDescription Enables debugging for this element +--- @param self BaseElement The element to debug +--- @param level number The debug level +function BaseElement.debug(self, level) + self._debugger = createDebugger(self) + self._debugLevel = level or DEBUG_LEVELS.INFO + return self +end -local BaseFrame = { - showDebugLog = function(self) - if not self._debugFrame then - local width = self.get("width") - local height = self.get("height") - self._debugFrame = self:addFrame("basaltDebugLog") - :setWidth(width) - :setHeight(height) - :setZ(999) - :listenEvent("mouse_scroll", true) - self.basalt.LOGGER.debug("Created debug log frame " .. self._debugFrame.get("name")) +--- Dumps debug information for this element +--- @shortDescription Dumps debug information +--- @param self BaseElement The element to dump debug info for +function BaseElement.dumpDebug(self) + if not self._debugger then return end + return self._debugger.dump() +end - self._debugFrame:addButton("basaltDebugLogClose") - :setWidth(9) - :setHeight(1) - :setX(width - 8) - :setY(height) - :setText("Close") - :onMouseClick(function() - self:hideDebugLog() - end) +---@class BaseFrame +local BaseFrame = {} - self._debugFrame._scrollOffset = 0 - self._debugFrame._processedLogs = {} +--- Shows the debug log frame +--- @shortDescription Shows the debug log frame +--- @param self BaseFrame The frame to show debug log in +function BaseFrame.showDebugLog(self) + if not self._debugFrame then + local width = self.get("width") + local height = self.get("height") + self._debugFrame = self:addFrame("basaltDebugLog") + :setWidth(width) + :setHeight(height) + :setZ(999) + :listenEvent("mouse_scroll", true) + self.basalt.LOGGER.debug("Created debug log frame " .. self._debugFrame.get("name")) - local function wrapText(text, width) - local lines = {} - while #text > 0 do - local line = text:sub(1, width) - table.insert(lines, line) - text = text:sub(width + 1) - end - return lines - end - - local function processLogs() - local processed = {} - local width = self._debugFrame.get("width") - - for _, entry in ipairs(log._logs) do - local lines = wrapText(entry.message, width) - for _, line in ipairs(lines) do - table.insert(processed, { - text = line, - level = entry.level - }) - end - end - return processed - end - - local totalLines = #processLogs() - self.get("height") - self._scrollOffset = totalLines - - local originalRender = self._debugFrame.render - self._debugFrame.render = function(frame) - originalRender(frame) - frame._processedLogs = processLogs() - - local height = frame.get("height")-2 - local totalLines = #frame._processedLogs - local maxScroll = math.max(0, totalLines - height) - frame._scrollOffset = math.min(frame._scrollOffset, maxScroll) - - for i = 1, height-2 do - local logIndex = i + frame._scrollOffset - local entry = frame._processedLogs[logIndex] - - if entry then - local color = entry.level == log.LEVEL.ERROR and colors.red - or entry.level == log.LEVEL.WARN and colors.yellow - or entry.level == log.LEVEL.DEBUG and colors.lightGray - or colors.white - - frame:textFg(2, i, entry.text, color) - end - end - end - - local baseDispatchEvent = self._debugFrame.dispatchEvent - self._debugFrame.dispatchEvent = function(self, event, direction, ...) - if(event == "mouse_scroll") then - self._scrollOffset = math.max(0, self._scrollOffset + direction) - self:updateRender() - return true - else - baseDispatchEvent(self, event, direction, ...) - end - end - end - self._debugFrame.set("visible", true) - return self - end, - - hideDebugLog = function(self) - if self._debugFrame then - self._debugFrame.set("visible", false) - end - return self - end, - - toggleDebugLog = function(self) - if self._debugFrame and self._debugFrame:isVisible() then + self._debugFrame:addButton("basaltDebugLogClose") + :setWidth(9) + :setHeight(1) + :setX(width - 8) + :setY(height) + :setText("Close") + :onMouseClick(function() self:hideDebugLog() - else - self:showDebugLog() + end) + + self._debugFrame._scrollOffset = 0 + self._debugFrame._processedLogs = {} + + local function wrapText(text, width) + local lines = {} + while #text > 0 do + local line = text:sub(1, width) + table.insert(lines, line) + text = text:sub(width + 1) + end + return lines end - return self - end -} + local function processLogs() + local processed = {} + local width = self._debugFrame.get("width") -local Container = { - debugChildren = function(self, level) - self:debug(level) - for _, child in pairs(self.get("children")) do - if child.debug then - child:debug(level) + for _, entry in ipairs(log._logs) do + local lines = wrapText(entry.message, width) + for _, line in ipairs(lines) do + table.insert(processed, { + text = line, + level = entry.level + }) + end + end + return processed + end + + local totalLines = #processLogs() - self.get("height") + self._scrollOffset = totalLines + + local originalRender = self._debugFrame.render + self._debugFrame.render = function(frame) + originalRender(frame) + frame._processedLogs = processLogs() + + local height = frame.get("height")-2 + local totalLines = #frame._processedLogs + local maxScroll = math.max(0, totalLines - height) + frame._scrollOffset = math.min(frame._scrollOffset, maxScroll) + + for i = 1, height-2 do + local logIndex = i + frame._scrollOffset + local entry = frame._processedLogs[logIndex] + + if entry then + local color = entry.level == log.LEVEL.ERROR and colors.red + or entry.level == log.LEVEL.WARN and colors.yellow + or entry.level == log.LEVEL.DEBUG and colors.lightGray + or colors.white + + frame:textFg(2, i, entry.text, color) + end + end + end + + local baseDispatchEvent = self._debugFrame.dispatchEvent + self._debugFrame.dispatchEvent = function(self, event, direction, ...) + if(event == "mouse_scroll") then + self._scrollOffset = math.max(0, self._scrollOffset + direction) + self:updateRender() + return true + else + baseDispatchEvent(self, event, direction, ...) end end - return self end -} + self._debugFrame.set("visible", true) + return self +end + +--- Hides the debug log frame +--- @shortDescription Hides the debug log frame +--- @param self BaseFrame The frame to hide debug log for +function BaseFrame.hideDebugLog(self) + if self._debugFrame then + self._debugFrame.set("visible", false) + end + return self +end + +--- Toggles the debug log frame +--- @shortDescription Toggles the debug log frame +--- @param self BaseFrame The frame to toggle debug log for +function BaseFrame.toggleDebugLog(self) + if self._debugFrame and self._debugFrame:isVisible() then + self:hideDebugLog() + else + self:showDebugLog() + end + return self +end + +---@class Container +local Container = {} + +--- Enables debugging for this container and all its children +--- @shortDescription Debug container and children +--- @param self Container The container to debug +--- @param level number The debug level +function Container.debugChildren(self, level) + self:debug(level) + for _, child in pairs(self.get("children")) do + if child.debug then + child:debug(level) + end + end + return self +end return { BaseElement = BaseElement, diff --git a/src/plugins/state.lua b/src/plugins/state.lua index ee632d8..cdda3f1 100644 --- a/src/plugins/state.lua +++ b/src/plugins/state.lua @@ -1,7 +1,12 @@ local PropertySystem = require("propertySystem") local errorManager = require("errorManager") + +--- This is the state plugin. It provides a state management system for UI elements with support for +--- persistent states, computed states, and state sharing between elements. +---@class BaseElement local BaseElement = {} +---@private function BaseElement.setup(element) element.defineProperty(element, "states", {default = {}, type = "table"}) element.defineProperty(element, "computedStates", {default = {}, type = "table"}) @@ -11,6 +16,15 @@ function BaseElement.setup(element) }) end +--- Initializes a new state for this element +--- @shortDescription Initializes a new state +--- @param self BaseElement The element to initialize state for +--- @param name string The name of the state +--- @param default any The default value of the state +--- @param canTriggerRender? boolean Whether state changes trigger a render +--- @param persist? boolean Whether to persist the state to disk +--- @param path? string Custom file path for persistence +--- @return BaseElement self The element instance function BaseElement:initializeState(name, default, canTriggerRender, persist, path) local states = self.get("states") @@ -47,6 +61,12 @@ function BaseElement:initializeState(name, default, canTriggerRender, persist, p return self end +--- Sets the value of a state +--- @shortDescription Sets a state value +--- @param self BaseElement The element to set state for +--- @param name string The name of the state +--- @param value any The new value for the state +--- @return BaseElement self The element instance function BaseElement:setState(name, value) local states = self.get("states") if not states[name] then @@ -78,6 +98,11 @@ function BaseElement:setState(name, value) return self end +--- Gets the value of a state +--- @shortDescription Gets a state value +--- @param self BaseElement The element to get state from +--- @param name string The name of the state +--- @return any value The current state value function BaseElement:getState(name) local states = self.get("states") if not states[name] then @@ -86,6 +111,12 @@ function BaseElement:getState(name) return states[name].value end +--- Creates a computed state that derives its value from other states +--- @shortDescription Creates a computed state +--- @param self BaseElement The element to create computed state for +--- @param key string The name of the computed state +--- @param computeFn function Function that computes the state value +--- @return BaseElement self The element instance function BaseElement:computed(key, computeFn) local computed = self.get("computedStates") computed[key] = setmetatable({}, { @@ -96,6 +127,12 @@ function BaseElement:computed(key, computeFn) return self end +--- Shares a state with other elements, keeping them in sync +--- @shortDescription Shares state between elements +--- @param self BaseElement The source element +--- @param stateKey string The state to share +--- @vararg BaseElement The target elements to share with +--- @return BaseElement self The source element function BaseElement:shareState(stateKey, ...) local value = self:getState(stateKey) @@ -116,6 +153,12 @@ function BaseElement:shareState(stateKey, ...) return self end +--- Registers a callback for state changes +--- @shortDescription Watches for state changes +--- @param self BaseElement The element to watch +--- @param stateName string The state to watch +--- @param callback function Called with (element, newValue, oldValue) +--- @return BaseElement self The element instance function BaseElement:onStateChange(stateName, callback) if not self.get("states")[stateName] then errorManager.error("Cannot observe state '" .. stateName .. "': State not initialized") diff --git a/src/plugins/theme.lua b/src/plugins/theme.lua index 247d6e6..d7b1132 100644 --- a/src/plugins/theme.lua +++ b/src/plugins/theme.lua @@ -40,15 +40,20 @@ local themes = { local currentTheme = "default" +--- This is the theme plugin. It provides a theming system that allows for consistent styling across elements +--- with support for inheritance, named styles, and dynamic theme switching. +---@class BaseElement local BaseElement = { hooks = { postInit = { pre = function(self) - self:applyTheme() - end} + self:applyTheme() + end + } } } +---@private function BaseElement.____getElementPath(self, types) if types then table.insert(types, 1, self._values.type) @@ -147,7 +152,11 @@ local function collectThemeProps(theme, path, elementType, elementName) return result end - function BaseElement:applyTheme() +--- Applies the current theme to this element +--- @shortDescription Applies theme styles to the element +--- @param self BaseElement The element to apply theme to +--- @return BaseElement self The element instance +function BaseElement:applyTheme() local styles = self:getTheme() if(styles ~= nil) then for prop, value in pairs(styles) do @@ -156,6 +165,10 @@ end end end +--- Gets the theme properties for this element +--- @shortDescription Gets theme properties for the element +--- @param self BaseElement The element to get theme for +--- @return table styles The theme properties function BaseElement:getTheme() local path = self:____getElementPath() local elementType = self.get("type") @@ -164,28 +177,37 @@ function BaseElement:getTheme() return collectThemeProps(themes[currentTheme], path, elementType, elementName) end -local themeAPI = { - setTheme = function(newTheme) - defaultTheme = newTheme - end, +--- The Theme API provides methods for managing themes globally +---@class ThemeAPI +local themeAPI = {} - getTheme = function() - return defaultTheme - end, +--- Sets the current theme +--- @shortDescription Sets a new theme +--- @param newTheme table The theme configuration to set +function themeAPI.setTheme(newTheme) + defaultTheme = newTheme +end - loadTheme = function(path) - local file = fs.open(path, "r") - if file then - local content = file.readAll() - file.close() - defaultTheme = textutils.unserializeJSON(content) - end +--- Gets the current theme configuration +--- @shortDescription Gets the current theme +--- @return table theme The current theme configuration +function themeAPI.getTheme() + return defaultTheme +end + +--- Loads a theme from a JSON file +--- @shortDescription Loads theme from JSON file +--- @param path string Path to the theme JSON file +function themeAPI.loadTheme(path) + local file = fs.open(path, "r") + if file then + local content = file.readAll() + file.close() + defaultTheme = textutils.unserializeJSON(content) end -} +end -local Theme = { +return { BaseElement = BaseElement, API = themeAPI } - -return Theme diff --git a/src/plugins/xml.lua b/src/plugins/xml.lua index a847432..59cb903 100644 --- a/src/plugins/xml.lua +++ b/src/plugins/xml.lua @@ -132,8 +132,15 @@ local function handleEvent(node, element, scope) end end +--- The XML plugin provides XML parsing and UI creation from XML markup +---@class BaseElement local BaseElement = {} +--- Creates an element from an XML node +--- @shortDescription Creates element from XML node +--- @param self BaseElement The element instance +--- @param node table The XML node to create from +--- @return BaseElement self The element instance function BaseElement:fromXML(node) for attr, value in pairs(node.attributes) do local config = self:getPropertyConfig(attr) @@ -145,8 +152,22 @@ function BaseElement:fromXML(node) return self end +---@class Container local Container = {} +--- Loads and creates UI elements from XML content +--- @shortDescription Loads UI from XML string +--- @param self Container The container to load into +--- @param content string The XML content to parse +--- @param scope? table Optional scope for variable resolution +--- @return Container self The container instance +--- @usage +--- local xml = [[ +--- +---