Updated Eventsystem

Improved List with multiple Selections
This commit is contained in:
Robert Jelic
2025-02-21 18:24:19 +01:00
parent db22ae0ba3
commit 3009df028b
20 changed files with 480 additions and 228 deletions

View File

@@ -133,6 +133,10 @@ versionDropdown:onSelect(function(self, index, value)
end end
end) end)
instalScreen:addLabel(coloring)
:setText("Path:")
:setPosition(2, "{versionDesc.y + versionDesc.height + 1}")
installScreen:addLabel(coloring) installScreen:addLabel(coloring)
:setText("Additional Components:") :setText("Additional Components:")
:setPosition(2, "{versionDesc.y + versionDesc.height + 1}") :setPosition(2, "{versionDesc.y + versionDesc.height + 1}")
@@ -147,21 +151,134 @@ local luaLSCheckbox = installScreen:addCheckbox()
-- Screen 3: Elements -- Screen 3: Elements
local elementsScreen = createScreen(3) local elementsScreen = createScreen(3)
elementsScreen:addLabel(coloring) elementsScreen:addLabel(coloring)
:setText("Elements:") :setText("Elements: (white = selected)")
:setPosition(2, 2) :setPosition(2, 2)
local elementsList = elementsScreen:addList() local elementsList = elementsScreen:addList("elementsList")
:setMultiSelection(true)
:setSelectedBackground(colors.lightGray)
:setForeground(colors.gray)
:setPosition(2, 4) :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() local function addElements()
elementsList:clear() elementsList:clear()
for k,v in pairs(getConfig().files)do for k,v in pairs(getConfig().categories.elements.files)do
if(k:match("src/elements/"))then elementsList:addItem({selected=true, text=v.name, callback=function() elementDesc:setText(v.description) end})
elementsList:addItem(v.name)
end
end end
end end
addElements() 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() basalt.run()

View File

