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)
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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

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})
---@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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()