diff --git a/install.lua b/install.lua index a523d85..d1f94ec 100644 --- a/install.lua +++ b/install.lua @@ -133,6 +133,10 @@ versionDropdown:onSelect(function(self, index, value) end end) +instalScreen:addLabel(coloring) + :setText("Path:") + :setPosition(2, "{versionDesc.y + versionDesc.height + 1}") + installScreen:addLabel(coloring) :setText("Additional Components:") :setPosition(2, "{versionDesc.y + versionDesc.height + 1}") @@ -147,21 +151,134 @@ local luaLSCheckbox = installScreen:addCheckbox() -- Screen 3: Elements local elementsScreen = createScreen(3) elementsScreen:addLabel(coloring) - :setText("Elements:") + :setText("Elements: (white = selected)") :setPosition(2, 2) -local elementsList = elementsScreen:addList() +local elementsList = elementsScreen:addList("elementsList") + :setMultiSelection(true) + :setSelectedBackground(colors.lightGray) + :setForeground(colors.gray) :setPosition(2, 4) - :setSize("{parent.width - 2}", "{parent.height - 6}") + :setSize("{parent.width - 30}", 8) + +local elementDesc = elementsScreen:addLabel("elementDesc") + :setAutoSize(false) + :setWidth("{parent.width - (elementsList.x + elementsList.width) - 2}") + :setText("Select an element to see its description.") + :setPosition("{elementsList.x + elementsList.width + 1}", 4) + :setSize(28, 8) + :setBackground(colors.lightGray) + +local eleScreenDesc = elementsScreen:addLabel() + :setAutoSize(false) + :setWidth("{parent.width - 2}") + :setText("This screen allows you to select which elements you want to install. You can select multiple elements.") + :setPosition(2, "{math.max(elementsList.y + elementsList.height, elementDesc.y + elementDesc.height) + 1}") + :setBackground(colors.lightGray) local function addElements() elementsList:clear() - for k,v in pairs(getConfig().files)do - if(k:match("src/elements/"))then - elementsList:addItem(v.name) - end + for k,v in pairs(getConfig().categories.elements.files)do + elementsList:addItem({selected=true, text=v.name, callback=function() elementDesc:setText(v.description) end}) end end addElements() +-- Screen 4 Plugins +local pluginScreen = createScreen(4) +pluginScreen:addLabel(coloring) + :setText("Plugins: (white = selected)") + :setPosition(2, 2) + +local pluginList = pluginScreen:addList("pluginList") + :setMultiSelection(true) + :setSelectedBackground(colors.lightGray) + :setForeground(colors.gray) + :setPosition(2, 4) + :setSize("{parent.width - 30}", 8) + +local pluginDesc = pluginScreen:addLabel("pluginDesc") + :setAutoSize(false) + :setWidth("{parent.width - (pluginList.x + pluginList.width) - 2}") + :setText("Select a plugin to see its description.") + :setPosition("{pluginList.x + pluginList.width + 1}", 4) + :setSize(28, 8) + :setBackground(colors.lightGray) + +local pluScreenDesc = pluginScreen:addLabel() + :setAutoSize(false) + :setWidth("{parent.width - 2}") + :setText("This screen allows you to select which plugins you want to install. You can select multiple plugins.") + :setPosition(2, "{math.max(pluginList.y + pluginList.height, pluginDesc.y + pluginDesc.height) + 1}") + :setBackground(colors.lightGray) + +local function addPlugins() + pluginList:clear() + for k,v in pairs(getConfig().categories.plugins.files)do + pluginList:addItem({selected = true, text= v.name, callback=function() pluginDesc:setText(v.description) end}) + end +end +addPlugins() + +-- Screen 5 Installation Progress +local progressScreen = createScreen(5) +local progressBar = progressScreen:addProgressBar() + :setPosition(2, "{parent.height - 2}") + :setSize("{parent.width - 12}", 2) + +local log = progressScreen:addList("log") + :setPosition(2, 2) + :setSize("{parent.width - 2}", "{parent.height - 6}") + :addItem("Starting installation...") + +local function install() + local function logMessage(message) + log:addItem(message) + end + + local function downloadFile(url, path) + logMessage("Downloading " .. url .. "...") + local request = http.get(url) + if request then + local file = fs.open(path, "w") + file.write(request.readAll()) + file.close() + request.close() + logMessage("Downloaded " .. url .. " to " .. path) + else + error("Failed to download " .. url) + end + end + + local function installElement(name, url) + logMessage("Installing element: " .. name) + --downloadFile(url, "/path/to/install/" .. name) + end + + local function installPlugin(name, url) + logMessage("Installing plugin: " .. name) + --downloadFile(url, "/path/to/install/" .. name) + end + + for _, element in ipairs(elementsList:getSelectedItems()) do + local item = element.item + basalt.LOGGER.debug(item.text) + installElement(item.text, getConfig().categories.elements.files[item.text].url) + end + + for _, plugin in ipairs(pluginList:getSelectedItems()) do + local item = plugin.item + installPlugin(item.text, getConfig().categories.plugins.files[item.text].url) + end + + progressBar:setProgress(100) + logMessage("Installation complete!") +end + +local installButton = progressScreen:addButton() + :setText("Install") + :setPosition("{parent.width - 9}", "{parent.height - 1}") + :setSize(9, 1) + :onMouseClick(install) + basalt.run() \ No newline at end of file diff --git a/src/elements/BaseElement.lua b/src/elements/BaseElement.lua index 0fe9232..0dff1e3 100644 --- a/src/elements/BaseElement.lua +++ b/src/elements/BaseElement.lua @@ -1,5 +1,7 @@ local PropertySystem = require("propertySystem") local uuid = require("libraries/utils").uuid +---@configDescription The base class for all UI elements in Basalt +---@configDefault true --- The base class for all UI elements in Basalt --- @class BaseElement : PropertySystem @@ -27,20 +29,37 @@ BaseElement.defineProperty(BaseElement, "id", {default = "", type = "string", re --- @property name string BaseElement The name of the element BaseElement.defineProperty(BaseElement, "name", {default = "", type = "string"}) ---- @property eventCallbacks table {} Table containing all registered event callbacks +--- @property eventCallbacks table BaseElement The event callbacks for the element 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 ---- @param event? string The event to handle ---- @usage BaseElement.listenTo(MyClass, "mouse_click") -function BaseElement.listenTo(class, eventName, event) - if not class._events then - class._events = {} + +function BaseElement.defineEvent(class, eventName, requiredEvent) + -- Events auf Klassenebene speichern, wie bei Properties + if not rawget(class, '_eventConfigs') then + class._eventConfigs = {} + end + + class._eventConfigs[eventName] = { + requires = requiredEvent and requiredEvent or eventName + } +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 + + 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 - class._events[eventName] = {enabled=true, name=eventName, event=event} end --- Creates a new BaseElement instance @@ -64,18 +83,37 @@ function BaseElement:init(props, basalt) self._values.id = uuid() self.basalt = basalt self._registeredEvents = {} - if BaseElement._events then - for key,event in pairs(BaseElement._events) do - self._registeredEvents[event.event or event.name] = true - local handlerName = "on" .. event.name:gsub("_(%l)", function(c) - return c:upper() - end):gsub("^%l", string.upper) - self[handlerName] = function(self, ...) - self:registerCallback(event.name, ...) + + 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 + if not events[eventName] then + events[eventName] = config + end + end + end + currentClass = getmetatable(currentClass) and getmetatable(currentClass).__index + end + + for eventName, config in pairs(events) do + self._registeredEvents[config.requires] = true + end + + if self._callbacks then + for eventName, methodName in pairs(self._callbacks) do + self[methodName] = function(self, ...) + self:registerCallback(eventName, ...) return self end end - end + end + return self end @@ -155,8 +193,8 @@ end --- @return table self The BaseElement instance --- @usage element:fireEvent("mouse_click", 1, 2) function BaseElement:fireEvent(event, ...) - if self._values.eventCallbacks[event] then - for _, callback in ipairs(self._values.eventCallbacks[event]) do + if self.get("eventCallbacks")[event] then + for _, callback in ipairs(self.get("eventCallbacks")[event]) do local result = callback(self, ...) return result end diff --git a/src/elements/BaseFrame.lua b/src/elements/BaseFrame.lua index e805793..6e85f96 100644 --- a/src/elements/BaseFrame.lua +++ b/src/elements/BaseFrame.lua @@ -2,6 +2,8 @@ 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 diff --git a/src/elements/Button.lua b/src/elements/Button.lua index 9d06724..997cc96 100644 --- a/src/elements/Button.lua +++ b/src/elements/Button.lua @@ -2,7 +2,9 @@ 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 +---@configDescription Standard button element with click handling and state management local Button = setmetatable({}, VisualElement) Button.__index = Button @@ -10,25 +12,35 @@ Button.__index = Button Button.defineProperty(Button, "text", {default = "Button", type = "string", canTriggerRender = true}) ---@event mouse_click The event that is triggered when the button is clicked -Button.listenTo(Button, "mouse_click") +Button.defineEvent(Button, "mouse_click") +Button.defineEvent(Button, "mouse_up") -function Button.new(props, basalt) +--- 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() - self:init(props, basalt) self.set("width", 10) self.set("height", 3) self.set("z", 5) return self end +--- Initializes the Button instance +--- @shortDescription Initializes the Button instance +--- @param props table The properties to initialize the element with +--- @param basalt table The basalt instance function Button:init(props, basalt) VisualElement.init(self, props, basalt) self.set("type", "Button") end +--- Renders the Button +--- @shortDescription Renders the Button function Button:render() VisualElement.render(self) local text = self.get("text") + text = text:sub(1, self.get("width")) local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height")) self:textFg(xO, yO, text, self.get("foreground")) end diff --git a/src/elements/Checkbox.lua b/src/elements/Checkbox.lua index 53db063..a7c82ff 100644 --- a/src/elements/Checkbox.lua +++ b/src/elements/Checkbox.lua @@ -28,7 +28,7 @@ end}) ---@property autoSize boolean true Whether to automatically size the checkbox Checkbox.defineProperty(Checkbox, "autoSize", {default = true, type = "boolean"}) -Checkbox.listenTo(Checkbox, "mouse_click") +Checkbox.defineEvent(Checkbox, "mouse_click") --- Creates a new Checkbox instance --- @shortDescription Creates a new Checkbox instance diff --git a/src/elements/Container.lua b/src/elements/Container.lua index cc4f749..c8e8581 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -418,14 +418,15 @@ function Container:mouse_drag(button, x, y) end function Container:mouse_scroll(direction, x, y) - if VisualElement.mouse_scroll(self, direction, x, y) then - local args = convertMousePosition(self, "mouse_scroll", direction, x, y) - local success, child = self:callChildrenEvents(true, "mouse_scroll", table.unpack(args)) - if(success)then - return true - end - return false + local args = convertMousePosition(self, "mouse_scroll", direction, x, y) + local success, child = self:callChildrenEvents(true, "mouse_scroll", table.unpack(args)) + if(success)then + return true end + if(VisualElement.mouse_scroll(self, direction, x, y))then + return true + end + return false end --- Handles key events @@ -504,6 +505,27 @@ function Container:textFg(x, y, text, fg) return self end +--- Draws a line of text and bg as color, it is usually used in the render loop +--- @shortDescription Draws a line of text and bg 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 +--- @param bg color The background color of the text +--- @return Container self The container instance +function Container:textBg(x, y, text, bg) + local w, h = self.get("width"), self.get("height") + + if y < 1 or y > h then return self end + + local textStart = x < 1 and (2 - x) or 1 + local textLen = math.min(#text - textStart + 1, w - math.max(1, x) + 1) + + if textLen <= 0 then return self end + + VisualElement.textBg(self, math.max(1, x), math.max(1, y), text:sub(textStart, textStart + textLen - 1), bg) + return self +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 diff --git a/src/elements/Dropdown.lua b/src/elements/Dropdown.lua index 10bcad8..d4cfc40 100644 --- a/src/elements/Dropdown.lua +++ b/src/elements/Dropdown.lua @@ -2,8 +2,12 @@ local VisualElement = require("elements/VisualElement") local List = require("elements/List") local tHex = require("libraries/colorHex") +---@configDescription A dropdown menu that shows a list of selectable items +---@configDefault false + --- 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 @@ -59,26 +63,12 @@ function Dropdown:mouse_click(button, x, y) end return true elseif self.get("isOpen") and relY > 1 then - local index = relY - 1 + self.get("offset") - local items = self.get("items") - - if index <= #items then - local item = items[index] - if type(item) == "table" and item.separator then - return false - end - - self.set("selectedIndex", index) - self.set("isOpen", false) - self.set("height", 1) - - if type(item) == "table" and item.callback then - item.callback(self) - end - - self:fireEvent("select", index, item) - return true - end + -- Nutze List's mouse_click für Item-Selektion + List.mouse_click(self, button, x, y) + -- Nach Selektion Dropdown schließen + self.set("isOpen", false) + self.set("height", 1) + return true end return false end @@ -88,52 +78,31 @@ end 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) + if #text == 0 then + -- Suche nach selektiertem Item + local selectedItems = self:getSelectedItems() + if #selectedItems > 0 then + local selectedItem = selectedItems[1].item + text = selectedItem.text or "" + end end + -- Header mit Dropdown Symbol 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"))) + -- Liste 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") - - for i = 2, self.get("height") do - local itemIndex = i - 1 + offset - local item = items[itemIndex] - - if item then - if type(item) == "table" and item.separator then - local separatorChar = (item.text or "-"):sub(1,1) - local separatorText = string.rep(separatorChar, width) - local fg = item.foreground or self.get("foreground") - local bg = item.background or self.get("background") - - self:textBg(1, i, string.rep(" ", width), bg) - self:textFg(1, i, separatorText, fg) - 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")) - - local fg = isSelected and - (item.selectedForeground or colors.white) or - (item.foreground or self.get("foreground")) - - self:textBg(1, i, string.rep(" ", width), bg) - self:textFg(1, i, itemText, fg) - end - end - end + -- Offset um 1 verschieben wegen Header + local oldOffset = self.get("offset") + self.set("offset", oldOffset + 1) + -- Liste ab Zeile 2 rendern + List.render(self) + -- Offset zurücksetzen + self.set("offset", oldOffset) end end diff --git a/src/elements/Frame.lua b/src/elements/Frame.lua index 829c3b8..952d987 100644 --- a/src/elements/Frame.lua +++ b/src/elements/Frame.lua @@ -6,9 +6,6 @@ local Container = elementManager.getElement("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 --- @shortDescription Creates a new Frame instance --- @return Frame self The newly created Frame instance diff --git a/src/elements/Input.lua b/src/elements/Input.lua index e86175b..76310c6 100644 --- a/src/elements/Input.lua +++ b/src/elements/Input.lua @@ -26,9 +26,9 @@ Input.defineProperty(Input, "pattern", {default = nil, type = "string"}) ---@property cursorColor number nil Color of the cursor Input.defineProperty(Input, "cursorColor", {default = nil, type = "number"}) -Input.listenTo(Input, "mouse_click") -Input.listenTo(Input, "key") -Input.listenTo(Input, "char") +Input.defineEvent(Input, "mouse_click") +Input.defineEvent(Input, "key") +Input.defineEvent(Input, "char") --- Creates a new Input instance --- @shortDescription Creates a new Input instance diff --git a/src/elements/List.lua b/src/elements/List.lua index 124335b..d164b82 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -6,20 +6,22 @@ local VisualElement = require("elements/VisualElement") local List = setmetatable({}, VisualElement) List.__index = List ----@property items table {} List of items to display. Items can be strings or tables with properties +---@property items table {} List of items to display. Items can be tables with properties including selected state List.defineProperty(List, "items", {default = {}, type = "table", canTriggerRender = true}) ----@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 true Whether items in the list can be selected List.defineProperty(List, "selectable", {default = true, type = "boolean"}) +---@property multiSelection boolean false Whether multiple items can be selected at once +List.defineProperty(List, "multiSelection", {default = false, type = "boolean"}) ---@property offset number 0 Current scroll offset for viewing long lists List.defineProperty(List, "offset", {default = 0, type = "number", canTriggerRender = true}) ----@property selectedColor color blue Background color for the selected item -List.defineProperty(List, "selectedColor", {default = colors.blue, type = "number"}) +---@property selectedBackground color blue Background color for selected items +List.defineProperty(List, "selectedBackground", {default = colors.blue, type = "number"}) +---@property selectedForeground color white Text color for selected items +List.defineProperty(List, "selectedForeground", {default = colors.white, type = "number"}) ---@event onSelect {index number, item any} Fired when an item is selected -List.listenTo(List, "mouse_click") -List.listenTo(List, "mouse_scroll") +List.defineEvent(List, "mouse_click") +List.defineEvent(List, "mouse_scroll") --- Creates a new List instance --- @shortDescription Creates a new List instance @@ -76,11 +78,24 @@ end --- @usage list:clear() function List:clear() self.set("items", {}) - self.set("selectedIndex", 0) self:updateRender() return self end +-- Gets the currently selected items +--- @shortDescription Gets the currently selected items +--- @return table selected List of selected items +--- @usage local selected = list:getSelectedItems() +function List:getSelectedItems() + local selected = {} + for i, item in ipairs(self.get("items")) do + if type(item) == "table" and item.selected then + table.insert(selected, {index = i, item = item}) + end + end + return selected +end + --- Handles mouse click events --- @shortDescription Handles mouse click events --- @param button number The mouse button that was clicked @@ -90,15 +105,27 @@ end 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) - local adjustedIndex = index + self.get("offset") local items = self.get("items") if adjustedIndex <= #items then local item = items[adjustedIndex] - self.set("selectedIndex", adjustedIndex) + if type(item) == "string" then + item = {text = item} + items[adjustedIndex] = item + end - if type(item) == "table" and item.callback then + if not self.get("multiSelection") then + for _, otherItem in ipairs(items) do + if type(otherItem) == "table" then + otherItem.selected = false + end + end + end + + item.selected = not item.selected + + if item.callback then item.callback(self) end @@ -146,7 +173,6 @@ function List:render() local items = self.get("items") local height = self.get("height") local offset = self.get("offset") - local selected = self.get("selectedIndex") local width = self.get("width") for i = 1, height do @@ -154,7 +180,12 @@ function List:render() local item = items[itemIndex] if item then - if type(item) == "table" and item.separator then + if type(item) == "string" then + item = {text = item} + items[itemIndex] = item + end + + if item.separator then local separatorChar = (item.text or "-"):sub(1,1) local separatorText = string.rep(separatorChar, width) local fg = item.foreground or self.get("foreground") @@ -163,15 +194,15 @@ function List:render() self:textBg(1, i, string.rep(" ", width), bg) self:textFg(1, i, separatorText, fg) else - local text = type(item) == "table" and item.text or item - local isSelected = itemIndex == selected + local text = item.text + local isSelected = item.selected local bg = isSelected and - (item.selectedBackground or self.get("selectedColor")) or + (item.selectedBackground or self.get("selectedBackground")) or (item.background or self.get("background")) local fg = isSelected and - (item.selectedForeground or colors.white) or + (item.selectedForeground or self.get("selectedForeground")) or (item.foreground or self.get("foreground")) self:textBg(1, i, string.rep(" ", width), bg) diff --git a/src/elements/Menu.lua b/src/elements/Menu.lua index d6b3d08..7a3d005 100644 --- a/src/elements/Menu.lua +++ b/src/elements/Menu.lua @@ -63,15 +63,19 @@ function Menu:render() VisualElement.render(self) local currentX = 1 - for i, item in ipairs(self.get("items")) do - local isSelected = i == self.get("selectedIndex") + for _, item in ipairs(self.get("items")) do + if type(item) == "string" then + item = {text = " "..item.." "} + self.get("items")[i] = item + end + local isSelected = item.selected local fg = item.selectable == false and self.get("separatorColor") or - (isSelected and (item.selectedForeground or self.get("foreground")) or + (isSelected and (item.selectedForeground or self.get("selectedForeground")) or (item.foreground or self.get("foreground"))) local bg = isSelected and - (item.selectedBackground or self.get("selectedColor")) or + (item.selectedBackground or self.get("selectedBackground")) or (item.background or self.get("background")) self:blit(currentX, 1, item.text, @@ -97,12 +101,26 @@ function Menu:mouse_click(button, x, y) for i, item in ipairs(self.get("items")) do if relX >= currentX and relX < currentX + #item.text then if item.selectable ~= false then - self.set("selectedIndex", i) - if type(item) == "table" then - if item.callback then - item.callback(self) + if type(item) == "string" then + item = {text = item} + self.get("items")[i] = item + end + + -- Wenn kein Multi-Selection, alle anderen deselektieren + if not self.get("multiSelection") then + for _, otherItem in ipairs(self.get("items")) do + if type(otherItem) == "table" then + otherItem.selected = false + end end end + + -- Toggle Selection + item.selected = not item.selected + + if item.callback then + item.callback(self) + end self:fireEvent("select", i, item) end return true diff --git a/src/elements/Program.lua b/src/elements/Program.lua index 081750f..a4263e4 100644 --- a/src/elements/Program.lua +++ b/src/elements/Program.lua @@ -15,14 +15,14 @@ Program.defineProperty(Program, "program", {default = nil, type = "table"}) Program.defineProperty(Program, "path", {default = "", type = "string"}) Program.defineProperty(Program, "running", {default = false, type = "boolean"}) -Program.listenTo(Program, "key") -Program.listenTo(Program, "char") -Program.listenTo(Program, "key_up") -Program.listenTo(Program, "paste") -Program.listenTo(Program, "mouse_click") -Program.listenTo(Program, "mouse_drag") -Program.listenTo(Program, "mouse_scroll") -Program.listenTo(Program, "mouse_up") +Program.defineEvent(Program, "key") +Program.defineEvent(Program, "char") +Program.defineEvent(Program, "key_up") +Program.defineEvent(Program, "paste") +Program.defineEvent(Program, "mouse_click") +Program.defineEvent(Program, "mouse_drag") +Program.defineEvent(Program, "mouse_scroll") +Program.defineEvent(Program, "mouse_up") local BasaltProgram = {} BasaltProgram.__index = BasaltProgram diff --git a/src/elements/Scrollbar.lua b/src/elements/Scrollbar.lua index 28ca443..7ff0b5b 100644 --- a/src/elements/Scrollbar.lua +++ b/src/elements/Scrollbar.lua @@ -38,10 +38,10 @@ Scrollbar.defineProperty(Scrollbar, "orientation", {default = "vertical", type = ---@property handleSize number 2 Size of the scrollbar handle in characters Scrollbar.defineProperty(Scrollbar, "handleSize", {default = 2, type = "number", canTriggerRender = true}) -Scrollbar.listenTo(Scrollbar, "mouse_click") -Scrollbar.listenTo(Scrollbar, "mouse_release") -Scrollbar.listenTo(Scrollbar, "mouse_drag") -Scrollbar.listenTo(Scrollbar, "mouse_scroll") +Scrollbar.defineEvent(Scrollbar, "mouse_click") +Scrollbar.defineEvent(Scrollbar, "mouse_release") +Scrollbar.defineEvent(Scrollbar, "mouse_drag") +Scrollbar.defineEvent(Scrollbar, "mouse_scroll") --- Creates a new Scrollbar instance --- @shortDescription Creates a new Scrollbar instance diff --git a/src/elements/Slider.lua b/src/elements/Slider.lua index 6efd676..4668971 100644 --- a/src/elements/Slider.lua +++ b/src/elements/Slider.lua @@ -18,9 +18,9 @@ Slider.defineProperty(Slider, "barColor", {default = colors.gray, type = "number 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") +Slider.defineEvent(Slider, "mouse_click") +Slider.defineEvent(Slider, "mouse_drag") +Slider.defineEvent(Slider, "mouse_up") --- Creates a new Slider instance --- @shortDescription Creates a new Slider instance diff --git a/src/elements/Table.lua b/src/elements/Table.lua index 82cf951..ddea898 100644 --- a/src/elements/Table.lua +++ b/src/elements/Table.lua @@ -26,8 +26,8 @@ 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") +Table.defineEvent(Table, "mouse_click") +Table.defineEvent(Table, "mouse_scroll") --- Creates a new Table instance --- @shortDescription Creates a new Table instance diff --git a/src/elements/TextBox.lua b/src/elements/TextBox.lua index 7e098d5..422189e 100644 --- a/src/elements/TextBox.lua +++ b/src/elements/TextBox.lua @@ -23,10 +23,10 @@ TextBox.defineProperty(TextBox, "syntaxPatterns", {default = {}, type = "table"} ---@property cursorColor number nil Color of the cursor TextBox.defineProperty(TextBox, "cursorColor", {default = nil, type = "number"}) -TextBox.listenTo(TextBox, "mouse_click") -TextBox.listenTo(TextBox, "key") -TextBox.listenTo(TextBox, "char") -TextBox.listenTo(TextBox, "mouse_scroll") +TextBox.defineEvent(TextBox, "mouse_click") +TextBox.defineEvent(TextBox, "key") +TextBox.defineEvent(TextBox, "char") +TextBox.defineEvent(TextBox, "mouse_scroll") function TextBox.new() local self = setmetatable({}, TextBox):__init() diff --git a/src/elements/Tree.lua b/src/elements/Tree.lua index 100c199..10d0ae7 100644 --- a/src/elements/Tree.lua +++ b/src/elements/Tree.lua @@ -22,8 +22,8 @@ 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") +Tree.defineEvent(Tree, "mouse_click") +Tree.defineEvent(Tree, "mouse_scroll") --- Creates a new Tree instance --- @shortDescription Creates a new Tree instance diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index 44ad798..431c80e 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -1,6 +1,8 @@ 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. @@ -84,11 +86,16 @@ VisualElement.combineProperties(VisualElement, "color", "foreground", "backgroun ---@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") -VisualElement.listenTo(VisualElement, "mouse_enter", "mouse_move") -VisualElement.listenTo(VisualElement, "mouse_leave", "mouse_move") -VisualElement.listenTo(VisualElement, "mouse_scroll") +VisualElement.defineEvent(VisualElement, "focus") +VisualElement.defineEvent(VisualElement, "blur") + +VisualElement.registerEventCallback(VisualElement, "MouseClick", "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, "Focus", "focus", "blur") +VisualElement.registerEventCallback(VisualElement, "Blur", "blur", "focus") local max, min = math.max, math.min @@ -114,7 +121,13 @@ end --- Draws multiple characters at once with colors --- @shortDescription Multi-character drawing with colors ----@protected +--- @param x number The x position to draw +--- @param y number The y position to draw +--- @param width number The width of the area to draw +--- @param height number The height of the area to draw +--- @param text string The text to draw +--- @param fg string The foreground color +--- @param bg string The background color function VisualElement:multiBlit(x, y, width, height, text, fg, bg) local xElement, yElement = self:calculatePosition() x = x + xElement - 1 diff --git a/src/plugins/animation.lua b/src/plugins/animation.lua index 576bb6a..af12e16 100644 --- a/src/plugins/animation.lua +++ b/src/plugins/animation.lua @@ -411,7 +411,7 @@ end ---@private function VisualElement.setup(element) element.defineProperty(element, "animation", {default = nil, type = "table"}) - element.listenTo(element, "timer") + element.defineEvent(element, "timer") end --- Creates a new Animation Object diff --git a/tools/generate-config.lua b/tools/generate-config.lua index 22094b4..8771b1a 100644 --- a/tools/generate-config.lua +++ b/tools/generate-config.lua @@ -6,13 +6,11 @@ local function serialize(t, indent) local result = "{\n" for k, v in pairs(t) do result = result .. indent .. " " - if type(k) == "string" then result = result .. "[\"" .. k .. "\"] = " else result = result .. "[" .. k .. "] = " end - if type(v) == "table" then result = result .. serialize(v, indent .. " ") elseif type(v) == "string" then @@ -25,33 +23,84 @@ local function serialize(t, indent) return result .. indent .. "}" end -local function extractConfigDescription(filePath) - local f = io.open(filePath, "r") - if not f then return nil end +local function parseFile(filePath) + local file = fs.open(filePath, "r") + local content = file.readAll() + file.close() - local content = f:read("*all") - f:close() + local config = { + description = "", + default = true, + requires = {} + } - return content:match("%-%-%-@configDescription%s*(.-)%s*[\n\r]") or "No description available" + -- Description aus @configDescription + local description = content:match("%-%-%-@configDescription%s*(.-)%s*\n") + if description then + config.description = description + end + + -- Default aus @configDefault + local default = content:match("%-%-%-@configDefault%s*(%w+)") + if default then + config.default = default == "true" + end + + -- Dependencies aus @requires + for required in content:gmatch("%-%-%-@requires%s*(%w+)") do + table.insert(config.requires, required) + end + + -- Dependencies aus @class inheritance + local className, parent = content:match("%-%-%-@class%s*([^%s:]+)%s*:%s*([^%s\n]+)") + if className and parent and parent ~= "PropertySystem" then + table.insert(config.requires, parent) + end + + return config end local function categorizeFile(path) - if path:match("^src/elements/") then + if path:match("^elements/") then return "elements", "UI Elements" - elseif path:match("^src/plugins/") then - return "plugins", "Plugins and Extensions" - elseif path:match("^src/libraries/") then - return "libraries", "Utility Libraries" - elseif path:match("^src/[^/]+%.lua$") then - return "core", "Core Framework Files" + elseif path:match("^plugins/") then + return "plugins", "Plugins" + elseif path:match("^libraries/") then + return "libraries", "Libraries" else - return "other", "Other Files" + return "core", "Core Files" end end -local function sortFiles(files) +local function scanDirectory(baseDir, relativePath) + local files = {} + local items = fs.list(fs.combine(baseDir, relativePath)) + + for _, item in ipairs(items) do + local fullPath = fs.combine(relativePath, item) + local absPath = fs.combine(baseDir, fullPath) + + if fs.isDir(absPath) then + for path, config in pairs(scanDirectory(baseDir, fullPath)) do + files[path] = config + end + elseif item:match("%.lua$") then + local config = parseFile(absPath) + config.name = item:gsub("%.lua$", "") + config.path = fullPath + files[fullPath] = config + end + end + + return files +end + +local function generateConfig(srcPath) + local files = scanDirectory(srcPath, "") local categories = {} - for path, info in pairs(files) do + + -- Files in Kategorien einordnen + for path, fileConfig in pairs(files) do local category, categoryDesc = categorizeFile(path) if not categories[category] then categories[category] = { @@ -59,61 +108,45 @@ local function sortFiles(files) files = {} } end - table.insert(categories[category].files, { - path = path, - name = info.name, - description = info.description - }) + categories[category].files[fileConfig.name] = { + path = fileConfig.path, + description = fileConfig.description, + default = fileConfig.default, + requires = fileConfig.requires + } end - for _, cat in pairs(categories) do - table.sort(cat.files, function(a, b) - return a.name < b.name - end) - end - - return categories -end - -local function scanDir(dir) - local files = {} - for file in io.popen('find "'..dir..'" -maxdepth 1 -type f -name "*.lua"'):lines() do - local name = file:match("([^/]+)%.lua$") - if name then - files[file] = { - name = name, - path = file:gsub("^src/", ""), - description = extractConfigDescription(file) - } + -- Dependencies validieren + for catName, cat in pairs(categories) do + for fileName, file in pairs(cat.files) do + for _, req in ipairs(file.requires or {}) do + local found = false + for _, checkCat in pairs(categories) do + if checkCat.files[req] then + found = true + break + end + end + if not found then + error(string.format("Missing dependency %s for %s", req, fileName)) + end + end end end - for file in io.popen('find "'..dir..'/elements" "'..dir..'/plugins" "'..dir..'/libraries" -type f -name "*.lua"'):lines() do - local name = file:match("([^/]+)%.lua$") - if name then - files[file] = { - name = name, - path = file:gsub("^src/", ""), - description = extractConfigDescription(file) - } - end - end - return files -end - -local sourceFiles = scanDir("src") -local categories = sortFiles(sourceFiles) - -local config = { - categories = categories, - metadata = { - generated = os.date(), - version = "2.0" + return { + categories = categories, + metadata = { + generated = os.date(), + version = "2.0" + } } -} - -local f = io.open("config.lua", "w") -if f then - f:write("return " .. serialize(config)) - f:close() end + +-- Config generieren +local config = generateConfig("/c:/Users/rjsha/AppData/Roaming/CraftOS-PC/computer/0/Basalt2/src") + +-- Config speichern +local configFile = fs.open("/c:/Users/rjsha/AppData/Roaming/CraftOS-PC/computer/0/Basalt2/config.lua", "w") +configFile.write("return " .. serialize(config)) +configFile.close()