@@ -1,5 +1,7 @@
local PropertySystem = require("propertySystem") local PropertySystem = require("propertySystem")
local uuid = require("libraries/utils").uuid 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 --- The base class for all UI elements in Basalt
--- @class BaseElement : PropertySystem --- @class BaseElement : PropertySystem
@@ -27,20 +29,37 @@ BaseElement.defineProperty(BaseElement, "id", {default = "", type = "string", re
--- @property name string BaseElement The name of the element --- @property name string BaseElement The name of the element
BaseElement.defineProperty(BaseElement, "name", {default = "", type = "string"}) 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"}) 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 function BaseElement.defineEvent(class, eventName, requiredEvent)
--- @param class table The class to add the event to -- Events auf Klassenebene speichern, wie bei Properties
--- @param eventName string The name of the event to register if not rawget(class, '_eventConfigs') then
--- @param event? string The event to handle class._eventConfigs = {}
--- @usage BaseElement.listenTo(MyClass, "mouse_click") end
function BaseElement.listenTo(class, eventName, event)
if not class._events then class._eventConfigs[eventName] = {
class._events = {} 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 end
class._events[eventName] = {enabled=true, name=eventName, event=event}
end end
--- Creates a new BaseElement instance --- Creates a new BaseElement instance
@@ -64,18 +83,37 @@ function BaseElement:init(props, basalt)
self._values.id = uuid() self._values.id = uuid()
self.basalt = basalt self.basalt = basalt
self._registeredEvents = {} self._registeredEvents = {}
if BaseElement._events then
for key,event in pairs(BaseElement._events) do local currentClass = getmetatable(self).__index
self._registeredEvents[event.event or event.name] = true
local handlerName = "on" .. event.name:gsub("_(%l)", function(c) -- Events Sammeln
return c:upper() local events = {}
end):gsub("^%l", string.upper) currentClass = self
self[handlerName] = function(self, ...)
self:registerCallback(event.name, ...) 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 return self
end end
end end
end end
return self return self
end end
@@ -155,8 +193,8 @@ end
--- @return table self The BaseElement instance --- @return table self The BaseElement instance
--- @usage element:fireEvent("mouse_click", 1, 2) --- @usage element:fireEvent("mouse_click", 1, 2)
function BaseElement:fireEvent(event, ...) function BaseElement:fireEvent(event, ...)
if self._values.eventCallbacks[event] then if self.get("eventCallbacks")[event] then
for _, callback in ipairs(self._values.eventCallbacks[event]) do for _, callback in ipairs(self.get("eventCallbacks")[event]) do
local result = callback(self, ...) local result = callback(self, ...)
return result return result
end end

View File

@@ -2,6 +2,8 @@ local elementManager = require("elementManager")
local Container = elementManager.getElement("Container") local Container = elementManager.getElement("Container")
local Render = require("render") 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. --- This is the base frame class. It is the root element of all elements and the only element without a parent.
---@class BaseFrame : Container ---@class BaseFrame : Container
---@field _render Render The render object ---@field _render Render The render object

View File

@@ -2,7 +2,9 @@ local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement") local VisualElement = elementManager.getElement("VisualElement")
local getCenteredPosition = require("libraries/utils").getCenteredPosition local getCenteredPosition = require("libraries/utils").getCenteredPosition
--- This is the button class. It is a visual element that can be clicked.
---@class Button : VisualElement ---@class Button : VisualElement
---@configDescription Standard button element with click handling and state management
local Button = setmetatable({}, VisualElement) local Button = setmetatable({}, VisualElement)
Button.__index = Button Button.__index = Button
@@ -10,25 +12,35 @@ Button.__index = Button
Button.defineProperty(Button, "text", {default = "Button", type = "string", canTriggerRender = true}) Button.defineProperty(Button, "text", {default = "Button", type = "string", canTriggerRender = true})
---@event mouse_click The event that is triggered when the button is clicked ---@event mouse_click The event that is triggered when the button is clicked
Button.listenTo(Button, "mouse_click") Button.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() local self = setmetatable({}, Button):__init()
self:init(props, basalt)
self.set("width", 10) self.set("width", 10)
self.set("height", 3) self.set("height", 3)
self.set("z", 5) self.set("z", 5)
return self return self
end end
--- Initializes the Button instance
--- @shortDescription Initializes the Button instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
function Button:init(props, basalt) function Button:init(props, basalt)
VisualElement.init(self, props, basalt) VisualElement.init(self, props, basalt)
self.set("type", "Button") self.set("type", "Button")
end end
--- Renders the Button
--- @shortDescription Renders the Button
function Button:render() function Button:render()
VisualElement.render(self) VisualElement.render(self)
local text = self.get("text") local text = self.get("text")
text = text:sub(1, self.get("width"))
local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height")) local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height"))
self:textFg(xO, yO, text, self.get("foreground")) self:textFg(xO, yO, text, self.get("foreground"))
end end

View File

@@ -28,7 +28,7 @@ end})
---@property autoSize boolean true Whether to automatically size the checkbox ---@property autoSize boolean true Whether to automatically size the checkbox
Checkbox.defineProperty(Checkbox, "autoSize", {default = true, type = "boolean"}) Checkbox.defineProperty(Checkbox, "autoSize", {default = true, type = "boolean"})
Checkbox.listenTo(Checkbox, "mouse_click") Checkbox.defineEvent(Checkbox, "mouse_click")
--- Creates a new Checkbox instance --- Creates a new Checkbox instance
--- @shortDescription Creates a new Checkbox instance --- @shortDescription Creates a new Checkbox instance

View File

@@ -418,14 +418,15 @@ function Container:mouse_drag(button, x, y)
end end
function Container:mouse_scroll(direction, x, y) 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 args = convertMousePosition(self, "mouse_scroll", direction, x, y) local success, child = self:callChildrenEvents(true, "mouse_scroll", table.unpack(args))
local success, child = self:callChildrenEvents(true, "mouse_scroll", table.unpack(args)) if(success)then
if(success)then return true
return true
end
return false
end end
if(VisualElement.mouse_scroll(self, direction, x, y))then
return true
end
return false
end end
--- Handles key events --- Handles key events
@@ -504,6 +505,27 @@ function Container:textFg(x, y, text, fg)
return self return self
end 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 --- 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 --- @shortDescription Draws a line of text and fg and bg as colors
--- @param x number The x position to draw the text --- @param x number The x position to draw the text

