diff --git a/.gitignore b/.gitignore index 16ea5ea..cc3a404 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ lua-ls-cc-tweaked-main test.xml ascii.lua tests -testWorkflows \ No newline at end of file +testWorkflows +basaltImage.lua \ No newline at end of file diff --git a/install.lua b/install.lua index 2c9f8e3..5d3e7a2 100644 --- a/install.lua +++ b/install.lua @@ -68,7 +68,7 @@ end local function createScreen(index) local screen = main:addFrame(coloring) - :onMouseScroll(function(self, direction) + :onScroll(function(self, direction) local height = getChildrenHeight(self) local scrollOffset = self:getOffsetY() local maxScroll = height - self:getHeight() @@ -129,7 +129,7 @@ nextButton = main:addButton() :setText("Next") :setPosition("{parent.width - 9}", "{parent.height - 1}") :setIgnoreOffset(true) - :onMouseClick(function() switchScreen(1) end) + :onClick(function() switchScreen(1) end) backButton = main:addButton() :setBackground("{self.clicked and colors.black or colors.white}") @@ -138,7 +138,7 @@ backButton = main:addButton() :setText("Back") :setPosition(2, "{parent.height - 1}") :setIgnoreOffset(true) - :onMouseClick(function() switchScreen(-1) end) + :onClick(function() switchScreen(-1) end) :setVisible(false) -- Screen 1: Welcome @@ -608,7 +608,7 @@ installButton = progressScreen:addButton() :setText("Install") :setPosition("{parent.width - 9}", "{parent.height - 3}") :setSize(9, 1) - :onMouseClick(function(self) + :onClick(function(self) if(currentlyInstalling)then return end @@ -621,7 +621,7 @@ local closeButton = progressScreen:addButton() :setText("Close") :setPosition("{parent.width - 9}", "{parent.height - 1}") :setSize(9, 1) - :onMouseClick(function(self) + :onClick(function(self) basalt.stop() end) diff --git a/src/elements/BaseElement.lua b/src/elements/BaseElement.lua index 0dff1e3..6907cc6 100644 --- a/src/elements/BaseElement.lua +++ b/src/elements/BaseElement.lua @@ -1,9 +1,8 @@ local PropertySystem = require("propertySystem") local uuid = require("libraries/utils").uuid ----@configDescription The base class for all UI elements in Basalt ----@configDefault true +---@configDescription The base class for all UI elements in Basalt. ---- The base class for all UI elements in Basalt +--- The base class for all UI elements in Basalt. This class provides basic properties and event handling functionality. --- @class BaseElement : PropertySystem local BaseElement = setmetatable({}, PropertySystem) BaseElement.__index = BaseElement @@ -34,7 +33,6 @@ BaseElement.defineProperty(BaseElement, "eventCallbacks", {default = {}, type = function BaseElement.defineEvent(class, eventName, requiredEvent) - -- Events auf Klassenebene speichern, wie bei Properties if not rawget(class, '_eventConfigs') then class._eventConfigs = {} end @@ -46,17 +44,15 @@ end function BaseElement.registerEventCallback(class, callbackName, ...) local methodName = callbackName:match("^on") and callbackName or "on"..callbackName - local events = {...} -- Alle Events als varargs - local mainEvent = events[1] -- Erstes Event ist immer das Haupt-Event + local events = {...} + local mainEvent = events[1] class[methodName] = function(self, ...) - -- Alle Events aktivieren for _, sysEvent in ipairs(events) do if not self._registeredEvents[sysEvent] then self:listenEvent(sysEvent, true) end end - -- Callback für das Haupt-Event registrieren self:registerCallback(mainEvent, ...) return self end @@ -85,11 +81,10 @@ function BaseElement:init(props, basalt) self._registeredEvents = {} local currentClass = getmetatable(self).__index - - -- Events Sammeln + local events = {} currentClass = self - + while currentClass do if type(currentClass) == "table" and currentClass._eventConfigs then for eventName, config in pairs(currentClass._eventConfigs) do @@ -237,7 +232,6 @@ end --- @shortDescription Destroys the element and cleans up all references --- @usage element:destroy() function BaseElement:destroy() - -- Remove from parent if exists if self.parent then self.parent:removeChild(self) end diff --git a/src/elements/BaseFrame.lua b/src/elements/BaseFrame.lua index f36bd15..ee2361a 100644 --- a/src/elements/BaseFrame.lua +++ b/src/elements/BaseFrame.lua @@ -109,11 +109,24 @@ function BaseFrame:setCursor(x, y, blink, color) self._render:setCursor(x, y, blink, color) end +---@private function BaseFrame:mouse_up(button, x, y) Container.mouse_up(self, button, x, y) Container.mouse_release(self, button, x, y) end +---@private +function BaseFrame:term_resize() + local width, height = self.get("term").getSize() + if(width == self.get("width") and height == self.get("height")) then + return + end + self.set("width", width) + self.set("height", height) + self._render:setSize(width, height) + self._renderUpdate = true +end + --- Renders the Frame --- @shortDescription Renders the Frame function BaseFrame:render() @@ -126,15 +139,4 @@ function BaseFrame:render() end end -function BaseFrame:term_resize() - local width, height = self.get("term").getSize() - if(width == self.get("width") and height == self.get("height")) then - return - end - self.set("width", width) - self.set("height", height) - self._render:setSize(width, height) - self._renderUpdate = true -end - return BaseFrame \ No newline at end of file diff --git a/src/elements/Button.lua b/src/elements/Button.lua index 25ec372..f2aff87 100644 --- a/src/elements/Button.lua +++ b/src/elements/Button.lua @@ -1,10 +1,9 @@ local elementManager = require("elementManager") local VisualElement = elementManager.getElement("VisualElement") local getCenteredPosition = require("libraries/utils").getCenteredPosition ----@cofnigDescription This is a button. It is a visual element that can be clicked. ----@configDefault true +---@cofnigDescription The Button is a standard button element with click handling and state management. ---- This is the button class. It is a visual element that can be clicked. +--- The Button is a standard button element with click handling and state management. ---@class Button : VisualElement ---@configDescription Standard button element with click handling and state management local Button = setmetatable({}, VisualElement) diff --git a/src/elements/Checkbox.lua b/src/elements/Checkbox.lua index a7c82ff..9a98285 100644 --- a/src/elements/Checkbox.lua +++ b/src/elements/Checkbox.lua @@ -1,6 +1,7 @@ local VisualElement = require("elements/VisualElement") +---@cofnigDescription This is a checkbox. It is a visual element that can be checked. ---- This is the checkbox class. It is a visual element that can be checked. +--- The Checkbox is a visual element that can be checked. ---@class Checkbox : VisualElement local Checkbox = setmetatable({}, VisualElement) Checkbox.__index = Checkbox diff --git a/src/elements/Container.lua b/src/elements/Container.lua index b7bba48..2b55637 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -330,6 +330,13 @@ local function convertMousePosition(self, event, ...) return args end +--- Calls a event on all children +--- @shortDescription Calls a event on all children +--- @param visibleOnly boolean Whether to only call the event on visible children +--- @param event string The event to call +--- @vararg any The event arguments +--- @return boolean handled Whether the event was handled +--- @return table child? The child that handled the event function Container:callChildrenEvents(visibleOnly, event, ...) local children = visibleOnly and self.get("visibleChildrenEvents") or self.get("childrenEvents") if children[event] then @@ -392,12 +399,23 @@ function Container:mouse_up(button, x, y) return false end +--- Handles mouse release events +--- @shortDescription Handles mouse release 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 function Container:mouse_release(button, x, y) VisualElement.mouse_release(self, button, x, y) local args = convertMousePosition(self, "mouse_release", button, x, y) self:callChildrenEvents(false, "mouse_release", table.unpack(args)) end +--- Handles mouse move events +--- @shortDescription Handles mouse move events +--- @param _ number unknown +--- @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 Container:mouse_move(_, x, y) if VisualElement.mouse_move(self, _, x, y) then local args = convertMousePosition(self, "mouse_move", _, x, y) @@ -409,6 +427,12 @@ function Container:mouse_move(_, x, y) return false end +--- Handles mouse drag events +--- @shortDescription Handles mouse drag 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 Container:mouse_drag(button, x, y) if VisualElement.mouse_drag(self, button, x, y) then local args = convertMousePosition(self, "mouse_drag", button, x, y) @@ -420,6 +444,12 @@ function Container:mouse_drag(button, x, y) return false end +--- Handles mouse scroll events +--- @shortDescription Handles mouse scroll events +--- @param direction number The direction of the scroll +--- @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 Container:mouse_scroll(direction, x, y) local args = convertMousePosition(self, "mouse_scroll", direction, x, y) local success, child = self:callChildrenEvents(true, "mouse_scroll", table.unpack(args)) diff --git a/src/elements/Dropdown.lua b/src/elements/Dropdown.lua index 701bd20..d966e30 100644 --- a/src/elements/Dropdown.lua +++ b/src/elements/Dropdown.lua @@ -7,7 +7,6 @@ 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 diff --git a/src/elements/Flexbox.lua b/src/elements/Flexbox.lua index b0e5524..325f817 100644 --- a/src/elements/Flexbox.lua +++ b/src/elements/Flexbox.lua @@ -1,12 +1,17 @@ local elementManager = require("elementManager") local Container = elementManager.getElement("Container") +---@configDescription A flexbox container that arranges its children in a flexible layout. +--- This is the Flexbox class. It is a container that arranges its children in a flexible layout. ---@class Flexbox : Container local Flexbox = setmetatable({}, Container) Flexbox.__index = Flexbox +---@property flexDirection string "row" The direction of the flexbox layout "row" or "column" Flexbox.defineProperty(Flexbox, "flexDirection", {default = "row", type = "string"}) +---@property flexSpacing number 1 The spacing between flex items Flexbox.defineProperty(Flexbox, "flexSpacing", {default = 1, type = "number"}) +---@property flexJustifyContent string "flex-start" The alignment of flex items along the main axis Flexbox.defineProperty(Flexbox, "flexJustifyContent", { default = "flex-start", type = "string", @@ -17,6 +22,8 @@ Flexbox.defineProperty(Flexbox, "flexJustifyContent", { return value end }) +---@property flexWrap boolean false Whether to wrap flex items onto multiple lines +---@property flexUpdateLayout boolean false Whether to update the layout of the flexbox Flexbox.defineProperty(Flexbox, "flexWrap", {default = false, type = "boolean"}) Flexbox.defineProperty(Flexbox, "flexUpdateLayout", {default = false, type = "boolean"}) @@ -226,6 +233,7 @@ local function updateLayout(self, direction, spacing, justifyContent, wrap) end --- Creates a new Flexbox instance +--- @shortDescription Creates a new Flexbox instance --- @return Flexbox object The newly created Flexbox instance --- @usage local element = Flexbox.new("myId", basalt) function Flexbox.new() @@ -239,11 +247,21 @@ function Flexbox.new() return self end +--- Initializes the Flexbox instance +--- @shortDescription Initializes the Flexbox instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Flexbox self The initialized instance function Flexbox:init(props, basalt) Container.init(self, props, basalt) self.set("type", "Flexbox") + return self end +--- Adds a child element to the flexbox +--- @shortDescription Adds a child element to the flexbox +--- @param element Element The child element to add +--- @return Flexbox self The flexbox instance function Flexbox:addChild(element) Container.addChild(self, element) @@ -257,6 +275,10 @@ function Flexbox:addChild(element) return self end +--- Removes a child element from the flexbox +--- @shortDescription Removes a child element from the flexbox +--- @param element Element The child element to remove +--- @return Flexbox self The flexbox instance function Flexbox:removeChild(element) Container.removeChild(self, element) @@ -276,7 +298,8 @@ function Flexbox:removeChild(element) return self end ---- Adds a new line break to the flexbox. +--- Adds a new line break to the flexbox +--- @shortDescription Adds a new line break to the flexbox. ---@param self Flexbox The element itself ---@return Flexbox function Flexbox:addLineBreak() @@ -284,6 +307,9 @@ function Flexbox:addLineBreak() return self end +--- Renders the flexbox and its children +--- @shortDescription Renders the flexbox and its children +--- @return Flexbox self The flexbox instance function Flexbox:render() if(self.get("flexUpdateLayout"))then updateLayout(self, self.get("flexDirection"), self.get("flexSpacing"), self.get("flexJustifyContent"), self.get("flexWrap")) diff --git a/src/elements/Frame.lua b/src/elements/Frame.lua index 952d987..adf1c0b 100644 --- a/src/elements/Frame.lua +++ b/src/elements/Frame.lua @@ -1,5 +1,6 @@ local elementManager = require("elementManager") local Container = elementManager.getElement("Container") +---@configDescription A frame element that serves as a grouping container for other elements. --- This is the frame class. It serves as a grouping container for other elements. ---@class Frame : Container diff --git a/src/elements/Image.lua b/src/elements/Image.lua new file mode 100644 index 0000000..57bca5e --- /dev/null +++ b/src/elements/Image.lua @@ -0,0 +1,205 @@ +local elementManager = require("elementManager") +local VisualElement = elementManager.getElement("VisualElement") +local tHex = require("libraries/colorHex") +---@configDescription An element that displays an image in bimg format +---@configDefault false + +--- This is the Image element class which can be used to display bimg format images. +--- The bimg format is a universal ComputerCraft image format. +--- See: https://github.com/SkyTheCodeMaster/bimg +---@class Image : VisualElement +local Image = setmetatable({}, VisualElement) +Image.__index = Image + +---@property bimg table {} The bimg image data +Image.defineProperty(Image, "bimg", {default = {}, type = "table", canTriggerRender = true}) +---@property currentFrame number 1 Current animation frame +Image.defineProperty(Image, "currentFrame", {default = 1, type = "number", canTriggerRender = true}) +---@property metadata table {} Image metadata (version, palette, etc) +Image.defineProperty(Image, "metadata", {default = {}, type = "table"}) + +function Image.new() + local self = setmetatable({}, Image):__init() + return self +end + +function Image:init(props, basalt) + VisualElement.init(self, props, basalt) + self.set("type", "Image") + return self +end + +--- Loads a bimg format image +--- @param bimgData table The bimg image data +function Image:loadBimg(bimgData) + if type(bimgData) ~= "table" then return self end + + local frames = {} + local metadata = {} + + for k, v in pairs(bimgData) do + if type(k) == "number" then + frames[k] = v + else + metadata[k] = v + end + end + + self.set("bimg", frames) + self.set("metadata", metadata) + + if frames[1] and frames[1][1] then + self.set("width", #frames[1][1][2]) + self.set("height", #frames[1]) + end + + return self +end + +--- Gets pixel information at position +--- @param x number X position +--- @param y number Y position +--- @return number? fg Foreground color +--- @return number? bg Background color +--- @return string? char Character at position +function Image:getPixelData(x, y) + local frame = self.get("bimg")[self.get("currentFrame")] + if not frame or not frame[y] then return end + + local text = frame[y][1] + local fg = frame[y][2] + local bg = frame[y][3] + + if not text or not fg or not bg then return end + + local fgColor = tonumber(fg:sub(x,x), 16) + local bgColor = tonumber(bg:sub(x,x), 16) + local char = text:sub(x,x) + + return fgColor, bgColor, char +end + +--- Sets character at position +--- @param x number X position +--- @param y number Y position +--- @param char string Single character to set +function Image:setChar(x, y, char) + if type(char) ~= "string" or #char ~= 1 then return self end + + local frame = self.get("bimg")[self.get("currentFrame")] + if not frame then + frame = {{}, {}, {}} + self.get("bimg")[self.get("currentFrame")] = frame + end + + if not frame[y] then + frame[y] = {"", "", ""} + end + + local text = frame[y][1] + while #text < x do + text = text .. " " + end + + frame[y][1] = text:sub(1, x-1) .. char .. text:sub(x+1) + self:updateRender() + return self +end + +--- Sets foreground color at position +--- @param x number X position +--- @param y number Y position +--- @param color number Color value (0-15) +function Image:setFg(x, y, color) + if type(color) ~= "number" then return self end + + local frame = self.get("bimg")[self.get("currentFrame")] + if not frame then + frame = {{}, {}, {}} + self.get("bimg")[self.get("currentFrame")] = frame + end + + if not frame[y] then + frame[y] = {"", "", ""} + end + + local fg = frame[y][2] + while #fg < x do + fg = fg .. "f" + end + + frame[y][2] = fg:sub(1, x-1) .. tHex[color] .. fg:sub(x+1) + self:updateRender() + return self +end + +--- Sets background color at position +--- @param x number X position +--- @param y number Y position +--- @param color number Color value (0-15) +function Image:setBg(x, y, color) + if type(color) ~= "number" then return self end + + local frame = self.get("bimg")[self.get("currentFrame")] + if not frame then + frame = {{}, {}, {}} + self.get("bimg")[self.get("currentFrame")] = frame + end + + if not frame[y] then + frame[y] = {"", "", ""} + end + + local bg = frame[y][3] + while #bg < x do + bg = bg .. "f" + end + + frame[y][3] = bg:sub(1, x-1) .. tHex[color] .. bg:sub(x+1) + self:updateRender() + return self +end + +--- Sets all properties at position +--- @param x number X position +--- @param y number Y position +--- @param char string? Character to set (optional) +--- @param fg number? Foreground color (optional) +--- @param bg number? Background color (optional) +function Image:setPixel(x, y, char, fg, bg) + if char then self:setChar(x, y, char) end + if fg then self:setFg(x, y, fg) end + if bg then self:setBg(x, y, bg) end + return self +end + +function Image:nextFrame() + if not self.get("metadata").animation then return end + + local frames = self.get("bimg") + local current = self.get("currentFrame") + local next = current + 1 + if next > #frames then next = 1 end + + self.set("currentFrame", next) + return self +end + +function Image:render() + VisualElement.render(self) + + local frame = self.get("bimg")[self.get("currentFrame")] + if not frame then return end + + for y, line in ipairs(frame) do + local text = line[1] + local fg = line[2] + local bg = line[3] + + if text and fg and bg then + self:blit(1, y, text, fg, bg) + end + end +end + +return Image diff --git a/src/elements/Input.lua b/src/elements/Input.lua index 62462ac..b2ea1c4 100644 --- a/src/elements/Input.lua +++ b/src/elements/Input.lua @@ -1,7 +1,6 @@ local VisualElement = require("elements/VisualElement") local tHex = require("libraries/colorHex") ---@configDescription A text input field with various features ----@configDefault true --- 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. diff --git a/src/elements/Label.lua b/src/elements/Label.lua index 79911f8..1ee58bf 100644 --- a/src/elements/Label.lua +++ b/src/elements/Label.lua @@ -1,6 +1,7 @@ local elementManager = require("elementManager") local VisualElement = elementManager.getElement("VisualElement") local wrapText = require("libraries/utils").wrapText +---@configDescription A simple text display element that automatically resizes its width based on the text content. --- This is the label class. It provides a simple text display element that automatically --- resizes its width based on the text content. diff --git a/src/elements/List.lua b/src/elements/List.lua index 9214e1d..b927caa 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -1,6 +1,5 @@ local VisualElement = require("elements/VisualElement") ---@configDescription A scrollable list of selectable items ----@configDefault true --- This is the list class. It provides a scrollable list of selectable items with support for --- custom item rendering, separators, and selection handling. @@ -151,7 +150,7 @@ function List:mouse_scroll(direction, x, y) if self:isInBounds(x, y) then local offset = self.get("offset") local maxOffset = math.max(0, #self.get("items") - self.get("height")) - + offset = math.min(maxOffset, math.max(0, offset + direction)) self.set("offset", offset) return true diff --git a/src/elements/Menu.lua b/src/elements/Menu.lua index a5fabfd..f770199 100644 --- a/src/elements/Menu.lua +++ b/src/elements/Menu.lua @@ -1,7 +1,7 @@ local VisualElement = require("elements/VisualElement") local List = require("elements/List") local tHex = require("libraries/colorHex") ----@configDescription A horizontal menu bar with selectable +---@configDescription A horizontal menu bar with selectable items. --- This is the menu class. It provides a horizontal menu bar with selectable items. --- Menu items are displayed in a single row and can have custom colors and callbacks. diff --git a/src/elements/Program.lua b/src/elements/Program.lua index a4263e4..2fa9624 100644 --- a/src/elements/Program.lua +++ b/src/elements/Program.lua @@ -2,17 +2,18 @@ local elementManager = require("elementManager") local VisualElement = elementManager.getElement("VisualElement") local errorManager = require("errorManager") ---TODO: --- Rendering optimization (only render when screen changed) --- Eventsystem improvement --- Cursor is sometimes not visible on time +--- @configDescription A program that runs in a window +--- This is the program class. It provides a program that runs in a window. ---@class Program : VisualElement local Program = setmetatable({}, VisualElement) Program.__index = Program +--- @property program table nil The program instance Program.defineProperty(Program, "program", {default = nil, type = "table"}) +--- @property path string "" The path to the program Program.defineProperty(Program, "path", {default = "", type = "string"}) +--- @property running boolean false Whether the program is running Program.defineProperty(Program, "running", {default = false, type = "boolean"}) Program.defineEvent(Program, "key") @@ -28,6 +29,7 @@ local BasaltProgram = {} BasaltProgram.__index = BasaltProgram local newPackage = dofile("rom/modules/main/cc/require.lua").make +---@private function BasaltProgram.new() local self = setmetatable({}, BasaltProgram) self.env = {} @@ -35,6 +37,7 @@ function BasaltProgram.new() return self end +---@private function BasaltProgram:run(path, width, height) self.window = window.create(term.current(), 1, 1, width, height, false) local pPath = shell.resolveProgram(path) @@ -79,10 +82,12 @@ function BasaltProgram:run(path, width, height) end end +---@private function BasaltProgram:resize(width, height) self.window.reposition(1, 1, width, height) end +---@private function BasaltProgram:resume(event, ...) if self.coroutine==nil or coroutine.status(self.coroutine)=="dead" then return end if(self.filter~=nil)then @@ -103,11 +108,13 @@ function BasaltProgram:resume(event, ...) return ok, result end +---@private function BasaltProgram:stop() end --- Creates a new Program instance +--- @shortDescription Creates a new Program instance --- @return Program object The newly created Program instance --- @usage local element = Program.new("myId", basalt) function Program.new() @@ -118,11 +125,21 @@ function Program.new() return self end +--- Initializes the Program instanceProperty +--- @shortDescription Initializes the Program instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Program self The initialized instance function Program:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Program") + return self end +--- Executes a program +--- @shortDescription Executes a program +--- @param path string The path to the program +--- @return Program self The Program instance function Program:execute(path) self.set("path", path) self.set("running", true) @@ -133,6 +150,11 @@ function Program:execute(path) return self end +--- Handles all incomming events +--- @shortDescription Handles all incomming events +--- @param event string The event to handle +--- @param ... any The event arguments +--- @return any result The event result function Program:dispatchEvent(event, ...) local program = self.get("program") local result = VisualElement.dispatchEvent(self, event, ...) @@ -148,6 +170,8 @@ function Program:dispatchEvent(event, ...) return result end +--- Gets called when the element gets focused +--- @shortDescription Gets called when the element gets focused function Program:focus() if(VisualElement.focus(self))then local program = self.get("program") @@ -159,6 +183,8 @@ function Program:focus() end end +--- Renders the program +--- @shortDescription Renders the program function Program:render() VisualElement.render(self) local program = self.get("program") diff --git a/src/elements/Scrollbar.lua b/src/elements/Scrollbar.lua index 7ff0b5b..c386765 100644 --- a/src/elements/Scrollbar.lua +++ b/src/elements/Scrollbar.lua @@ -54,6 +54,11 @@ function Scrollbar.new() return self end +--- Initializes the Scrollbar instance +--- @shortDescription Initializes the Scrollbar instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return Scrollbar self The initialized instance function Scrollbar:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Scrollbar") @@ -61,6 +66,7 @@ function Scrollbar:init(props, basalt) end --- Attaches the scrollbar to an element's property +--- @shortDescription Attaches the scrollbar to an element's property --- @param element BaseElement The element to attach to --- @param config table Configuration {property = "propertyName", min = number|function, max = number|function} --- @return Scrollbar self The scrollbar instance @@ -72,6 +78,9 @@ function Scrollbar:attach(element, config) return self end +--- Updates the attached element's property based on the scrollbar value +--- @shortDescription Updates the attached element's property based on the scrollbar value +--- @return Scrollbar self The scrollbar instance function Scrollbar:updateAttachedElement() local element = self.get("attachedElement") if not element then return end @@ -85,6 +94,7 @@ function Scrollbar:updateAttachedElement() local mappedValue = min + (value / 100) * (max - min) element.set(self.get("attachedProperty"), math.floor(mappedValue + 0.5)) + return self end local function getScrollbarSize(self) @@ -96,6 +106,12 @@ local function getRelativeScrollPosition(self, x, y) return self.get("orientation") == "vertical" and relY or relX end +--- Handles mouse click events +--- @shortDescription Handles mouse click events +--- @param button number The mouse button 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 Scrollbar:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local size = getScrollbarSize(self) @@ -116,6 +132,12 @@ function Scrollbar:mouse_click(button, x, y) end end +--- Handles mouse drag events +--- @shortDescription Handles mouse drag events +--- @param button number The mouse button being dragged +--- @param x number The x position of the drag +--- @param y number The y position of the drag +--- @return boolean Whether the event was handled function Scrollbar:mouse_drag(button, x, y) if(VisualElement.mouse_drag(self, button, x, y))then local size = getScrollbarSize(self) @@ -134,6 +156,12 @@ function Scrollbar:mouse_drag(button, x, y) end end +--- Handles mouse scroll events +--- @shortDescription Handles mouse scroll events +--- @param direction number The scroll direction (1 for up, -1 for down) +--- @param x number The x position of the scroll +--- @param y number The y position of the scroll +--- @return boolean Whether the event was handled function Scrollbar:mouse_scroll(direction, x, y) if not self:isInBounds(x, y) then return false end direction = direction > 0 and -1 or 1 @@ -146,6 +174,8 @@ function Scrollbar:mouse_scroll(direction, x, y) return true end +--- Renders the Scrollbar +--- @shortDescription Renders the scrollbar function Scrollbar:render() VisualElement.render(self) diff --git a/src/elements/Slider.lua b/src/elements/Slider.lua index 4668971..4a539d2 100644 --- a/src/elements/Slider.lua +++ b/src/elements/Slider.lua @@ -74,6 +74,12 @@ function Slider:mouse_click(button, x, y) end Slider.mouse_drag = Slider.mouse_click +--- Handles mouse release events +--- @shortDescription Handles mouse release events +--- @param button number The mouse button that was released +--- @param x number The x position of the release +--- @param y number The y position of the release +--- @return boolean handled Whether the event was handled function Slider:mouse_scroll(direction, x, y) if self:isInBounds(x, y) then local step = self.get("step") diff --git a/src/elements/Table.lua b/src/elements/Table.lua index ddea898..48fa629 100644 --- a/src/elements/Table.lua +++ b/src/elements/Table.lua @@ -52,28 +52,6 @@ function Table:init(props, basalt) 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 diff --git a/src/elements/TextBox.lua b/src/elements/TextBox.lua index fe19502..e7dcf2e 100644 --- a/src/elements/TextBox.lua +++ b/src/elements/TextBox.lua @@ -30,6 +30,9 @@ TextBox.defineEvent(TextBox, "key") TextBox.defineEvent(TextBox, "char") TextBox.defineEvent(TextBox, "mouse_scroll") +--- Creates a new TextBox instance +--- @shortDescription Creates a new TextBox instance +--- @return TextBox self The newly created TextBox instance function TextBox.new() local self = setmetatable({}, TextBox):__init() self.set("width", 20) @@ -37,6 +40,11 @@ function TextBox.new() return self end +--- Initializes the TextBox instance +--- @shortDescription Initializes the TextBox instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance +--- @return TextBox self The initialized instance function TextBox:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "TextBox") @@ -44,6 +52,7 @@ function TextBox:init(props, basalt) end --- Adds a new syntax highlighting pattern +--- @shortDescription Adds a new syntax highlighting pattern --- @param pattern string The regex pattern to match --- @param color colors The color to apply function TextBox:addSyntaxPattern(pattern, color) @@ -98,6 +107,9 @@ local function backspace(self) self:updateRender() end +--- Updates the viewport to keep the cursor in view +--- @shortDescription Updates the viewport to keep the cursor in view +--- @return TextBox self The TextBox instance function TextBox:updateViewport() local cursorX = self.get("cursorX") local cursorY = self.get("cursorY") @@ -119,14 +131,23 @@ function TextBox:updateViewport() elseif cursorY - scrollY < 1 then self.set("scrollY", cursorY - 1) end + return self end +--- Handles character input +--- @shortDescription Handles character input +--- @param char string The character that was typed +--- @return boolean handled Whether the event was handled function TextBox:char(char) if not self.get("editable") or not self.get("focused") then return false end insertChar(self, char) 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 TextBox:key(key) if not self.get("editable") or not self.get("focused") then return false end local lines = self.get("lines") @@ -163,6 +184,12 @@ function TextBox:key(key) return true end +--- Handles mouse scroll events +--- @shortDescription Handles mouse scroll events +--- @param direction number The scroll direction +--- @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 TextBox:mouse_scroll(direction, x, y) if self:isInBounds(x, y) then local scrollY = self.get("scrollY") @@ -172,7 +199,7 @@ function TextBox:mouse_scroll(direction, x, y) local maxScroll = math.max(0, #lines - height + 2) local newScroll = math.max(0, math.min(maxScroll, scrollY + direction)) - + self.set("scrollY", newScroll) self:updateRender() return true @@ -180,6 +207,12 @@ function TextBox:mouse_scroll(direction, x, y) return false 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 TextBox:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) @@ -199,6 +232,10 @@ function TextBox:mouse_click(button, x, y) return false end +--- Sets the text of the TextBox +--- @shortDescription Sets the text of the TextBox +--- @param text string The text to set +--- @return TextBox self The TextBox instance function TextBox:setText(text) local lines = {} if text == "" then @@ -212,6 +249,9 @@ function TextBox:setText(text) return self end +--- Gets the text of the TextBox +--- @shortDescription Gets the text of the TextBox +--- @return string text The text of the TextBox function TextBox:getText() return table.concat(self.get("lines"), "\n") end @@ -220,7 +260,7 @@ 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 @@ -234,6 +274,8 @@ local function applySyntaxHighlighting(self, line) return text, colors end +--- Renders the TextBox +--- @shortDescription Renders the TextBox with syntax highlighting function TextBox:render() VisualElement.render(self) diff --git a/src/elements/Tree.lua b/src/elements/Tree.lua index 10d0ae7..851e68c 100644 --- a/src/elements/Tree.lua +++ b/src/elements/Tree.lua @@ -1,5 +1,6 @@ local VisualElement = require("elements/VisualElement") local sub = string.sub +---@cofnigDescription The tree element provides a hierarchical view of nodes that can be expanded and collapsed, with support for selection and scrolling. --- This is the tree class. It provides a hierarchical view of nodes that can be expanded and collapsed, --- with support for selection and scrolling. @@ -8,7 +9,12 @@ 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}) +Tree.defineProperty(Tree, "nodes", {default = {}, type = "table", canTriggerRender = true, setter = function(self, value) + if #value > 0 then + self.get("expandedNodes")[value[1]] = true + end + return value +end}) ---@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 @@ -48,19 +54,6 @@ function Tree:init(props, basalt) 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 - self.get("expandedNodes")[nodes[1]] = true - end - return self -end - --- Expands a node --- @shortDescription Expands a node to show its children --- @param node table The node to expand @@ -107,6 +100,12 @@ local function flattenTree(nodes, expandedNodes, level, result) return result end +--- Handles mouse click events +--- @shortDescription Handles mouse click events for node selection and expansion +--- @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 Tree:mouse_click(button, x, y) if VisualElement.mouse_click(self, button, x, y) then local relX, relY = self:getRelativePosition(x, y) @@ -137,7 +136,12 @@ function Tree:onSelect(callback) return self end ----@private +--- Handles mouse scroll events +--- @shortDescription Handles mouse scroll events for vertical scrolling +--- @param direction number The scroll direction (1 for up, -1 for 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 Tree:mouse_scroll(direction, x, y) if VisualElement.mouse_scroll(self, direction, x, y) then local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes")) @@ -163,6 +167,8 @@ function Tree:getNodeSize() return width, height end +--- Renders the tree +--- @shortDescription Renders the tree with nodes, selection and scrolling function Tree:render() VisualElement.render(self) diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index d635fa7..a5c8dcc 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -2,7 +2,6 @@ local elementManager = require("elementManager") local BaseElement = elementManager.getElement("BaseElement") local tHex = require("libraries/colorHex") ---@configDescription The Visual Element class which is the base class for all visual UI elements ----@configDefault true --- 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. @@ -79,24 +78,28 @@ VisualElement.combineProperties(VisualElement, "size", "width", "height") ---@combinedProperty color {foreground background} Combined foreground, background colors VisualElement.combineProperties(VisualElement, "color", "foreground", "background") ----@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 onClick {button, x, y} Fired on mouse click +---@event onMouseUp {button, x, y} Fired on mouse button release +---@event onRelease {button, x, y} Fired when mouse leaves while clicked +---@event onDrag {button, x, y} Fired when mouse moves while clicked +---@event onScroll {direction, x, y} Fired on mouse scroll +---@event onEnter {-} Fired when mouse enters element +---@event onLeave {-} Fired when mouse leaves element ---@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 +---@event onKey {key} Fired on key press +---@event onKeyUp {key} Fired on key release +---@event onChar {char} Fired on character input VisualElement.defineEvent(VisualElement, "focus") VisualElement.defineEvent(VisualElement, "blur") -VisualElement.registerEventCallback(VisualElement, "MouseClick", "mouse_click", "mouse_up") +VisualElement.registerEventCallback(VisualElement, "Click", "mouse_click", "mouse_up") VisualElement.registerEventCallback(VisualElement, "MouseUp", "mouse_up", "mouse_click") -VisualElement.registerEventCallback(VisualElement, "MouseDrag", "mouse_drag", "mouse_click", "mouse_up") -VisualElement.registerEventCallback(VisualElement, "MouseScroll", "mouse_scroll") -VisualElement.registerEventCallback(VisualElement, "MouseEnter", "mouse_enter", "mouse_move") +VisualElement.registerEventCallback(VisualElement, "Drag", "mouse_drag", "mouse_click", "mouse_up") +VisualElement.registerEventCallback(VisualElement, "Scroll", "mouse_scroll") +VisualElement.registerEventCallback(VisualElement, "Enter", "mouse_enter", "mouse_move") +VisualElement.registerEventCallback(VisualElement, "LeEave", "mouse_leave", "mouse_move") VisualElement.registerEventCallback(VisualElement, "Focus", "focus", "blur") VisualElement.registerEventCallback(VisualElement, "Blur", "blur", "focus")