Updated Eventsystem
Improved List with multiple Selections
This commit is contained in:
131
install.lua
131
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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user