View File

@@ -2,8 +2,12 @@ local VisualElement = require("elements/VisualElement")
local List = require("elements/List") local List = require("elements/List")
local tHex = require("libraries/colorHex") 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. --- 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 ---@class Dropdown : List
local Dropdown = setmetatable({}, List) local Dropdown = setmetatable({}, List)
Dropdown.__index = Dropdown Dropdown.__index = Dropdown
@@ -59,26 +63,12 @@ function Dropdown:mouse_click(button, x, y)
end end
return true return true
elseif self.get("isOpen") and relY > 1 then elseif self.get("isOpen") and relY > 1 then
local index = relY - 1 + self.get("offset") -- Nutze List's mouse_click für Item-Selektion
local items = self.get("items") List.mouse_click(self, button, x, y)
-- Nach Selektion Dropdown schließen
if index <= #items then self.set("isOpen", false)
local item = items[index] self.set("height", 1)
if type(item) == "table" and item.separator then return true
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
end end
return false return false
end end
@@ -88,52 +78,31 @@ end
function Dropdown:render() function Dropdown:render()
VisualElement.render(self) VisualElement.render(self)
-- Header rendern
local text = self.get("selectedText") local text = self.get("selectedText")
if #text == 0 and self.get("selectedIndex") > 0 then if #text == 0 then
local item = self.get("items")[self.get("selectedIndex")] -- Suche nach selektiertem Item
text = type(item) == "table" and item.text or tostring(item) local selectedItems = self:getSelectedItems()
if #selectedItems > 0 then
local selectedItem = selectedItems[1].item
text = selectedItem.text or ""
end
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"), 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("foreground")], self.get("width")),
string.rep(tHex[self.get("background")], self.get("width"))) string.rep(tHex[self.get("background")], self.get("width")))
-- Liste rendern wenn offen
if self.get("isOpen") then if self.get("isOpen") then
local items = self.get("items") -- Offset um 1 verschieben wegen Header
local offset = self.get("offset") local oldOffset = self.get("offset")
local selected = self.get("selectedIndex") self.set("offset", oldOffset + 1)
local width = self.get("width") -- Liste ab Zeile 2 rendern
List.render(self)
for i = 2, self.get("height") do -- Offset zurücksetzen
local itemIndex = i - 1 + offset self.set("offset", oldOffset)
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
end end
end end

View File

@@ -6,9 +6,6 @@ local Container = elementManager.getElement("Container")
local Frame = setmetatable({}, Container) local Frame = setmetatable({}, Container)
Frame.__index = Frame Frame.__index = Frame
---@event onResize {width number, height number} Fired when the frame is resized
Frame.listenTo(Frame, "resize")
--- Creates a new Frame instance --- Creates a new Frame instance
--- @shortDescription Creates a new Frame instance --- @shortDescription Creates a new Frame instance
--- @return Frame self The newly created Frame instance --- @return Frame self The newly created Frame instance

View File

@@ -26,9 +26,9 @@ Input.defineProperty(Input, "pattern", {default = nil, type = "string"})
---@property cursorColor number nil Color of the cursor ---@property cursorColor number nil Color of the cursor
Input.defineProperty(Input, "cursorColor", {default = nil, type = "number"}) Input.defineProperty(Input, "cursorColor", {default = nil, type = "number"})
Input.listenTo(Input, "mouse_click") Input.defineEvent(Input, "mouse_click")
Input.listenTo(Input, "key") Input.defineEvent(Input, "key")
Input.listenTo(Input, "char") Input.defineEvent(Input, "char")
--- Creates a new Input instance --- Creates a new Input instance
--- @shortDescription Creates a new Input instance --- @shortDescription Creates a new Input instance

View File

