From c9a74dfbd4e793c8b39ff83f3df5bfb4c3ac1ce2 Mon Sep 17 00:00:00 2001 From: Robert Jelic <36573031+NoryiE@users.noreply.github.com> Date: Sun, 7 May 2023 15:17:27 +0200 Subject: [PATCH] Small Fixes - added Label RichText System - experimental - moved eventhandlers to object - Fixed still listening to incomming events when removing a object - Fixed not passing args when creating a event via xml, args are now always stored inside a table called event --- Basalt/libraries/utils.lua | 281 +++++++++++++++++++++++++++++--- Basalt/main.lua | 41 ++--- Basalt/objects/Container.lua | 15 +- Basalt/objects/Label.lua | 72 ++++---- Basalt/objects/Object.lua | 12 ++ Basalt/objects/Thread.lua | 1 + Basalt/objects/Timer.lua | 3 +- Basalt/objects/VisualObject.lua | 11 -- Basalt/plugins/xml.lua | 24 ++- 9 files changed, 356 insertions(+), 104 deletions(-) diff --git a/Basalt/libraries/utils.lua b/Basalt/libraries/utils.lua index 6f99e70..059005b 100644 --- a/Basalt/libraries/utils.lua +++ b/Basalt/libraries/utils.lua @@ -1,3 +1,4 @@ +local tHex = require("tHex") local sub,find,reverse,rep,insert,len = string.sub,string.find,string.reverse,string.rep,table.insert,string.len local function splitString(str, delimiter) @@ -16,6 +17,200 @@ local function splitString(str, delimiter) return result end +local function removeTags(input) + return input:gsub("{[^}]+}", "") +end + + +local function wrapText(str, width) + str = removeTags(str) + if(str=="")or(width==0)then + return {""} + end + local uniqueLines = splitString(str, "\n") + local result = {} + for k, v in pairs(uniqueLines) do + if #v == 0 then + table.insert(result, "") + else + while #v > width do + local last_space = width + for i = width, 1, -1 do + if sub(v, i, i) == " " then + last_space = i + break + end + end + + if last_space == width then + local line = sub(v, 1, last_space - 1) .. "-" + table.insert(result, line) + v = sub(v, last_space) + else + local line = sub(v, 1, last_space - 1) + table.insert(result, line) + v = sub(v, last_space + 1) + end + + if #v <= width then + break + end + end + if #v > 0 then + table.insert(result, v) + end + end + end + return result +end + +--- Coonverts a string with special tags to a table with colors and text +-- @param input The string to convert +-- @return A table with the following structure: { {text = "Hello", color = colors.red}, {text = "World", color = colors.blue} } +local function convertRichText(input) + local parsedResult = {} + local currentPosition = 1 + local rawPosition = 1 + + while currentPosition <= #input do + local closestColor, closestBgColor + local color, bgColor + local colorEnd, bgColorEnd + + for colorName, _ in pairs(colors) do + local fgPattern = "{fg:" .. colorName.."}" + local bgColorPattern = "{bg:" .. colorName.."}" + local colorStart, colorEndCandidate = input:find(fgPattern, currentPosition) + local bgColorStart, bgColorEndCandidate = input:find(bgColorPattern, currentPosition) + + if colorStart and (not closestColor or colorStart < closestColor) then + closestColor = colorStart + color = colorName + colorEnd = colorEndCandidate + end + + if bgColorStart and (not closestBgColor or bgColorStart < closestBgColor) then + closestBgColor = bgColorStart + bgColor = colorName + bgColorEnd = bgColorEndCandidate + end + end + + local nextPosition + if closestColor and (not closestBgColor or closestColor < closestBgColor) then + nextPosition = closestColor + elseif closestBgColor then + nextPosition = closestBgColor + else + nextPosition = #input + 1 + end + + local text = input:sub(currentPosition, nextPosition - 1) + if #text > 0 then + table.insert(parsedResult, { + color = nil, + bgColor = nil, + text = text, + position = rawPosition + }) + rawPosition = rawPosition + #text + currentPosition = currentPosition + #text + end + + if closestColor and (not closestBgColor or closestColor < closestBgColor) then + table.insert(parsedResult, { + color = color, + bgColor = nil, + text = "", + position = rawPosition, + }) + currentPosition = colorEnd + 1 + elseif closestBgColor then + table.insert(parsedResult, { + color = nil, + bgColor = bgColor, + text = "", + position = rawPosition, + }) + currentPosition = bgColorEnd + 1 + else + break + end + end + + return parsedResult +end + +--- Wrapts text with special color tags, like {fg:red} or {bg:blue} to multiple lines +--- @param text string Text to wrap +--- @param width number Width of the line +--- @return table Table of lines +local function wrapRichText(text, width) + local colorData = convertRichText(text) + local formattedLines = {} + local x, y = 1, 1 + local currentColor, currentBgColor + + local function addFormattedEntry(entry) + table.insert(formattedLines, { + x = x, + y = y, + text = entry.text, + color = entry.color or currentColor, + bgColor = entry.bgColor or currentBgColor + }) + end + + for _, entry in ipairs(colorData) do + if entry.color then + currentColor = entry.color + elseif entry.bgColor then + currentBgColor = entry.bgColor + else + local words = splitString(entry.text, " ") + + for i, word in ipairs(words) do + local wordLength = #word + + if i > 1 then + if x + 1 + wordLength <= width then + addFormattedEntry({ text = " " }) + x = x + 1 + else + x = 1 + y = y + 1 + end + end + + while wordLength > 0 do + local line = word:sub(1, width - x + 1) + word = word:sub(width - x + 2) + wordLength = #word + + addFormattedEntry({ text = line }) + + if wordLength > 0 then + x = 1 + y = y + 1 + else + x = x + #line + end + end + end + end + + if x > width then + x = 1 + y = y + 1 + end + end + + return formattedLines +end + + + + return { getTextHorizontalAlign = function(text, width, textAlign, replaceChar) text = sub(text, 1, width) @@ -74,29 +269,9 @@ tableCount = function(t) end, splitString = splitString, +removeTags = removeTags, -wrapText = function(str, width) - local uniqueLines = splitString(str, "\n") - local result = {} - for k,v in pairs(uniqueLines)do - if(#v==0)then table.insert(result, "") end - while #v > width do - local last_space = find(reverse(sub(v, 1, width)), " ") - if not last_space then - last_space = width - else - last_space = width - last_space --+ 1 - end - local line = sub(v, 1, last_space) - table.insert(result, line) - v = sub(v, last_space + 1) - end - if #v > 0 then - table.insert(result, v) - end - end - return result -end, +wrapText = wrapText, xmlValue = function(name, tab) local var @@ -120,7 +295,69 @@ xmlValue = function(name, tab) return var end, +convertRichText = convertRichText, +--- Writes text with special color tags +--- @param obj object The object to write to +--- @param x number X-Position +--- @param y number Y-Position +--- @param text string The text to write +writeRichText = function(obj, x, y, text) + local richText = convertRichText(text) + if(#richText==0)then + obj:addText(x, y, text) + return + end + + local defaultFG, defaultBG = obj:getForeground(), obj:getBackground() + for _,v in pairs(richText)do + obj:addText(x+v.position-1, y, v.text) + if(v.color~=nil)then + obj:addFG(x+v.position-1, y, tHex[colors[v.color] ]:rep(#v.text)) + defaultFG = colors[v.color] + else + obj:addFG(x+v.position-1, y, tHex[defaultFG]:rep(#v.text)) + end + if(v.bgColor~=nil)then + obj:addBG(x+v.position-1, y, tHex[colors[v.bgColor] ]:rep(#v.text)) + defaultBG = colors[v.bgColor] + else + if(defaultBG~=false)then + obj:addBG(x+v.position-1, y, tHex[defaultBG]:rep(#v.text)) + end + end + end +end, + +wrapRichText = wrapRichText, + +--- Writes wrapped Text with special tags. +--- @param obj object The object to write to +--- @param x number X-Position +--- @param y number Y-Position +--- @param text string Text +--- @param width number Width +--- @param height number Height +writeWrappedText = function(obj, x, y, text, width, height) + local wrapped = wrapRichText(text, width) + for k,v in pairs(wrapped)do + if(v.y>height)then + break + end + if(v.text~=nil)then + obj:addText(x+v.x-1, y+v.y-1, v.text) + end + if(v.color~=nil)then + obj:addFG(x+v.x-1, y+v.y-1, tHex[colors[v.color] ]:rep(#v.text)) + end + if(v.bgColor~=nil)then + obj:addBG(x+v.x-1, y+v.y-1, tHex[colors[v.bgColor] ]:rep(#v.text)) + end + end +end, + +--- Returns a random UUID. +--- @return string UUID. uuid = function() return string.gsub(string.format('%x-%x-%x-%x-%x', math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0x0fff) + 0x4000, math.random(0, 0x3fff) + 0x8000), ' ', '0') end diff --git a/Basalt/main.lua b/Basalt/main.lua index 009406a..7d2a524 100644 --- a/Basalt/main.lua +++ b/Basalt/main.lua @@ -43,26 +43,6 @@ local function stop() end end -function basalt.basaltError(errMsg) - baseTerm.clear() - baseTerm.setBackgroundColor(colors.black) - baseTerm.setTextColor(colors.red) - local w,h = baseTerm.getSize() - if(basalt.logging)then - log(errMsg, "Error") - end - - local text = wrapText("Basalt error: "..errMsg, w) - local yPos = 1 - for k,v in pairs(text)do - baseTerm.setCursorPos(1,yPos) - baseTerm.write(v) - yPos = yPos + 1 - end - baseTerm.setCursorPos(1,yPos+1) - updaterActive = false -end - local function schedule(f) assert(f~="function", "Schedule needs a function in order to work!") return function(...) @@ -160,6 +140,26 @@ local bInstance = { end } +local function defaultErrorHandler(errMsg) + baseTerm.clear() + baseTerm.setBackgroundColor(colors.black) + baseTerm.setTextColor(colors.red) + local w,h = baseTerm.getSize() + if(basalt.logging)then + log(errMsg, "Error") + end + + local text = wrapText("Basalt error: "..errMsg, w) + local yPos = 1 + for k,v in pairs(text)do + baseTerm.setCursorPos(1,yPos) + baseTerm.write(v) + yPos = yPos + 1 + end + baseTerm.setCursorPos(1,yPos+1) + updaterActive = false +end + local function handleSchedules(event, p1, p2, p3, p4) if(#schedules>0)then local finished = {} @@ -329,6 +329,7 @@ local function createFrame(name) end basalt = { + basaltError = defaultErrorHandler, logging = false, dynamicValueEvents = false, drawFrames = drawFrames, diff --git a/Basalt/objects/Container.lua b/Basalt/objects/Container.lua index 460c7e4..9b31021 100644 --- a/Basalt/objects/Container.lua +++ b/Basalt/objects/Container.lua @@ -10,7 +10,6 @@ return function(name, basalt) local events = {} local container = {} - local activeEvents = {} local focusedObject local sorted = true @@ -69,14 +68,14 @@ return function(name, basalt) local function updateZIndex(self, element, newZ) objId = objId + 1 evId = evId + 1 - for k,v in pairs(elements)do + for _,v in pairs(elements)do if(v.element==element)then v.zIndex = newZ v.objId = objId break end end - for k,v in pairs(events)do + for _,v in pairs(events)do for a,b in pairs(v)do if(b.element==element)then b.zIndex = newZ @@ -109,7 +108,6 @@ return function(name, basalt) end end if(tableCount(events[a])<=0)then - activeEvents[a] = false if(parent~=nil)then parent:removeEvent(a, self) end @@ -143,7 +141,6 @@ return function(name, basalt) end local function removeEvent(self, event, element) - local parent = self:getParent() if(events[event]~=nil)then for a, b in pairs(events[event]) do if(b.element == element)then @@ -288,11 +285,11 @@ return function(name, basalt) getEvent = getEvent, addEvent = addEvent, removeEvent = removeEvent, + removeEvents = removeEvents, updateZIndex = updateZIndex, listenEvent = function(self, event, active) base.listenEvent(self, event, active) - activeEvents[event] = active~=nil and active or true if(events[event]==nil)then events[event] = {} end return self end, @@ -348,8 +345,10 @@ return function(name, basalt) if(self.getOffset~=nil)then xO, yO = self:getOffset() end - if(obj.element.getIgnoreOffset())then - xO, yO = 0, 0 + if(obj.element.getIgnoreOffset~=nil)then + if(obj.element.getIgnoreOffset())then + xO, yO = 0, 0 + end end if (obj.element[v[1]](obj.element, btn, x+xO, y+yO, ...)) then return true diff --git a/Basalt/objects/Label.lua b/Basalt/objects/Label.lua index b8c19c1..b3db4c9 100644 --- a/Basalt/objects/Label.lua +++ b/Basalt/objects/Label.lua @@ -1,5 +1,6 @@ local utils = require("utils") local wrapText = utils.wrapText +local writeWrappedText = utils.writeWrappedText local tHex = require("tHex") return function(name, basalt) @@ -8,92 +9,97 @@ return function(name, basalt) local objectType = "Label" base:setZIndex(3) + base:setSize(5, 1) + base:setBackground(false) local autoSize = true - local fgColChanged,bgColChanged = false,false local text, textAlign = "Label", "left" local object = { + --- Returns the object type. + --- @return string getType = function(self) return objectType end, + --- Returns the label's base object. + --- @return object getBase = function(self) return base - end, + end, + --- Changes the label's text. + --- @param newText string The new text of the label. + --- @return object setText = function(self, newText) text = tostring(newText) if(autoSize)then - self:setSize(#text, 1) + local t = wrapText(text, #text) + local newW, newH = 1,1 + for k,v in pairs(t)do + newH = newH+1 + newW = math.max(newW, v:len()) + end + self:setSize(newW, newH) autoSize = true end self:updateDraw() return self end, + --- Returns the label's autoSize property. + --- @return boolean getAutoSize = function(self) return autoSize end, + --- Sets the label's autoSize property. + --- @param bool boolean The new value of the autoSize property. + --- @return object setAutoSize = function(self, bool) autoSize = bool return self end, + --- Returns the label's text. + --- @return string getText = function(self) return text end, - setBackground = function(self, col) - base.setBackground(self, col) - bgColChanged = true - return self - end, - - setForeground = function(self, col) - base.setForeground(self, col) - fgColChanged = true - return self - end, - - setSize = function(self, ...) - base.setSize(self, ...) + --- Sets the size of the label. + --- @param width number The width of the label. + --- @param height number The height of the label. + --- @return object + setSize = function(self, width, height) + base.setSize(self, width, height) autoSize = false return self end, + --- Sets the text alignment of the label. + --- @param align string The alignment of the text. Can be "left", "center", or "right". + --- @return object setTextAlign = function(self, align) textAlign = align or textAlign return self; end, + --- Queues a new draw function to be called when the object is drawn. draw = function(self) base.draw(self) self:addDraw("label", function() - local parent = self:getParent() - local obx, oby = self:getPosition() - local w,h = self:getSize() - local bgCol,fgCol = self:getBackground(), self:getForeground() - if not(autoSize)then - local text = wrapText(text, w) - for k,v in pairs(text)do - if(k<=h)then - local align = textAlign=="center" and math.floor(w/2-v:len()/2+0.5) or textAlign=="right" and w-(v:len()-1) or 1 - self:addText(align, k, v) - end - end - else - self:addText(1, 1, text:sub(1,w)) - end + local w, h = self:getSize() + local align = textAlign=="center" and math.floor(w/2-text:len()/2+0.5) or textAlign=="right" and w-(text:len()-1) or 1 + writeWrappedText(self, align, 1, text, w, h) end) end, + --- Initializes the label. init = function(self) base.init(self) local parent = self:getParent() self:setForeground(parent:getForeground()) - self:setBackground(parent:getBackground()) end } diff --git a/Basalt/objects/Object.lua b/Basalt/objects/Object.lua index f3b209f..3efde3e 100644 --- a/Basalt/objects/Object.lua +++ b/Basalt/objects/Object.lua @@ -99,6 +99,7 @@ return function(name, basalt) remove = function(self) if (parent ~= nil) then parent:removeObject(self) + parent:removeEvents(self) end self:updateDraw() return self @@ -140,6 +141,17 @@ return function(name, basalt) return eventSystem:removeEvent(event, index) end, + eventHandler = function(self, event, ...) + local val = self:sendEvent("other_event", event, ...) + if(val~=nil)then return val end + end, + + customEventHandler = function(self, event, ...) + local val = self:sendEvent("custom_event", event, ...) + if(val~=nil)then return val end + return true + end, + sendEvent = function(self, event, ...) return eventSystem:sendEvent(event, self, event, ...) end, diff --git a/Basalt/objects/Thread.lua b/Basalt/objects/Thread.lua index 4a94dc6..36129df 100644 --- a/Basalt/objects/Thread.lua +++ b/Basalt/objects/Thread.lua @@ -85,6 +85,7 @@ return function(name, basalt) end, eventHandler = function(self, event, ...) + base.eventHandler(self, event, ...) if (isActive) then if (coroutine.status(cRoutine) == "suspended") then if(filter~=nil)then diff --git a/Basalt/objects/Timer.lua b/Basalt/objects/Timer.lua index f92388c..09585e5 100644 --- a/Basalt/objects/Timer.lua +++ b/Basalt/objects/Timer.lua @@ -48,7 +48,8 @@ return function(name, basalt) return self end, - eventHandler = function(self, event, tObj) + eventHandler = function(self, event, ...) + base.eventHandler(self, event, ...) if event == "timer" and tObj == timerObj and timerIsActive then self:sendEvent("timed_event") if (repeats >= 1) then diff --git a/Basalt/objects/VisualObject.lua b/Basalt/objects/VisualObject.lua index 98c927a..840cf2e 100644 --- a/Basalt/objects/VisualObject.lua +++ b/Basalt/objects/VisualObject.lua @@ -395,17 +395,6 @@ return function(name, basalt) end end, - eventHandler = function(self, event, ...) - local val = self:sendEvent("other_event", ...) - if(val~=nil)then return val end - end, - - customEventHandler = function(self, event, ...) - local val = self:sendEvent("custom_event", ...) - if(val~=nil)then return val end - return true - end, - getFocusHandler = function(self) local val = self:sendEvent("get_focus") if(val~=nil)then return val end diff --git a/Basalt/plugins/xml.lua b/Basalt/plugins/xml.lua index 7dc2599..d310ce2 100644 --- a/Basalt/plugins/xml.lua +++ b/Basalt/plugins/xml.lua @@ -159,11 +159,15 @@ local function executeScript(scripts) end local function registerFunctionEvent(self, data, event, scripts) + local eventEnv = scripts.env if(data:sub(1,1)=="$")then local data = data:sub(2) - event(self, self:getBasalt():getVariable(data)) + event(self, self:getBasalt().getVariable(data)) else - event(self, load(data, nil, "t", scripts.env)) + event(self, function(...) + eventEnv.event = {...} + load(data, nil, "t", eventEnv)() + end) end end @@ -181,8 +185,8 @@ return { if(xmlValue("height", data)~=nil)then h = xmlValue("height", data) end if(xmlValue("background", data)~=nil)then self:setBackground(colors[xmlValue("background", data)]) end - - if(xmlValue("script", data)~=nil)then + + if(xmlValue("script", data)~=nil)then if(scripts[1]==nil)then scripts[1] = {} end @@ -248,7 +252,7 @@ return { lastXMLReferences = {} base.setValuesByXMLData(self, data, scripts) local xOffset, yOffset = self:getOffset() - if(xmlValue("layout", data)~=nil)then self:addLayout(xmlValue("layout", data)) end + if(xmlValue("layout", data)~=nil)then self:loadLayout(xmlValue("layout", data)) end if(xmlValue("xOffset", data)~=nil)then xOffset = xmlValue("xOffset", data) end self:setOffset(xOffset, yOffset) @@ -316,7 +320,7 @@ return { setValuesByXMLData = function(self, data, scripts) base.setValuesByXMLData(self, data, scripts) local xOffset, yOffset = self:getOffset() - if(xmlValue("layout", data)~=nil)then self:addLayout(xmlValue("layout", data)) end + if(xmlValue("layout", data)~=nil)then self:loadLayout(xmlValue("layout", data)) end if(xmlValue("xOffset", data)~=nil)then xOffset = xmlValue("xOffset", data) end if(xmlValue("yOffset", data)~=nil)then yOffset = xmlValue("yOffset", data) end self:setOffset(xOffset, yOffset) @@ -580,10 +584,12 @@ return { end, Thread = function(base, basalt) - local object = { + local object = { setValuesByXMLData = function(self, data, scripts) - base.setValuesByXMLData(self, data, scripts) - if(xmlValue("start", data)~=nil)then self:start(load(xmlValue("start", data), nil, "t", scripts.env)) end + if(xmlValue("start", data)~=nil)then + local f = load(xmlValue("start", data), nil, "t", scripts.env) + self:start(f) + end return self end, }