diff --git a/README.md b/README.md index c437523..f9771bc 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,28 @@ -# Basalt - A UI Framework for CC:Tweaked +# Basalt 2 - A UI Framework for CC:Tweaked ![GitHub Repo stars](https://img.shields.io/github/stars/Pyroxenium/Basalt2?style=for-the-badge) [![Discord](https://img.shields.io/discord/976905222251233320?label=Discord&style=for-the-badge)](https://discord.gg/yNNnmBVBpE) -This is a complete rework of Basalt. It provides an intuitive way to create complex user interfaces for your CC:Tweaked programs. +Welcome, -Basalt is intended to be an easy-to-understand UI Framework designed for CC:Tweaked - a popular minecraft mod. For more information about CC:Tweaked, checkout the project's [wiki](https://tweaked.cc/) or [download](https://modrinth.com/mod/cc-tweaked). -**Note:** Basalt is still under developement and you may find bugs! +This is a complete rework of [Basalt](https://github.com/Pyroxenium/Basalt). There are many new features, including auto-generated LuaLS annotations and auto-generated documentation, ensuring that everything is always up-to-date. -Check out the [wiki](https://basalt.madefor.cc/) for more information. -If you have questions, feel free to join the discord server: [discord.gg/yNNnmBVBpE](https://discord.gg/yNNnmBVBpE). +Basalt is a UI framework designed for [CC:Tweaked](https://tweaked.cc/) - a popular Minecraft mod. For more information about CC:Tweaked, check out the project's [wiki](https://tweaked.cc/) or download it from [Modrinth](https://modrinth.com/mod/cc-tweaked) or [CurseForge](https://www.curseforge.com/minecraft/mc-mods/cc-tweaked). ## Documentation -For detailed documentation, examples and guides, visit [basalt.madefor.cc](https://basalt.madefor.cc/) +For detailed documentation, examples, and guides, visit our documentation on [basalt.madefor.cc](https://basalt.madefor.cc/). ## Support If you need help or have questions: -- Check the [documentation](https://basalt.madefor.cc/) -- Join our [Discord](https://discord.gg/yNNnmBVBpE) -- Report issues on [GitHub](https://github.com/Pyroxenium/Basalt2/issues) +- Check the [documentation](https://basalt.madefor.cc/). +- Join our [Discord](https://discord.gg/yNNnmBVBpE). +- Report issues or contribute on [GitHub](https://github.com/Pyroxenium/Basalt2/issues). ## License -This project is licensed under the MIT License. +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). ## Demo diff --git a/install.lua b/install.lua index 47aa213..1de709c 100644 --- a/install.lua +++ b/install.lua @@ -1,4 +1,5 @@ local basalt = require("src") +local url = "https://raw.githubusercontent.com/Pyroxenium/Basalt2/refs/heads/main/src/" local configPath = "https://raw.githubusercontent.com/Pyroxenium/Basalt2/refs/heads/main/config.lua" local coloring = {foreground=colors.black, background=colors.white} @@ -135,19 +136,33 @@ end) installScreen:addLabel(coloring) :setText("Path:") - :setPosition(2, "{versionDesc.y + versionDesc.height + 1}") + :setPosition(2, "{versionDesc.y + versionDesc.height + 5}") installScreen:addLabel(coloring) :setText("Additional Components:") :setPosition(2, "{versionDesc.y + versionDesc.height + 1}") -local luaLSCheckbox = installScreen:addCheckbox() - :setPosition(2, 12) + local luaLSCheckbox = installScreen:addCheckbox() :setText("[ ] LLS definitions") :setCheckedText("[x] LLS definitions") + :setPosition(2, "{versionDesc.y + versionDesc.height + 2}") :setBackground(colors.white) :setForeground(colors.black) +local luaMinifyCheckbox = installScreen:addCheckbox() + :setText("[ ] Minify Project") + :setCheckedText("[x] Minify Project") + :setPosition(2, "{versionDesc.y + versionDesc.height + 3}") + :setBackground(colors.white) + :setForeground(colors.black) + +local installPathInput = installScreen:addInput() + :setPosition(8, "{versionDesc.y + versionDesc.height + 5}") + :setPlaceholder("basalt") + :setSize(12, 1) + :setBackground(colors.black) + :setForeground(colors.white) + -- Screen 3: Elements local elementsScreen = createScreen(3) elementsScreen:addLabel(coloring) @@ -179,7 +194,13 @@ local eleScreenDesc = elementsScreen:addLabel() local function addElements() elementsList:clear() for k,v in pairs(getConfig().categories.elements.files)do - elementsList:addItem({selected=true, text=v.name, callback=function() elementDesc:setText(v.description) end}) + elementsList:addItem({selected=v.default, text=k, item=v, callback=function() + if(v.description)and(v.description~="")then + elementDesc:setText(v.description) + else + elementDesc:setText("No description available.") + end + end}) end end addElements() @@ -215,11 +236,24 @@ local pluScreenDesc = pluginScreen:addLabel() 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}) + pluginList:addItem({selected = v.default, text=k, item=v, callback=function() + if(v.description)and(v.description~="")then + elementDesc:setText(v.description) + else + elementDesc:setText("No description available.") + end + end}) + end end addPlugins() +local function tableGet(t) + local count = 0 + for _ in pairs(t) do count = count + 1 end + return count +end + -- Screen 5 Installation Progress local progressScreen = createScreen(5) local progressBar = progressScreen:addProgressBar() @@ -234,8 +268,14 @@ local log = progressScreen:addList("log") local function install() local function logMessage(message) log:addItem(message) + log:scrollToBottom() end + local fileCount = tableGet(getConfig().categories.core.files) + tableGet(getConfig().categories.libraries.files) + fileCount = fileCount + tableGet(elementsList:getSelectedItems()) + tableGet(pluginList:getSelectedItems()) + progressBar:setProgress(0) + local progressStep = math.ceil(100 / fileCount) + local function downloadFile(url, path) logMessage("Downloading " .. url .. "...") local request = http.get(url) @@ -250,25 +290,43 @@ local function install() end end - local function installElement(name, url) + local path = installPathInput:getText() + if path == "" then + path = "basalt" + end + + for k, v in pairs(getConfig().categories.core.files) do + logMessage("Installing core: " .. k) + downloadFile(url..v.path, fs.combine(path, v.path)) + progressBar:setProgress(progressBar:getProgress() + progressStep) + end + + for k, v in pairs(getConfig().categories.libraries.files) do + logMessage("Installing library: " .. k) + downloadFile(url..v.path, fs.combine(path, v.path)) + progressBar:setProgress(progressBar:getProgress() + progressStep) + end + + local function installElement(name, item) logMessage("Installing element: " .. name) - --downloadFile(url, "/path/to/install/" .. name) + downloadFile(url..item.path, fs.combine(path, item.path)) + progressBar:setProgress(progressBar:getProgress() + progressStep) end - local function installPlugin(name, url) + local function installPlugin(name, item) logMessage("Installing plugin: " .. name) - --downloadFile(url, "/path/to/install/" .. name) + downloadFile(url..item.path, fs.combine(path, item.path)) + progressBar:setProgress(progressBar:getProgress() + progressStep) end - for _, element in ipairs(elementsList:getSelectedItems()) do + for k, 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) + installElement(item.text, item.item) end for _, plugin in ipairs(pluginList:getSelectedItems()) do local item = plugin.item - installPlugin(item.text, getConfig().categories.plugins.files[item.text].url) + installPlugin(item.text, item.item) end progressBar:setProgress(100) diff --git a/src/elements/Dropdown.lua b/src/elements/Dropdown.lua index d4cfc40..a20261c 100644 --- a/src/elements/Dropdown.lua +++ b/src/elements/Dropdown.lua @@ -62,13 +62,37 @@ function Dropdown:mouse_click(button, x, y) self.set("height", 1 + math.min(self.get("dropdownHeight"), #self.get("items"))) end return true - elseif self.get("isOpen") and relY > 1 then - -- 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 + elseif self.get("isOpen") and relY > 1 and self.get("selectable") then + local itemIndex = (relY - 1) + self.get("offset") + local items = self.get("items") + + if itemIndex <= #items then + local item = items[itemIndex] + if type(item) == "string" then + item = {text = item} + items[itemIndex] = item + end + + 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 + + self:fireEvent("select", itemIndex, item) + self.set("isOpen", false) + self.set("height", 1) + self:updateRender() + return true + end end return false end @@ -78,10 +102,8 @@ end function Dropdown:render() VisualElement.render(self) - -- Header rendern local text = self.get("selectedText") if #text == 0 then - -- Suche nach selektiertem Item local selectedItems = self:getSelectedItems() if #selectedItems > 0 then local selectedItem = selectedItems[1].item @@ -89,20 +111,51 @@ function Dropdown:render() 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 - -- 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) + local items = self.get("items") + local height = self.get("height") - 1 + local offset = self.get("offset") + local width = self.get("width") + + for i = 1, height do + local itemIndex = i + offset + local item = items[itemIndex] + + if item 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") + local bg = item.background or self.get("background") + + self:textBg(1, i + 1, string.rep(" ", width), bg) + self:textFg(1, i + 1, separatorText, fg) + else + local text = item.text + local isSelected = item.selected + + local bg = isSelected and + (item.selectedBackground or self.get("selectedBackground")) or + (item.background or self.get("background")) + + local fg = isSelected and + (item.selectedForeground or self.get("selectedForeground")) or + (item.foreground or self.get("foreground")) + + self:textBg(1, i + 1, string.rep(" ", width), bg) + self:textFg(1, i + 1, text, fg) + end + end + end end end diff --git a/src/elements/List.lua b/src/elements/List.lua index 0bf145e..43e15ef 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -167,6 +167,23 @@ function List:onSelect(callback) return self end +--- Scrolls the list to the bottom +--- @shortDescription Scrolls the list to the bottom +--- @return List self The List instance +function List:scrollToBottom() + local maxOffset = math.max(0, #self.get("items") - self.get("height")) + self.set("offset", maxOffset) + return self +end + +--- Scrolls the list to the top +--- @shortDescription Scrolls the list to the top +--- @return List self The List instance +function List:scrollToTop() + self.set("offset", 0) + return self +end + --- Renders the list --- @shortDescription Renders the list function List:render() diff --git a/src/elements/ProgressBar.lua b/src/elements/ProgressBar.lua index 1d99387..f1f11a4 100644 --- a/src/elements/ProgressBar.lua +++ b/src/elements/ProgressBar.lua @@ -11,7 +11,7 @@ ProgressBar.defineProperty(ProgressBar, "progress", {default = 0, type = "number ---@property showPercentage boolean false Whether to show the percentage text in the center ProgressBar.defineProperty(ProgressBar, "showPercentage", {default = false, type = "boolean"}) ---@property progressColor color lime The color used for the filled portion of the progress bar -ProgressBar.defineProperty(ProgressBar, "progressColor", {default = colors.lime, type = "number"}) +ProgressBar.defineProperty(ProgressBar, "progressColor", {default = colors.black, type = "number"}) --- Creates a new ProgressBar instance --- @shortDescription Creates a new ProgressBar instance @@ -42,7 +42,9 @@ function ProgressBar:render() local progress = math.min(100, math.max(0, self.get("progress"))) local fillWidth = math.floor((width * progress) / 100) - self:textBg(1, 1, string.rep(" ", fillWidth), self.get("progressColor")) + for i = 1, self.get("height") do + self:textBg(1, i, string.rep(" ", fillWidth), self.get("progressColor")) + end if self.get("showPercentage") then local text = tostring(progress).."%" diff --git a/tools/generate-config.lua b/tools/generate-config.lua index 1ad6c53..7e70158 100644 --- a/tools/generate-config.lua +++ b/tools/generate-config.lua @@ -24,16 +24,20 @@ local function serialize(t, indent) end local function parseFile(filePath) + if filePath:match("LuaLS%.lua$") then return nil end + local file = io.open(filePath, "r") if not file then return nil end local content = file:read("*all") + local size = #content file:close() local config = { description = "", default = true, - requires = {} + requires = {}, + size = size } local description = content:match("%-%-%-@configDescription%s*(.-)%s*\n") @@ -79,11 +83,13 @@ local function scanDirectory(srcPath) if not pipe then return end for path in pipe:lines() do - local config = parseFile(path) - if config then - config.name = path:match("([^/]+)%.lua$") - config.path = path:gsub("^" .. srcPath .. "/", "") - files[path] = config + if(path~="LuaLS.lua")then + local config = parseFile(path) + if config then + config.name = path:match("([^/]+)%.lua$") + config.path = path:gsub("^" .. srcPath .. "/", "") + files[path] = config + end end end pipe:close() @@ -109,7 +115,8 @@ local function generateConfig(srcPath) path = fileConfig.path, description = fileConfig.description, default = fileConfig.default, - requires = fileConfig.requires + requires = fileConfig.requires, + size = fileConfig.size } end @@ -139,7 +146,6 @@ local function generateConfig(srcPath) } end --- Config generieren und speichern local config = generateConfig("src") local configFile = io.open("config.lua", "w") configFile:write("return " .. serialize(config))