@@ -6,20 +6,22 @@ local VisualElement = require("elements/VisualElement")
local List = setmetatable({}, VisualElement) local List = setmetatable({}, VisualElement)
List.__index = List 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}) 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 ---@property selectable boolean true Whether items in the list can be selected
List.defineProperty(List, "selectable", {default = true, type = "boolean"}) 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 ---@property offset number 0 Current scroll offset for viewing long lists
List.defineProperty(List, "offset", {default = 0, type = "number", canTriggerRender = true}) List.defineProperty(List, "offset", {default = 0, type = "number", canTriggerRender = true})
---@property selectedColor color blue Background color for the selected item ---@property selectedBackground color blue Background color for selected items
List.defineProperty(List, "selectedColor", {default = colors.blue, type = "number"}) 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 ---@event onSelect {index number, item any} Fired when an item is selected
List.listenTo(List, "mouse_click") List.defineEvent(List, "mouse_click")
List.listenTo(List, "mouse_scroll") List.defineEvent(List, "mouse_scroll")
--- Creates a new List instance --- Creates a new List instance
--- @shortDescription Creates a new List instance --- @shortDescription Creates a new List instance
@@ -76,11 +78,24 @@ end
--- @usage list:clear() --- @usage list:clear()
function List:clear() function List:clear()
self.set("items", {}) self.set("items", {})
self.set("selectedIndex", 0)
self:updateRender() self:updateRender()
return self return self
end 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 --- Handles mouse click events
--- @shortDescription Handles mouse click events --- @shortDescription Handles mouse click events
--- @param button number The mouse button that was clicked --- @param button number The mouse button that was clicked
@@ -90,15 +105,27 @@ end
function List:mouse_click(button, x, y) function List:mouse_click(button, x, y)
if button == 1 and self:isInBounds(x, y) and self.get("selectable") then if button == 1 and self:isInBounds(x, y) and self.get("selectable") then
local _, index = self:getRelativePosition(x, y) local _, index = self:getRelativePosition(x, y)
local adjustedIndex = index + self.get("offset") local adjustedIndex = index + self.get("offset")
local items = self.get("items") local items = self.get("items")
if adjustedIndex <= #items then if adjustedIndex <= #items then
local item = items[adjustedIndex] 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) item.callback(self)
end end
@@ -146,7 +173,6 @@ function List:render()
local items = self.get("items") local items = self.get("items")
local height = self.get("height") local height = self.get("height")
local offset = self.get("offset") local offset = self.get("offset")
local selected = self.get("selectedIndex")
local width = self.get("width") local width = self.get("width")
for i = 1, height do for i = 1, height do
@@ -154,7 +180,12 @@ function List:render()
local item = items[itemIndex] local item = items[itemIndex]
if item then 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 separatorChar = (item.text or "-"):sub(1,1)
local separatorText = string.rep(separatorChar, width) local separatorText = string.rep(separatorChar, width)
local fg = item.foreground or self.get("foreground") local fg = item.foreground or self.get("foreground")
@@ -163,15 +194,15 @@ function List:render()
self:textBg(1, i, string.rep(" ", width), bg) self:textBg(1, i, string.rep(" ", width), bg)
self:textFg(1, i, separatorText, fg) self:textFg(1, i, separatorText, fg)
else else
local text = type(item) == "table" and item.text or item local text = item.text
local isSelected = itemIndex == selected local isSelected = item.selected
local bg = isSelected and local bg = isSelected and
(item.selectedBackground or self.get("selectedColor")) or (item.selectedBackground or self.get("selectedBackground")) or
(item.background or self.get("background")) (item.background or self.get("background"))
local fg = isSelected and local fg = isSelected and
(item.selectedForeground or colors.white) or (item.selectedForeground or self.get("selectedForeground")) or
(item.foreground or self.get("foreground")) (item.foreground or self.get("foreground"))
self:textBg(1, i, string.rep(" ", width), bg) self:textBg(1, i, string.rep(" ", width), bg)

View File

@@ -63,15 +63,19 @@ function Menu:render()
VisualElement.render(self) VisualElement.render(self)
local currentX = 1 local currentX = 1
for i, item in ipairs(self.get("items")) do for _, item in ipairs(self.get("items")) do
local isSelected = i == self.get("selectedIndex") 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 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"))) (item.foreground or self.get("foreground")))
local bg = isSelected and local bg = isSelected and
(item.selectedBackground or self.get("selectedColor")) or (item.selectedBackground or self.get("selectedBackground")) or
(item.background or self.get("background")) (item.background or self.get("background"))
self:blit(currentX, 1, item.text, 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 for i, item in ipairs(self.get("items")) do
if relX >= currentX and relX < currentX + #item.text then if relX >= currentX and relX < currentX + #item.text then
if item.selectable ~= false then if item.selectable ~= false then
self.set("selectedIndex", i) if type(item) == "string" then
if type(item) == "table" then item = {text = item}
if item.callback then self.get("items")[i] = item
item.callback(self) 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
end end
-- Toggle Selection
item.selected = not item.selected
if item.callback then
item.callback(self)
end
self:fireEvent("select", i, item) self:fireEvent("select", i, item)
end end
return true return true

View File

@@ -15,14 +15,14 @@ Program.defineProperty(Program, "program", {default = nil, type = "table"})
Program.defineProperty(Program, "path", {default = "", type = "string"}) Program.defineProperty(Program, "path", {default = "", type = "string"})
Program.defineProperty(Program, "running", {default = false, type = "boolean"}) Program.defineProperty(Program, "running", {default = false, type = "boolean"})
Program.listenTo(Program, "key") Program.defineEvent(Program, "key")
Program.listenTo(Program, "char") Program.defineEvent(Program, "char")
Program.listenTo(Program, "key_up") Program.defineEvent(Program, "key_up")
Program.listenTo(Program, "paste") Program.defineEvent(Program, "paste")
Program.listenTo(Program, "mouse_click") Program.defineEvent(Program, "mouse_click")
Program.listenTo(Program, "mouse_drag") Program.defineEvent(Program, "mouse_drag")
Program.listenTo(Program, "mouse_scroll") Program.defineEvent(Program, "mouse_scroll")
Program.listenTo(Program, "mouse_up") Program.defineEvent(Program, "mouse_up")
local BasaltProgram = {} local BasaltProgram = {}
BasaltProgram.__index = BasaltProgram BasaltProgram.__index = BasaltProgram

View File

@@ -38,10 +38,10 @@ Scrollbar.defineProperty(Scrollbar, "orientation", {default = "vertical", type =
---@property handleSize number 2 Size of the scrollbar handle in characters ---@property handleSize number 2 Size of the scrollbar handle in characters
Scrollbar.defineProperty(Scrollbar, "handleSize", {default = 2, type = "number", canTriggerRender = true}) Scrollbar.defineProperty(Scrollbar, "handleSize", {default = 2, type = "number", canTriggerRender = true})
Scrollbar.listenTo(Scrollbar, "mouse_click") Scrollbar.defineEvent(Scrollbar, "mouse_click")
Scrollbar.listenTo(Scrollbar, "mouse_release") Scrollbar.defineEvent(Scrollbar, "mouse_release")
Scrollbar.listenTo(Scrollbar, "mouse_drag") Scrollbar.defineEvent(Scrollbar, "mouse_drag")
Scrollbar.listenTo(Scrollbar, "mouse_scroll") Scrollbar.defineEvent(Scrollbar, "mouse_scroll")
--- Creates a new Scrollbar instance --- Creates a new Scrollbar instance
--- @shortDescription Creates a new Scrollbar instance --- @shortDescription Creates a new Scrollbar instance

View File

@@ -18,9 +18,9 @@ Slider.defineProperty(Slider, "barColor", {default = colors.gray, type = "number
Slider.defineProperty(Slider, "sliderColor", {default = colors.blue, type = "number", canTriggerRender = true}) Slider.defineProperty(Slider, "sliderColor", {default = colors.blue, type = "number", canTriggerRender = true})
---@event onChange {value number} Fired when the slider value changes ---@event onChange {value number} Fired when the slider value changes
Slider.listenTo(Slider, "mouse_click") Slider.defineEvent(Slider, "mouse_click")
Slider.listenTo(Slider, "mouse_drag") Slider.defineEvent(Slider, "mouse_drag")
Slider.listenTo(Slider, "mouse_up") Slider.defineEvent(Slider, "mouse_up")
--- Creates a new Slider instance --- Creates a new Slider instance
--- @shortDescription Creates a new Slider instance --- @shortDescription Creates a new Slider instance

View File

@@ -26,8 +26,8 @@ Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string"})
---@property scrollOffset number 0 Current scroll position ---@property scrollOffset number 0 Current scroll position
Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true}) Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true})
Table.listenTo(Table, "mouse_click") Table.defineEvent(Table, "mouse_click")
Table.listenTo(Table, "mouse_scroll") Table.defineEvent(Table, "mouse_scroll")
--- Creates a new Table instance --- Creates a new Table instance
--- @shortDescription Creates a new Table instance --- @shortDescription Creates a new Table instance

View File

@@ -23,10 +23,10 @@ TextBox.defineProperty(TextBox, "syntaxPatterns", {default = {}, type = "table"}
---@property cursorColor number nil Color of the cursor ---@property cursorColor number nil Color of the cursor
TextBox.defineProperty(TextBox, "cursorColor", {default = nil, type = "number"}) TextBox.defineProperty(TextBox, "cursorColor", {default = nil, type = "number"})
TextBox.listenTo(TextBox, "mouse_click") TextBox.defineEvent(TextBox, "mouse_click")
TextBox.listenTo(TextBox, "key") TextBox.defineEvent(TextBox, "key")
TextBox.listenTo(TextBox, "char") TextBox.defineEvent(TextBox, "char")
TextBox.listenTo(TextBox, "mouse_scroll") TextBox.defineEvent(TextBox, "mouse_scroll")
function TextBox.new() function TextBox.new()
local self = setmetatable({}, TextBox):__init() local self = setmetatable({}, TextBox):__init()

View File

@@ -22,8 +22,8 @@ Tree.defineProperty(Tree, "nodeColor", {default = colors.white, type = "number"}
---@property selectedColor color lightBlue Background color of selected node ---@property selectedColor color lightBlue Background color of selected node
Tree.defineProperty(Tree, "selectedColor", {default = colors.lightBlue, type = "number"}) Tree.defineProperty(Tree, "selectedColor", {default = colors.lightBlue, type = "number"})
Tree.listenTo(Tree, "mouse_click") Tree.defineEvent(Tree, "mouse_click")
Tree.listenTo(Tree, "mouse_scroll") Tree.defineEvent(Tree, "mouse_scroll")
--- Creates a new Tree instance --- Creates a new Tree instance
--- @shortDescription Creates a new Tree instance --- @shortDescription Creates a new Tree instance

View File

@@ -1,6 +1,8 @@
local elementManager = require("elementManager") local elementManager = require("elementManager")
local BaseElement = elementManager.getElement("BaseElement") local BaseElement = elementManager.getElement("BaseElement")
local tHex = require("libraries/colorHex") 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 --- 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. --- 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 onKeyUp {key number, code number} Fired on key release
---@event onChar {char string} Fired on character input ---@event onChar {char string} Fired on character input
VisualElement.listenTo(VisualElement, "focus") VisualElement.defineEvent(VisualElement, "focus")
VisualElement.listenTo(VisualElement, "blur") VisualElement.defineEvent(VisualElement, "blur")
VisualElement.listenTo(VisualElement, "mouse_enter", "mouse_move")
VisualElement.listenTo(VisualElement, "mouse_leave", "mouse_move") VisualElement.registerEventCallback(VisualElement, "MouseClick", "mouse_click", "mouse_up")
VisualElement.listenTo(VisualElement, "mouse_scroll") 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 local max, min = math.max, math.min
@@ -114,7 +121,13 @@ end
--- Draws multiple characters at once with colors --- Draws multiple characters at once with colors
--- @shortDescription Multi-character drawing 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) function VisualElement:multiBlit(x, y, width, height, text, fg, bg)
local xElement, yElement = self:calculatePosition() local xElement, yElement = self:calculatePosition()
x = x + xElement - 1 x = x + xElement - 1

View File

@@ -411,7 +411,7 @@ end
---@private ---@private
function VisualElement.setup(element) function VisualElement.setup(element)
element.defineProperty(element, "animation", {default = nil, type = "table"}) element.defineProperty(element, "animation", {default = nil, type = "table"})
element.listenTo(element, "timer") element.defineEvent(element, "timer")
end end
--- Creates a new Animation Object --- Creates a new Animation Object

View File

@@ -6,13 +6,11 @@ local function serialize(t, indent)
local result = "{\n" local result = "{\n"
for k, v in pairs(t) do for k, v in pairs(t) do
result = result .. indent .. " " result = result .. indent .. " "
if type(k) == "string" then if type(k) == "string" then
result = result .. "[\"" .. k .. "\"] = " result = result .. "[\"" .. k .. "\"] = "
else else
result = result .. "[" .. k .. "] = " result = result .. "[" .. k .. "] = "
end end
if type(v) == "table" then if type(v) == "table" then
result = result .. serialize(v, indent .. " ") result = result .. serialize(v, indent .. " ")
elseif type(v) == "string" then elseif type(v) == "string" then
@@ -25,33 +23,84 @@ local function serialize(t, indent)
return result .. indent .. "}" return result .. indent .. "}"
end end
local function extractConfigDescription(filePath) local function parseFile(filePath)
local f = io.open(filePath, "r") local file = fs.open(filePath, "r")
if not f then return nil end local content = file.readAll()
file.close()
local content = f:read("*all") local config = {
f:close() 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 end
local function categorizeFile(path) local function categorizeFile(path)
if path:match("^src/elements/") then if path:match("^elements/") then
return "elements", "UI Elements" return "elements", "UI Elements"
elseif path:match("^src/plugins/") then elseif path:match("^plugins/") then
return "plugins", "Plugins and Extensions" return "plugins", "Plugins"
elseif path:match("^src/libraries/") then elseif path:match("^libraries/") then
return "libraries", "Utility Libraries" return "libraries", "Libraries"
elseif path:match("^src/[^/]+%.lua$") then
return "core", "Core Framework Files"
else else
return "other", "Other Files" return "core", "Core Files"
end end
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 = {} 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) local category, categoryDesc = categorizeFile(path)
if not categories[category] then if not categories[category] then
categories[category] = { categories[category] = {
@@ -59,61 +108,45 @@ local function sortFiles(files)
files = {} files = {}
} }
end end
table.insert(categories[category].files, { categories[category].files[fileConfig.name] = {
path = path, path = fileConfig.path,
name = info.name, description = fileConfig.description,
description = info.description default = fileConfig.default,
}) requires = fileConfig.requires
}
end end
for _, cat in pairs(categories) do -- Dependencies validieren
table.sort(cat.files, function(a, b) for catName, cat in pairs(categories) do
return a.name < b.name for fileName, file in pairs(cat.files) do
end) for _, req in ipairs(file.requires or {}) do
end local found = false
for _, checkCat in pairs(categories) do
return categories if checkCat.files[req] then
end found = true
break
local function scanDir(dir) end
local files = {} end
for file in io.popen('find "'..dir..'" -maxdepth 1 -type f -name "*.lua"'):lines() do if not found then
local name = file:match("([^/]+)%.lua$") error(string.format("Missing dependency %s for %s", req, fileName))
if name then end
files[file] = { end
name = name,
path = file:gsub("^src/", ""),
description = extractConfigDescription(file)
}
end end
end end
for file in io.popen('find "'..dir..'/elements" "'..dir..'/plugins" "'..dir..'/libraries" -type f -name "*.lua"'):lines() do return {
local name = file:match("([^/]+)%.lua$") categories = categories,
if name then metadata = {
files[file] = { generated = os.date(),
name = name, version = "2.0"
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"
} }
}
local f = io.open("config.lua", "w")
if f then
f:write("return " .. serialize(config))
f:close()
end 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()