diff --git a/src/Basalt b/src/Basalt deleted file mode 160000 index 8b3b6f3..0000000 --- a/src/Basalt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8b3b6f3490243c9d62ddf9e616534a9499c73689 diff --git a/src/Basalt/Frame.lua b/src/Basalt/Frame.lua new file mode 100644 index 0000000..c5be088 --- /dev/null +++ b/src/Basalt/Frame.lua @@ -0,0 +1,1112 @@ +local Object = require("Object") +local _OBJECTS = require("loadObjects") +local BasaltDraw = require("basaltDraw") +local utils = require("utils") +local layout = require("layout") +local uuid = utils.uuid +local rpairs = utils.rpairs +local xmlValue = utils.getValueFromXML +local tableCount = utils.tableCount + +local sub,min,max = string.sub,math.min,math.max + +return function(name, parent, pTerm, basalt) + -- Frame + local base = Object(name) + local objectType = "Frame" + local objects = {} + local objZIndex = {} + local object = {} + local events = {} + local eventZIndex = {} + local variables = {} + local theme = {} + local dynamicValues = {} + local dynValueId = 0 + local termObject = pTerm or term.current() + + local monSide = "" + local isMonitor = false + local monitorAttached = false + local dragXOffset = 0 + local dragYOffset = 0 + local isScrollable = false + local scrollAmount = 0 + local mirrorActive = false + local mirrorAttached = false + local mirrorSide = "" + local isMovable = false + local isDragging =false + + local focusedObjectCache + local focusedObject + local autoSize = true + local autoScroll = true + local initialized = false + + local activeEvents = {} + + base:setZIndex(10) + + local basaltDraw = BasaltDraw(termObject) + + local cursorBlink = false + local xCursor = 1 + local yCursor = 1 + local cursorColor = colors.white + + local xOffset, yOffset = 0, 0 + + local lastXMLReferences = {} + + local function xmlDefaultValues(data, obj) + if(obj~=nil)then + obj:setValuesByXMLData(data) + end + end + + local function addXMLObjectType(tab, f, self) + if(tab~=nil)then + if(tab.properties~=nil)then tab = {tab} end + + for k,v in pairs(tab)do + local obj = f(self, v["@id"] or uuid()) + table.insert(lastXMLReferences, obj) + xmlDefaultValues(v, obj) + end + end + end + + local function getObject(name) + for _, value in pairs(objects) do + for _, b in pairs(value) do + if (b:getName() == name) then + return b + end + end + end + end + local function getDeepObject(name) + local o = getObject(name) + if(o~=nil)then return o end + for _, value in pairs(objects) do + for _, b in pairs(value) do + if (b:getType() == "Frame") then + local oF = b:getDeepObject(name) + if(oF~=nil)then return oF end + end + end + end + end + + local function addObject(obj) + local zIndex = obj:getZIndex() + if (getObject(obj.name) ~= nil) then + return nil + end + if (objects[zIndex] == nil) then + for x = 1, #objZIndex + 1 do + if (objZIndex[x] ~= nil) then + if (zIndex == objZIndex[x]) then + break + end + if (zIndex > objZIndex[x]) then + table.insert(objZIndex, x, zIndex) + break + end + else + table.insert(objZIndex, zIndex) + end + end + if (#objZIndex <= 0) then + table.insert(objZIndex, zIndex) + end + objects[zIndex] = {} + end + obj.parent = object + if(obj.init~=nil)then + obj:init() + end + table.insert(objects[zIndex], obj) + return obj + end + + local function removeEvents(self, obj) + for a, b in pairs(events) do + for c, d in pairs(b) do + for key, value in pairs(d) do + if (value == obj) then + table.remove(events[a][c], key) + if(self.parent~=nil)then + if(tableCount(events[a])<=0)then + self.parent:removeEvent(a, self) + end + end + end + end + end + end + end + + local function removeObject(obj) + for a, b in pairs(objects) do + for key, value in pairs(b) do + if (value == obj) then + table.remove(objects[a], key) + removeEvents(object, obj) + return true; + end + end + end + return false + end + + local function getEvent(self, event, name) + for _, value in pairs(events[event]) do + for _, b in pairs(value) do + if (b:getName() == name) then + return b + end + end + end + end + + local function addEvent(self, event, obj) + local zIndex = obj:getZIndex() + if(events[event]==nil)then events[event] = {} end + if(eventZIndex[event]==nil)then eventZIndex[event] = {} end + if (getEvent(self, event, obj.name) ~= nil) then + return nil + end + if(self.parent~=nil)then + self.parent:addEvent(event, self) + end + activeEvents[event] = true + if (events[event][zIndex] == nil) then + for x = 1, #eventZIndex[event] + 1 do + if (eventZIndex[event][x] ~= nil) then + if (zIndex == eventZIndex[event][x]) then + break + end + if (zIndex > eventZIndex[event][x]) then + table.insert(eventZIndex[event], x, zIndex) + break + end + else + table.insert(eventZIndex[event], zIndex) + end + end + if (#eventZIndex[event] <= 0) then + table.insert(eventZIndex[event], zIndex) + end + events[event][zIndex] = {} + end + table.insert(events[event][zIndex], obj) + return obj + end + + local function removeEvent(self, event, obj) + if(events[event]~=nil)then + for a, b in pairs(events[event]) do + for key, value in pairs(b) do + if (value == obj) then + table.remove(events[event][a], key) + if(#events[event][a]<=0)then + events[event][a] = nil + if(self.parent~=nil)then + if(tableCount(events[event])<=0)then + activeEvents[event] = false + self.parent:removeEvent(event, self) + end + end + end + return true; + end + end + end + end + return false + end + + local function stringToNumber(str) + local ok, err = pcall(load("return " .. str)) + if not(ok)then error(str.." is not a valid dynamic code") end + return load("return " .. str)() + end + + local function newDynamicValue(_, obj, str) + for k,v in pairs(dynamicValues)do + if(v[2]==str)and(v[4]==obj)then + return v + end + end + dynValueId = dynValueId + 1 + dynamicValues[dynValueId] = {0, str, {}, obj, dynValueId} + return dynamicValues[dynValueId] + end + + local function dynValueGetObjects(obj, str) + local names = {} + local t = {} + for v in str:gmatch("%a+%.x") do + local name = v:gsub("%.x", "") + if(name~="self")and(name~="parent")then + table.insert(names, name) end + end + for v in str:gmatch("%w+%.y") do + local name = v:gsub("%.y", "") + if(name~="self")and(name~="parent")then table.insert(names, name) end + end + for v in str:gmatch("%a+%.w") do + local name = v:gsub("%.w", "") + if(name~="self")and(name~="parent")then + table.insert(names, name) + + end + end + for v in str:gmatch("%a+%.h") do + local name = v:gsub("%.h", "") + if(name~="self")and(name~="parent")then + table.insert(names, name) end + end + for k,v in pairs(names)do + t[v] = getObject(v) + if(t[v]==nil)then + error("Dynamic Values - unable to find object "..v) + end + end + t["self"] = obj + t["parent"] = obj:getParent() + return t + end + + local function dynValueObjectToNumber(str, objList) + local newStr = str + for v in str:gmatch("%w+%.x") do + newStr = newStr:gsub(v, objList[v:gsub("%.x", "")]:getX()) + end + for v in str:gmatch("%w+%.y") do + newStr = newStr:gsub(v, objList[v:gsub("%.y", "")]:getY()) + end + for v in str:gmatch("%w+%.w") do + newStr = newStr:gsub(v, objList[v:gsub("%.w", "")]:getWidth()) + end + for v in str:gmatch("%w+%.h") do + newStr = newStr:gsub(v, objList[v:gsub("%.h", "")]:getHeight()) + end + return newStr + end + + + local function recalculateDynamicValues(self) + if(#dynamicValues>0)then + for n=1,dynValueId do + if(dynamicValues[n]~=nil)then + local numberStr + if(#dynamicValues[n][3]<=0)then dynamicValues[n][3] = dynValueGetObjects(dynamicValues[n][4], dynamicValues[n][2]) end + numberStr = dynValueObjectToNumber(dynamicValues[n][2], dynamicValues[n][3]) + dynamicValues[n][1] = stringToNumber(numberStr) + if(dynamicValues[n][4]:getType()=="Frame")then + dynamicValues[n][4]:recalculateDynamicValues() + end + end + end + for _, index in pairs(objZIndex) do + if (objects[index] ~= nil) then + for _, value in pairs(objects[index]) do + if (value.eventHandler ~= nil) then + value:eventHandler("dynamicValueEvent", self) + end + end + end + end + end + end + + local function getDynamicValue(id) + return dynamicValues[id][1] + end + + local function calculateMaxScroll(self) + for _, value in pairs(objects) do + for _, b in pairs(value) do + if(b.getHeight~=nil)and(b.getY~=nil)then + local h, y = b:getHeight(), b:getY() + if (h + y - self:getHeight() > scrollAmount) then + scrollAmount = max(h + y - self:getHeight(), 0) + end + end + end + end + end + + + local function focusSystem(self) + if(focusedObject~=focusedObjectCache)then + if(focusedObject~=nil)then + focusedObject:loseFocusHandler() + end + if(focusedObjectCache~=nil)then + focusedObjectCache:getFocusHandler() + end + focusedObject = focusedObjectCache + end + end + + object = { + barActive = false, + barBackground = colors.gray, + barTextcolor = colors.black, + barText = "New Frame", + barTextAlign = "left", + + addEvent = addEvent, + removeEvent = removeEvent, + removeEvents = removeEvents, + getEvent = getEvent, + + newDynamicValue = newDynamicValue, + recalculateDynamicValues = recalculateDynamicValues, + getDynamicValue = getDynamicValue, + + getType = function(self) + return objectType + end; + + setFocusedObject = function(self, obj) + focusedObjectCache = obj + focusSystem(self) + return self + end; + + getVariable = function(self, name) + return basalt.getVariable(name) + end, + + setSize = function(self, w, h, rel) + base.setSize(self, w, h, rel) + if(self.parent==nil)then + basaltDraw = BasaltDraw(termObject) + end + for _, index in pairs(objZIndex) do + if (objects[index] ~= nil) then + for _, value in pairs(objects[index]) do + if (value.eventHandler ~= nil) then + value:eventHandler("basalt_resize", value, self) + end + end + end + end + self:recalculateDynamicValues() + autoSize = false + return self + end; + + setTheme = function(self, _theme, col) + if(type(_theme)=="table")then + theme = _theme + elseif(type(_theme)=="string")then + theme[_theme] = col + end + self:updateDraw() + return self + end, + + getTheme = function(self, name) + return theme[name] or (self.parent~=nil and self.parent:getTheme(name) or basalt.getTheme(name)) + end, + + setPosition = function(self, x, y, rel) + base.setPosition(self, x, y, rel) + for _, index in pairs(objZIndex) do + if (objects[index] ~= nil) then + for _, value in pairs(objects[index]) do + if (value.eventHandler ~= nil) then + value:eventHandler("basalt_reposition", value, self) + end + end + end + end + self:recalculateDynamicValues() + return self + end; + + getBasaltInstance = function(self) + return basalt + end, + + setOffset = function(self, xO, yO) + xOffset = xO ~= nil and math.floor(xO < 0 and math.abs(xO) or -xO) or xOffset + yOffset = yO ~= nil and math.floor(yO < 0 and math.abs(yO) or -yO) or yOffset + self:updateDraw() + return self + end; + + getOffsetInternal = function(self) + return xOffset, yOffset + end; + + getOffset = function(self) + return xOffset < 0 and math.abs(xOffset) or -xOffset, yOffset < 0 and math.abs(yOffset) or -yOffset + end; + + removeFocusedObject = function(self) + focusedObjectCache = nil + focusSystem(self) + return self + end; + + getFocusedObject = function(self) + return focusedObject + end; + + setCursor = function(self, _blink, _xCursor, _yCursor, color) + if(self.parent~=nil)then + local obx, oby = self:getAnchorPosition() + self.parent:setCursor(_blink or false, (_xCursor or 0)+obx-1, (_yCursor or 0)+oby-1, color or cursorColor) + else + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition(self:getX(), self:getY(), true)) + cursorBlink = _blink or false + if (_xCursor ~= nil) then + xCursor = obx + _xCursor - 1 + end + if (_yCursor ~= nil) then + yCursor = oby + _yCursor - 1 + end + cursorColor = color or cursorColor + if (cursorBlink) then + termObject.setTextColor(cursorColor) + termObject.setCursorPos(xCursor, yCursor) + termObject.setCursorBlink(cursorBlink) + else + termObject.setCursorBlink(false) + end + end + return self + end; + + setMovable = function(self, movable) + if(self.parent~=nil)then + isMovable = movable or not isMovable + self.parent:addEvent("mouse_click", self) + activeEvents["mouse_click"] = true + self.parent:addEvent("mouse_up", self) + activeEvents["mouse_up"] = true + self.parent:addEvent("mouse_drag", self) + activeEvents["mouse_drag"] = true + end + return self; + end; + + setScrollable = function(self, scrollable) + isScrollable = (scrollable or scrollable==nil) and true or false + if(self.parent~=nil)then + self.parent:addEvent("mouse_scroll", self) + end + activeEvents["mouse_scroll"] = true + return self + end, + + setScrollAmount = function(self, max) + scrollAmount = max or scrollAmount + autoScroll = false + return self + end, + + + getScrollAmount = function(self) + return autoScroll and scrollAmount or calculateMaxScroll(self) + end, + + show = function(self) + base.show(self) + if(self.parent==nil)then + basalt.setActiveFrame(self) + if(isMonitor)then + basalt.setMonitorFrame(monSide, self) + else + basalt.setMainFrame(self) + end + end + return self; + end; + + hide = function (self) + base.hide(self) + if(self.parent==nil)then + if(activeFrame == self)then activeFrame = nil end + if(isMonitor)then + if(basalt.getMonitorFrame(monSide) == self)then + basalt.setActiveFrame(nil) + end + else + if(basalt.getMainFrame() == self)then + basalt.setMainFrame(nil) + end + end + end + return self + end; + + addLayout = function(self, file) + if(file~=nil)then + if(fs.exists(file))then + local f = fs.open(file, "r") + local data = layout:ParseXmlText(f.readAll()) + f.close() + lastXMLReferences = {} + self:setValuesByXMLData(data) + end + end + return self + end, + + getLastLayout = function(self) + return lastXMLReferences + end, + + addLayoutFromString = function(self, str) + if(str~=nil)then + local data = layout:ParseXmlText(str) + self:setValuesByXMLData(data) + end + return self + end, + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("movable", data)~=nil)then if(xmlValue("movable", data))then self:setMovable(true) end end + if(xmlValue("scrollable", data)~=nil)then if(xmlValue("scrollable", data))then self:setScrollable(true) end end + if(xmlValue("monitor", data)~=nil)then self:setMonitor(xmlValue("monitor", data)):show() end + if(xmlValue("mirror", data)~=nil)then self:setMirror(xmlValue("mirror", data)) end + if(xmlValue("bar", data)~=nil)then if(xmlValue("bar", data))then self:showBar(true) else self:showBar(false) end end + if(xmlValue("barText", data)~=nil)then self.barText = xmlValue("barText", data) end + if(xmlValue("barBG", data)~=nil)then self.barBackground = colors[xmlValue("barBG", data)] end + if(xmlValue("barFG", data)~=nil)then self.barTextcolor = colors[xmlValue("barFG", data)] end + if(xmlValue("barAlign", data)~=nil)then self.barTextAlign = xmlValue("barAlign", data) end + if(xmlValue("layout", data)~=nil)then self:addLayout(xmlValue("layout", data)) end + if(xmlValue("xOffset", data)~=nil)then self:setOffset(xmlValue("xOffset", data), yOffset) end + if(xmlValue("yOffset", data)~=nil)then self:setOffset(yOffset, xmlValue("yOffset", data)) end + if(xmlValue("scrollAmount", data)~=nil)then self:setScrollAmount(xmlValue("scrollAmount", data)) end + + local objectList = data:children() + + for k,v in pairs(objectList)do + if(v.___name~="animation")then + local name = v.___name:gsub("^%l", string.upper) + if(_OBJECTS[name]~=nil)then + addXMLObjectType(v, self["add"..name], self) + end + end + end + + addXMLObjectType(data["frame"], self.addFrame, self) + addXMLObjectType(data["animation"], self.addAnimation, self) + return self + end, + + showBar = function(self, showIt) -- deprecated + self.barActive = showIt or not self.barActive + self:updateDraw() + return self + end; + + setBar = function(self, text, bgCol, fgCol) -- deprecated + self.barText = text or "" + self.barBackground = bgCol or self.barBackground + self.barTextcolor = fgCol or self.barTextcolor + self:updateDraw() + return self + end; + + setBarTextAlign = function(self, align) -- deprecated + self.barTextAlign = align or "left" + self:updateDraw() + return self + end; + + setMirror = function(self, side) + if(self.parent~=nil)then error("Frame has to be a base frame in order to attach a mirror.") end + mirrorSide = side + if(mirror~=nil)then + basaltDraw.setMirror(mirror) + end + mirrorActive = true + return self + end, + + removeMirror = function(self) + mirror = nil + mirrorActive = false + basaltDraw.setMirror(nil) + return self + end, + + setMonitor = function(self, side) + if(side~=nil)and(side~=false)then + if(peripheral.getType(side)=="monitor")then + termObject = peripheral.wrap(side) + monitorAttached = true + + end + if(self.parent~=nil)then + self.parent:removeObject(self) + end + isMonitor = true + basalt.setMonitorFrame(side, self) + else + termObject = parentTerminal + isMonitor = false + if(basalt.getMonitorFrame(monSide)==self)then + basalt.setMonitorFrame(monSide, nil) + end + end + basaltDraw = BasaltDraw(termObject) + self:setSize(termObject.getSize()) + autoSize = true + monSide = side or nil + self:updateDraw() + return self; + end; + + loseFocusHandler = function(self) + base.loseFocusHandler(self) + if(focusedObject~=nil)then focusedObject:loseFocusHandler() focusedObject = nil end + end; + + getFocusHandler = function(self) + base.getFocusHandler(self) + if (self.parent ~= nil) then + if(isMovable)then + self.parent:removeEvents(self) + self.parent:removeObject(self) + self.parent:addObject(self) + for k,v in pairs(activeEvents)do + if(v)then + self.parent:addEvent(k, self) + end + end + self:updateDraw() + end + end + if(focusedObject~=nil)then focusedObject:getFocusHandler() end + end; + + eventHandler = function(self, event, p1, p2, p3, p4) + base.eventHandler(self, event, p1, p2, p3, p4) + if(events["other_event"]~=nil)then + for _, index in ipairs(eventZIndex["other_event"]) do + if (events["other_event"][index] ~= nil) then + for _, value in rpairs(events["other_event"][index]) do + if (value.eventHandler ~= nil) then + if (value:eventHandler(event, p1, p2, p3, p4)) then + return true + end + end + end + end + end + end + if(autoSize)and not(isMonitor)then + if(self.parent==nil)then + if(event=="term_resize")then + self:setSize(termObject.getSize()) + autoSize = true + end + end + end + if(isMonitor)then + if(autoSize)then + if(event=="monitor_resize")and(p1==monSide)then + self:setSize(termObject.getSize()) + autoSize = true + self:updateDraw() + end + end + if(event == "peripheral")and(p1==monSide)then + if(peripheral.getType(monSide)=="monitor")then + monitorAttached = true + termObject = peripheral.wrap(monSide) + basaltDraw = BasaltDraw(termObject) + self:updateDraw() + end + end + if(event == "peripheral_detach")and(p1==monSide)then + monitorAttached = false + end + end + if(mirrorActive)then + if(peripheral.getType(mirrorSide)=="monitor")then + mirrorAttached = true + basaltDraw.setMirror(peripheral.wrap(mirrorSide)) + end + if(event == "peripheral_detach")and(p1==mirrorSide)then + monitorAttached = false + end + if(event=="monitor_touch")and(mirrorSide==p1)then + self:mouseHandler(1, p2, p3, true) + end + end + if (event == "terminate")and(self.parent==nil)then + basalt.stop() + end + end, + + mouseHandler = function(self, button, x, y) + if(base.mouseHandler(self, button, x, y))then + if(events["mouse_click"]~=nil)then + self:setCursor(false) + for _, index in ipairs(eventZIndex["mouse_click"]) do + if (events["mouse_click"][index] ~= nil) then + for _, value in rpairs(events["mouse_click"][index]) do + if (value.mouseHandler ~= nil) then + if (value:mouseHandler(button, x, y)) then + focusSystem(self) + return true + end + end + end + end + end + end + if (isMovable) then + local fx, fy = self:getAbsolutePosition(self:getAnchorPosition()) + if (x >= fx) and (x <= fx + self:getWidth() - 1) and (y == fy)then + isDragging = true + dragXOffset = fx - x + dragYOffset = yOff and 1 or 0 + end + end + self:removeFocusedObject() + return true + end + return false + end, + + mouseUpHandler = function(self, button, x, y) + if (isDragging) then + isDragging = false + end + if(base.mouseUpHandler(self, button, x, y))then + if(events["mouse_up"]~=nil)then + for _, index in ipairs(eventZIndex["mouse_up"]) do + if (events["mouse_up"][index] ~= nil) then + for _, value in rpairs(events["mouse_up"][index]) do + if (value.mouseUpHandler ~= nil) then + if (value:mouseUpHandler(button, x, y)) then + focusSystem(self) + return true + end + end + end + end + end + end + focusSystem(self) + return true + end + return false + end, + + scrollHandler = function(self, dir, x, y) + if(base.scrollHandler(self, dir, x, y))then + if(events["mouse_scroll"]~=nil)then + for _, index in pairs(eventZIndex["mouse_scroll"]) do + if (events["mouse_scroll"][index] ~= nil) then + for _, value in rpairs(events["mouse_scroll"][index]) do + if (value.scrollHandler ~= nil) then + if (value:scrollHandler(dir, x, y)) then + focusSystem(self) + return true + end + end + end + end + end + end + local cache = yOffset + if(isScrollable)then + calculateMaxScroll(self) + if(dir>0)or(dir<0)then + yOffset = max(min(yOffset-dir, 0),-scrollAmount) + self:updateDraw() + end + end + self:removeFocusedObject() + if(yOffset==cache)then return false end + return true + end + return false + end, + + dragHandler = function(self, button, x, y) + if (isDragging) then + local xO, yO = self.parent:getOffsetInternal() + xO = xO < 0 and math.abs(xO) or -xO + yO = yO < 0 and math.abs(yO) or -yO + local parentX = 1 + local parentY = 1 + if (self.parent ~= nil) then + parentX, parentY = self.parent:getAbsolutePosition(self.parent:getAnchorPosition()) + end + self:setPosition(x + dragXOffset - (parentX - 1) + xO, y + dragYOffset - (parentY - 1) + yO) + self:updateDraw() + return true + end + if(self:isVisible())and(self:isEnabled())then + if(events["mouse_drag"]~=nil)then + for _, index in ipairs(eventZIndex["mouse_drag"]) do + if (events["mouse_drag"][index] ~= nil) then + for _, value in rpairs(events["mouse_drag"][index]) do + if (value.dragHandler ~= nil) then + if (value:dragHandler(button, x, y)) then + focusSystem(self) + return true + end + end + end + end + end + end + end + focusSystem(self) + base.dragHandler(self, button, x, y) + return false + end, + + keyHandler = function(self, key, isHolding) + if (self:isFocused())or(self.parent==nil)then + local val = self:getEventSystem():sendEvent("key", self, "key", key) + if(val==false)then return false end + if(events["key"]~=nil)then + for _, index in pairs(eventZIndex["key"]) do + if (events["key"][index] ~= nil) then + for _, value in rpairs(events["key"][index]) do + if (value.keyHandler ~= nil) then + if (value:keyHandler(key, isHolding)) then + return true + end + end + end + end + end + end + end + return false + end, + + keyUpHandler = function(self, key) + if (self:isFocused())or(self.parent==nil)then + local val = self:getEventSystem():sendEvent("key_up", self, "key_up", key) + if(val==false)then return false end + if(events["key_up"]~=nil)then + for _, index in pairs(eventZIndex["key_up"]) do + if (events["key_up"][index] ~= nil) then + for _, value in rpairs(events["key_up"][index]) do + if (value.keyUpHandler ~= nil) then + if (value:keyUpHandler(key)) then + return true + end + end + end + end + end + end + end + return false + end, + + charHandler = function(self, char) + if (self:isFocused())or(self.parent==nil)then + local val = self:getEventSystem():sendEvent("char", self, "char", char) + if(val==false)then return false end + if(events["char"]~=nil)then + for _, index in pairs(eventZIndex["char"]) do + if (events["char"][index] ~= nil) then + for _, value in rpairs(events["char"][index]) do + if (value.charHandler ~= nil) then + if (value:charHandler(char)) then + return true + end + end + end + end + end + end + end + return false + end, + + setText = function(self, x, y, text) + local obx, oby = self:getAnchorPosition() + if (y >= 1) and (y <= self:getHeight()) then + if (self.parent ~= nil) then + self.parent:setText(max(x + (obx - 1), obx), oby + y - 1, sub(text, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1))) + else + basaltDraw.setText(max(x + (obx - 1), obx), oby + y - 1, sub(text, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1))) + end + end + end; + + setBG = function(self, x, y, bgCol) + local obx, oby = self:getAnchorPosition() + if (y >= 1) and (y <= self:getHeight()) then + if (self.parent ~= nil) then + self.parent:setBG(max(x + (obx - 1), obx), oby + y - 1, sub(bgCol, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1))) + else + basaltDraw.setBG(max(x + (obx - 1), obx), oby + y - 1, sub(bgCol, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1))) + end + end + end; + + setFG = function(self, x, y, fgCol) + local obx, oby = self:getAnchorPosition() + if (y >= 1) and (y <= self:getHeight()) then + if (self.parent ~= nil) then + self.parent:setFG(max(x + (obx - 1), obx), oby + y - 1, sub(fgCol, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1))) + else + basaltDraw.setFG(max(x + (obx - 1), obx), oby + y - 1, sub(fgCol, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1))) + end + end + end; + + writeText = function(self, x, y, text, bgCol, fgCol) + local obx, oby = self:getAnchorPosition() + if (y >= 1) and (y <= self:getHeight()) then + if (self.parent ~= nil) then + self.parent:writeText(max(x + (obx - 1), obx), oby + y - 1, sub(text, max(1 - x + 1, 1), self:getWidth() - x + 1), bgCol, fgCol) + else + basaltDraw.writeText(max(x + (obx - 1), obx), oby + y - 1, sub(text, max(1 - x + 1, 1), max(self:getWidth() - x + 1,1)), bgCol, fgCol) + end + end + end; + + drawBackgroundBox = function(self, x, y, width, height, bgCol) + local obx, oby = self:getAnchorPosition() + + height = (y < 1 and (height + y > self:getHeight() and self:getHeight() or height + y - 1) or (height + y > self:getHeight() and self:getHeight() - y + 1 or height)) + width = (x < 1 and (width + x > self:getWidth() and self:getWidth() or width + x - 1) or (width + x > self:getWidth() and self:getWidth() - x + 1 or width)) + if (self.parent ~= nil) then + self.parent:drawBackgroundBox(max(x + (obx - 1), obx), max(y + (oby - 1), oby), width, height, bgCol) + else + basaltDraw.drawBackgroundBox(max(x + (obx - 1), obx), max(y + (oby - 1), oby), width, height, bgCol) + end + end; + + drawTextBox = function(self, x, y, width, height, symbol) + local obx, oby = self:getAnchorPosition() + height = (y < 1 and (height + y > self:getHeight() and self:getHeight() or height + y - 1) or (height + y > self:getHeight() and self:getHeight() - y + 1 or height)) + width = (x < 1 and (width + x > self:getWidth() and self:getWidth() or width + x - 1) or (width + x > self:getWidth() and self:getWidth() - x + 1 or width)) + if (self.parent ~= nil) then + self.parent:drawTextBox(max(x + (obx - 1), obx), max(y + (oby - 1), oby), width, height, sub(symbol,1,1)) + else + basaltDraw.drawTextBox(max(x + (obx - 1), obx), max(y + (oby - 1), oby), width, height, sub(symbol,1,1)) + end + end; + + drawForegroundBox = function(self, x, y, width, height, fgCol) + local obx, oby = self:getAnchorPosition() + height = (y < 1 and (height + y > self:getHeight() and self:getHeight() or height + y - 1) or (height + y > self:getHeight() and self:getHeight() - y + 1 or height)) + width = (x < 1 and (width + x > self:getWidth() and self:getWidth() or width + x - 1) or (width + x > self:getWidth() and self:getWidth() - x + 1 or width)) + if (self.parent ~= nil) then + self.parent:drawForegroundBox(max(x + (obx - 1), obx), max(y + (oby - 1), oby), width, height, fgCol) + else + basaltDraw.drawForegroundBox(max(x + (obx - 1), obx), max(y + (oby - 1), oby), width, height, fgCol) + end + end; + + draw = function(self, force) + if(isMonitor)and not(monitorAttached)then return false end; + if(self.parent==nil)then if(self:getDraw()==false)then return false end end + if (base.draw(self))then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local anchx, anchy = self:getAnchorPosition() + local w,h = self:getSize() + if (self.parent == nil) then + if(self.bgColor~=false)then + basaltDraw.drawBackgroundBox(anchx, anchy, w, h, self.bgColor) + basaltDraw.drawTextBox(anchx, anchy, w, h, " ") + end + if(self.fgColor~=false)then basaltDraw.drawForegroundBox(anchx, anchy, w, h, self.fgColor) end + end + if (self.barActive) then + if (self.parent ~= nil) then + self.parent:writeText(anchx, anchy, utils.getTextHorizontalAlign(self.barText, w, self.barTextAlign), self.barBackground, self.barTextcolor) + else + basaltDraw.writeText(anchx, anchy, utils.getTextHorizontalAlign(self.barText, w, self.barTextAlign), self.barBackground, self.barTextcolor) + end + if(self:getBorder("left"))then + if (self.parent ~= nil) then + self.parent:drawBackgroundBox(anchx-1, anchy, 1, 1, self.barBackground) + if(self.bgColor~=false)then + self.parent:drawBackgroundBox(anchx-1, anchy+1, 1, h-1, self.bgColor) + end + end + end + if(self:getBorder("top"))then + if (self.parent ~= nil) then + self.parent:drawBackgroundBox(anchx-1, anchy-1, w+1, 1, self.barBackground) + end + end + end + + for _, index in rpairs(objZIndex) do + if (objects[index] ~= nil) then + for _, value in pairs(objects[index]) do + if (value.draw ~= nil) then + value:draw() + end + end + end + end + end + end; + + updateTerm = function(self) + if(isMonitor)and not(monitorAttached)then return false end; + basaltDraw.update() + end; + + addObject = function(self, obj) + return addObject(obj) + end; + + removeObject = function(self, obj) + return removeObject(obj) + end; + + getObject = function(self, obj) + return getObject(obj) + end; + getDeepObject = function(self, name) + return getDeepObject(name) + end, + + addFrame = function(self, name) + local obj = basalt.newFrame(name or uuid(), self, nil, basalt) + return addObject(obj) + end, + + init = function(self) + if not(initialized)then + if (parent ~= nil) then + base.width, base.height = parent:getSize() + self:setBackground(parent:getTheme("FrameBG")) + self:setForeground(parent:getTheme("FrameText")) + else + base.width, base.height = termObject.getSize() + self:setBackground(basalt.getTheme("BasaltBG")) + self:setForeground(basalt.getTheme("BasaltText")) + end + initialized = true + end + end, + } + for k,v in pairs(_OBJECTS)do + object["add"..k] = function(self, name) + return addObject(v(name or uuid(), self)) + end + end + setmetatable(object, base) + return object +end diff --git a/src/Basalt/Object.lua b/src/Basalt/Object.lua new file mode 100644 index 0000000..ccc7b6b --- /dev/null +++ b/src/Basalt/Object.lua @@ -0,0 +1,867 @@ +local basaltEvent = require("basaltEvent") +local utils = require("utils") +local split = utils.splitString +local numberFromString = utils.numberFromString +local xmlValue = utils.getValueFromXML + +return function(name) + -- Base object + local objectType = "Object" -- not changeable + local object = {} + local zIndex = 1 + local value + local anchor = "topLeft" + local ignOffset = false + local isVisible = true + local initialized = false + + local shadow = false + local borderColors = { + left = false, + right = false, + top = false, + bottom = false + } + + local shadowColor = colors.black + local isEnabled = true + local isDragging = false + local dragStartX, dragStartY, dragXOffset, dragYOffset = 0, 0, 0, 0 + + local draw = true + local activeEvents = {} + + local eventSystem = basaltEvent() + + object = { + x = 1, + y = 1, + width = 1, + height = 1, + bgColor = colors.black, + bgSymbol = " ", + bgSymbolColor = colors.black, + fgColor = colors.white, + transparentColor = false, + name = name or "Object", + parent = nil, + + show = function(self) + isVisible = true + self:updateDraw() + return self + end; + + hide = function(self) + isVisible = false + self:updateDraw() + return self + end, + + enable = function(self) + isEnabled = true + return self + end, + + disable = function(self) + isEnabled = false + return self + end, + + isEnabled = function(self) + return isEnabled + end, + + generateXMLEventFunction = function(self, func, val) + local createF = function(str) + if(str:sub(1,1)=="#")then + local o = self:getBaseFrame():getDeepObject(str:sub(2,str:len())) + if(o~=nil)and(o.internalObjetCall~=nil)then + func(self,function()o:internalObjetCall()end) + end + else + func(self,self:getBaseFrame():getVariable(str)) + end + end + if(type(val)=="string")then + createF(val) + elseif(type(val)=="table")then + for k,v in pairs(val)do + createF(v) + end + end + return self + end, + + setValuesByXMLData = function(self, data) + local baseFrame = self:getBaseFrame() + if(xmlValue("x", data)~=nil)then self:setPosition(xmlValue("x", data), self.y) end + if(xmlValue("y", data)~=nil)then self:setPosition(self.x, xmlValue("y", data)) end + if(xmlValue("width", data)~=nil)then self:setSize(xmlValue("width", data), self.height) end + if(xmlValue("height", data)~=nil)then self:setSize(self.width, xmlValue("height", data)) end + if(xmlValue("bg", data)~=nil)then self:setBackground(colors[xmlValue("bg", data)]) end + if(xmlValue("fg", data)~=nil)then self:setForeground(colors[xmlValue("fg", data)]) end + if(xmlValue("value", data)~=nil)then self:setValue(colors[xmlValue("value", data)]) end + if(xmlValue("visible", data)~=nil)then if(xmlValue("visible", data))then self:show() else self:hide() end end + if(xmlValue("enabled", data)~=nil)then if(xmlValue("enabled", data))then self:enable() else self:disable() end end + if(xmlValue("zIndex", data)~=nil)then self:setZIndex(xmlValue("zIndex", data)) end + if(xmlValue("anchor", data)~=nil)then self:setAnchor(xmlValue("anchor", data)) end + if(xmlValue("shadowColor", data)~=nil)then self:setShadow(colors[xmlValue("shadowColor", data)]) end + if(xmlValue("border", data)~=nil)then self:setBorder(colors[xmlValue("border", data)]) end + if(xmlValue("borderLeft", data)~=nil)then borderColors["left"] = xmlValue("borderLeft", data) end + if(xmlValue("borderTop", data)~=nil)then borderColors["top"] = xmlValue("borderTop", data) end + if(xmlValue("borderRight", data)~=nil)then borderColors["right"] = xmlValue("borderRight", data) end + if(xmlValue("borderBottom", data)~=nil)then borderColors["bottom"] = xmlValue("borderBottom", data) end + if(xmlValue("borderColor", data)~=nil)then self:setBorder(colors[xmlValue("borderColor", data)]) end + if(xmlValue("ignoreOffset", data)~=nil)then if(xmlValue("ignoreOffset", data))then self:ignoreOffset(true) end end + if(xmlValue("onClick", data)~=nil)then self:generateXMLEventFunction(self.onClick, xmlValue("onClick", data)) end + if(xmlValue("onClickUp", data)~=nil)then self:generateXMLEventFunction(self.onClickUp, xmlValue("onClickUp", data)) end + if(xmlValue("onScroll", data)~=nil)then self:generateXMLEventFunction(self.onScroll, xmlValue("onScroll", data)) end + if(xmlValue("onDrag", data)~=nil)then self:generateXMLEventFunction(self.onDrag, xmlValue("onDrag", data)) end + if(xmlValue("onKey", data)~=nil)then self:generateXMLEventFunction(self.onKey, xmlValue("onKey", data)) end + if(xmlValue("onKeyUp", data)~=nil)then self:generateXMLEventFunction(self.onKeyUp, xmlValue("onKeyUp", data)) end + if(xmlValue("onChange", data)~=nil)then self:generateXMLEventFunction(self.onChange, xmlValue("onChange", data)) end + if(xmlValue("onResize", data)~=nil)then self:generateXMLEventFunction(self.onResize, xmlValue("onResize", data)) end + if(xmlValue("onReposition", data)~=nil)then self:generateXMLEventFunction(self.onReposition, xmlValue("onReposition", data)) end + if(xmlValue("onEvent", data)~=nil)then self:generateXMLEventFunction(self.onEvent, xmlValue("onEvent", data)) end + if(xmlValue("onGetFocus", data)~=nil)then self:generateXMLEventFunction(self.onGetFocus, xmlValue("onGetFocus", data)) end + if(xmlValue("onLoseFocus", data)~=nil)then self:generateXMLEventFunction(self.onLoseFocus, xmlValue("onLoseFocus", data)) end + self:updateDraw() + return self + end, + + isVisible = function(self) + return isVisible + end; + + setFocus = function(self) + if (self.parent ~= nil) then + self.parent:setFocusedObject(self) + end + return self + end; + + setZIndex = function(self, index) + zIndex = index + if (self.parent ~= nil) then + self.parent:removeObject(self) + self.parent:addObject(self) + self:updateEventHandlers() + end + + return self + end, + + updateEventHandlers = function(self) + for k,v in pairs(activeEvents)do + if(v)then + self.parent:addEvent(k, self) + end + end + end, + + getZIndex = function(self) + return zIndex; + end; + + getType = function(self) + return objectType + end; + + getName = function(self) + return self.name + end; + + remove = function(self) + if (self.parent ~= nil) then + self.parent:removeObject(self) + end + self:updateDraw() + return self + end; + + setParent = function(self, frame) + if (frame.getType ~= nil and frame:getType() == "Frame") then + self:remove() + frame:addObject(self) + if (self.draw) then + self:show() + end + end + return self + end; + + setValue = function(self, _value) + if (value ~= _value) then + value = _value + self:updateDraw() + self:valueChangedHandler() + end + return self + end; + + getValue = function(self) + return value + end; + + getDraw = function(self) + return draw + end; + + updateDraw = function(self, change) + draw = change + if(change == nil)then draw = true end + if(draw)then if(self.parent~=nil)then self.parent:updateDraw() end end + return self + end; + + + getEventSystem = function(self) + return eventSystem + end; + + + getParent = function(self) + return self.parent + end; + + setPosition = function(self, xPos, yPos, rel) + if(type(xPos)=="number")then + self.x = rel and self:getX()+xPos or xPos + end + if(type(yPos)=="number")then + self.y = rel and self:getY()+yPos or yPos + end + if(self.parent~=nil)then + if(type(xPos)=="string")then + self.x = self.parent:newDynamicValue(self, xPos) + end + if(type(yPos)=="string")then + self.y = self.parent:newDynamicValue(self, yPos) + end + self.parent:recalculateDynamicValues() + end + eventSystem:sendEvent("basalt_reposition", self) + self:updateDraw() + return self + end; + + getX = function(self) + return type(self.x) == "number" and self.x or math.floor(self.x[1]+0.5) + end; + + getY = function(self) + return type(self.y) == "number" and self.y or math.floor(self.y[1]+0.5) + end; + + getPosition = function(self) + return self:getX(), self:getY() + end; + + getVisibility = function(self) + return isVisible + end; + + setVisibility = function(self, _isVisible) + isVisible = _isVisible or not isVisible + self:updateDraw() + return self + end; + + setSize = function(self, width, height, rel) + if(type(width)=="number")then + self.width = rel and self.width+width or width + end + if(type(height)=="number")then + self.height = rel and self.height+height or height + end + if(self.parent~=nil)then + if(type(width)=="string")then + self.width = self.parent:newDynamicValue(self, width) + end + if(type(height)=="string")then + self.height = self.parent:newDynamicValue(self, height) + end + self.parent:recalculateDynamicValues() + end + eventSystem:sendEvent("basalt_resize", self) + self:updateDraw() + return self + end; + + getHeight = function(self) + return type(self.height) == "number" and self.height or math.floor(self.height[1]+0.5) + end; + + getWidth = function(self) + return type(self.width) == "number" and self.width or math.floor(self.width[1]+0.5) + end; + + getSize = function(self) + return self:getWidth(), self:getHeight() + end; + + calculateDynamicValues = function(self) + if(type(self.width)=="table")then self.width:calculate() end + if(type(self.height)=="table")then self.height:calculate() end + if(type(self.x)=="table")then self.x:calculate() end + if(type(self.y)=="table")then self.y:calculate() end + self:updateDraw() + return self + end, + + setBackground = function(self, color, symbol, symbolCol) + self.bgColor = color or false + self.bgSymbol = symbol or (self.bgColor~=false and self.bgSymbol or false) + self.bgSymbolColor = symbolCol or self.bgSymbolColor + self:updateDraw() + return self + end; + + setTransparent = function(self, color) + self.transparentColor = color or false + self.bgSymbol = false + self.bgSymbolColor = false + self:updateDraw() + return self + end; + + getBackground = function(self) + return self.bgColor + end; + + setForeground = function(self, color) + self.fgColor = color or false + self:updateDraw() + return self + end; + + getForeground = function(self) + return self.fgColor + end; + + setShadow = function(self, color) + if(color==false)then + shadow = false + else + shadowColor = color + shadow = true + end + self:updateDraw() + return self + end; + + isShadowActive = function(self) + return shadow; + end; + + setBorder = function(self, ...) + if(...~=nil)then + local t = {...} + for k,v in pairs(t)do + if(v=="left")or(#t==1)then + borderColors["left"] = t[1] + end + if(v=="top")or(#t==1)then + borderColors["top"] = t[1] + end + if(v=="right")or(#t==1)then + borderColors["right"] = t[1] + end + if(v=="bottom")or(#t==1)then + borderColors["bottom"] = t[1] + end + end + end + self:updateDraw() + return self + end; + + getBorder = function(self, side) + if(side=="left")then + return borderLeft; + end + if(side=="top")then + return borderTop; + end + if(side=="right")then + return borderRight; + end + if(side=="bottom")then + return borderBottom; + end + end; + + draw = function(self) + if (isVisible)then + if(self.parent~=nil)then + local x, y = self:getAnchorPosition() + local w,h = self:getSize() + local wP,hP = self.parent:getSize() + if(x+w<1)or(x>wP)or(y+h<1)or(y>hP)then return false end + if(self.transparentColor~=false)then + self.parent:drawForegroundBox(x, y, w, h, self.transparentColor) + end + if(self.bgColor~=false)then + self.parent:drawBackgroundBox(x, y, w, h, self.bgColor) + end + if(self.bgSymbol~=false)then + self.parent:drawTextBox(x, y, w, h, self.bgSymbol) + if(self.bgSymbol~=" ")then + self.parent:drawForegroundBox(x, y, w, h, self.bgSymbolColor) + end + end + if(shadow)then + self.parent:drawBackgroundBox(x+1, y+h, w, 1, shadowColor) + self.parent:drawBackgroundBox(x+w, y+1, 1, h, shadowColor) + self.parent:drawForegroundBox(x+1, y+h, w, 1, shadowColor) + self.parent:drawForegroundBox(x+w, y+1, 1, h, shadowColor) + end + if(borderColors["left"]~=false)then + self.parent:drawTextBox(x-1, y, 1, h, "\149") + self.parent:drawBackgroundBox(x-1, y, 1, h, self.bgColor) + self.parent:drawForegroundBox(x-1, y, 1, h, borderColors["left"]) + end + if(borderColors["left"]~=false)and(borderColors["top"]~=false)then + self.parent:drawTextBox(x-1, y-1, 1, 1, "\151") + self.parent:drawBackgroundBox(x-1, y-1, 1, 1, self.bgColor) + self.parent:drawForegroundBox(x-1, y-1, 1, 1, borderColors["left"]) + end + if(borderColors["top"]~=false)then + + self.parent:drawTextBox(x, y-1, w, 1, "\131") + self.parent:drawBackgroundBox(x, y-1, w, 1, self.bgColor) + self.parent:drawForegroundBox(x, y-1, w, 1, borderColors["top"]) + end + if(borderColors["top"]~=false)and(borderColors["right"]~=false)then + self.parent:drawTextBox(x+w, y-1, 1, 1, "\148") + self.parent:drawForegroundBox(x+w, y-1, 1, 1, self.bgColor) + self.parent:drawBackgroundBox(x+w, y-1, 1, 1, borderColors["right"]) + end + if(borderColors["right"]~=false)then + self.parent:drawTextBox(x+w, y, 1, h, "\149") + self.parent:drawForegroundBox(x+w, y, 1, h, self.bgColor) + self.parent:drawBackgroundBox(x+w, y, 1, h, borderColors["right"]) + end + if(borderColors["right"]~=false)and(borderColors["bottom"]~=false)then + self.parent:drawTextBox(x+w, y+h, 1, 1, "\133") + self.parent:drawForegroundBox(x+w, y+h, 1, 1, self.bgColor) + self.parent:drawBackgroundBox(x+w, y+h, 1, 1, borderColors["right"]) + end + if(borderColors["bottom"]~=false)then + self.parent:drawTextBox(x, y+h, w, 1, "\143") + self.parent:drawForegroundBox(x, y+h, w, 1, self.bgColor) + self.parent:drawBackgroundBox(x, y+h, w, 1, borderColors["bottom"]) + end + if(borderColors["bottom"]~=false)and(borderColors["left"]~=false)then + self.parent:drawTextBox(x-1, y+h, 1, 1, "\138") + self.parent:drawForegroundBox(x-1, y+h, 1, 1, self.bgColor) + self.parent:drawBackgroundBox(x-1, y+h, 1, 1, borderColors["left"]) + end + end + draw = false + return true + end + return false + end; + + + getAbsolutePosition = function(self, x, y) + -- relative position to absolute position + if (x == nil) or (y == nil) then + x, y = self:getAnchorPosition() + end + + if (self.parent ~= nil) then + local fx, fy = self.parent:getAbsolutePosition() + x = fx + x - 1 + y = fy + y - 1 + end + return x, y + end; + + getAnchorPosition = function(self, x, y, ignOff) + if (x == nil) then + x = self:getX() + end + if (y == nil) then + y = self:getY() + end + if(self.parent~=nil)then + local pw,ph = self.parent:getSize() + if (anchor == "top") then + x = math.floor(pw/2) + x - 1 + elseif(anchor == "topRight") then + x = pw + x - 1 + elseif(anchor == "right") then + x = pw + x - 1 + y = math.floor(ph/2) + y - 1 + elseif(anchor == "bottomRight") then + x = pw + x - 1 + y = ph + y - 1 + elseif(anchor == "bottom") then + x = math.floor(pw/2) + x - 1 + y = ph + y - 1 + elseif(anchor == "bottomLeft") then + y = ph + y - 1 + elseif(anchor == "left") then + y = math.floor(ph/2) + y - 1 + elseif(anchor == "center") then + x = math.floor(pw/2) + x - 1 + y = math.floor(ph/2) + y - 1 + end + + local xO, yO = self.parent:getOffsetInternal() + if not(ignOffset or ignOff) then + return x+xO, y+yO + end + end + return x, y + end; + + ignoreOffset = function(self, ignore) + ignOffset = ignore + if(ignore==nil)then ignOffset = true end + return self + end; + + getBaseFrame = function(self) + if(self.parent~=nil)then + return self.parent:getBaseFrame() + end + return self + end; + + setAnchor = function(self, newAnchor) + anchor = newAnchor + self:updateDraw() + return self + end; + + getAnchor = function(self) + return anchor + end; + + onChange = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("value_changed", v) + end + end + return self + end; + + onClick = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("mouse_click", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("mouse_click", self) + activeEvents["mouse_click"] = true + end + return self + end; + + onClickUp = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("mouse_up", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("mouse_up", self) + activeEvents["mouse_up"] = true + end + return self + end; + + + onScroll = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("mouse_scroll", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("mouse_scroll", self) + activeEvents["mouse_scroll"] = true + end + return self + end; + + onDrag = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("mouse_drag", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("mouse_drag", self) + activeEvents["mouse_drag"] = true + self.parent:addEvent("mouse_click", self) + activeEvents["mouse_click"] = true + self.parent:addEvent("mouse_up", self) + activeEvents["mouse_up"] = true + end + return self + end; + + onEvent = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("other_event", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("other_event", self) + activeEvents["other_event"] = true + end + return self + end; + + onKey = function(self, ...) + if(isEnabled)then + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("key", v) + self:registerEvent("char", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("key", self) + self.parent:addEvent("char", self) + activeEvents["key"] = true + activeEvents["char"] = true + end + end + return self + end; + + onResize = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("basalt_resize", v) + end + end + return self + end; + + onReposition = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("basalt_reposition", v) + end + end + return self + end; + + onKeyUp = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("key_up", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("key_up", self) + activeEvents["key_up"] = true + end + return self + end; + + isFocused = function(self) + if (self.parent ~= nil) then + return self.parent:getFocusedObject() == self + end + return false + end; + + onGetFocus = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("get_focus", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("mouse_click", self) + activeEvents["mouse_click"] = true + end + return self + end; + + onLoseFocus = function(self, ...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + self:registerEvent("lose_focus", v) + end + end + if(self.parent~=nil)then + self.parent:addEvent("mouse_click", self) + activeEvents["mouse_click"] = true + end + return self + end; + + registerEvent = function(self, event, func) + return eventSystem:registerEvent(event, func) + end; + + removeEvent = function(self, event, index) + return eventSystem:removeEvent(event, index) + end; + + sendEvent = function(self, event, ...) + return eventSystem:sendEvent(event, self, ...) + end; + + isCoordsInObject = function(self, x, y) + if(isVisible)and(isEnabled)then + local objX, objY = self:getAbsolutePosition(self:getAnchorPosition()) + local w, h = self:getSize() + if (objX <= x) and (objX + w > x) and (objY <= y) and (objY + h > y) then + return true + end + end + return false + end, + + mouseHandler = function(self, button, x, y, isMon) + if(self:isCoordsInObject(x, y))then + local val = eventSystem:sendEvent("mouse_click", self, "mouse_click", button, x, y, isMon) + if(val==false)then return false end + if(self.parent~=nil)then + self.parent:setFocusedObject(self) + end + isDragging = true + dragStartX, dragStartY = x, y + return true + end + return false + end, + + mouseUpHandler = function(self, button, x, y) + isDragging = false + if(self:isCoordsInObject(x, y))then + local val = eventSystem:sendEvent("mouse_up", self, "mouse_up", button, x, y) + if(val==false)then return false end + return true + end + return false + end, + + dragHandler = function(self, button, x, y) + if(isDragging)then + local xO, yO, parentX, parentY = 0, 0, 1, 1 + if (self.parent ~= nil) then + xO, yO = self.parent:getOffsetInternal() + xO = xO < 0 and math.abs(xO) or -xO + yO = yO < 0 and math.abs(yO) or -yO + parentX, parentY = self.parent:getAbsolutePosition(self.parent:getAnchorPosition()) + end + local dX, dY = x + dragXOffset - (parentX - 1) + xO, y + dragYOffset - (parentY - 1) + yO + local val = eventSystem:sendEvent("mouse_drag", self, button, dX, dY, dragStartX-x, dragStartY-y, x, y) + local objX, objY = self:getAbsolutePosition(self:getAnchorPosition()) + dragStartX, dragStartY = x, y + if(val~=nil)then return val end + if(self.parent~=nil)then + self.parent:setFocusedObject(self) + end + return true + end + + if(self:isCoordsInObject(x, y))then + local objX, objY = self:getAbsolutePosition(self:getAnchorPosition()) + dragStartX, dragStartY = x, y + dragXOffset, dragYOffset = objX - x, objY - y + end + return false + end, + + scrollHandler = function(self, dir, x, y) + if(self:isCoordsInObject(x, y))then + local val = eventSystem:sendEvent("mouse_scroll", self, "mouse_scroll", dir, x, y) + if(val==false)then return false end + if(self.parent~=nil)then + self.parent:setFocusedObject(self) + end + return true + end + return false + end, + + keyHandler = function(self, key, isHolding) + if(isEnabled)and(isVisible)then + if (self:isFocused()) then + local val = eventSystem:sendEvent("key", self, "key", key, isHolding) + if(val==false)then return false end + return true + end + end + return false + end; + + keyUpHandler = function(self, key) + if(isEnabled)and(isVisible)then + if (self:isFocused()) then + local val = eventSystem:sendEvent("key_up", self, "key_up", key) + if(val==false)then return false end + return true + end + end + return false + end; + + charHandler = function(self, char) + if(isEnabled)and(isVisible)then + if (self:isFocused()) then + local val = eventSystem:sendEvent("char", self, "char", char) + if(val==false)then return false end + return true + end + end + return false + end, + + valueChangedHandler = function(self) + eventSystem:sendEvent("value_changed", self, value) + end; + + eventHandler = function(self, event, p1, p2, p3, p4) + local val = eventSystem:sendEvent("other_event", self, event, p1, p2, p3, p4) + if(val~=nil)then return val end + return true + end; + + getFocusHandler = function(self) + local val = eventSystem:sendEvent("get_focus", self) + if(val~=nil)then return val end + return true + end; + + loseFocusHandler = function(self) + isDragging = false + local val = eventSystem:sendEvent("lose_focus", self) + if(val~=nil)then return val end + return true + end; + + init = function(self) + if(self.parent~=nil)then + for k,v in pairs(activeEvents)do + if(v)then + self.parent:addEvent(k, self) + end + end + end + if not(initialized)then + initialized = true + return true + end + end + + } + + object.__index = object + return object +end \ No newline at end of file diff --git a/src/Basalt/init.lua b/src/Basalt/init.lua new file mode 100644 index 0000000..bb8e6b0 --- /dev/null +++ b/src/Basalt/init.lua @@ -0,0 +1,10 @@ +local curDir = fs.getDir(table.pack(...)[2]) or "" + +local defaultPath = package.path +local format = "%s;/%s/?.lua;/%s/?/init.lua" +package.path = string.format(format, package.path, curDir,curDir)..string.format(format, package.path, curDir.."/libraries",curDir.."/libraries")..string.format(format, package.path, curDir.."/objects",curDir.."/objects") + +local Basalt = require("main") +package.path = defaultPath + +return Basalt \ No newline at end of file diff --git a/src/Basalt/libraries/basaltDraw.lua b/src/Basalt/libraries/basaltDraw.lua new file mode 100644 index 0000000..62ce41e --- /dev/null +++ b/src/Basalt/libraries/basaltDraw.lua @@ -0,0 +1,209 @@ +local tHex = require("tHex") +local sub,rep = string.sub,string.rep + +return function(drawTerm) + local terminal = drawTerm or term.current() + local mirrorTerm + local width, height = terminal.getSize() + local cacheT = {} + local cacheBG = {} + local cacheFG = {} + + local _cacheT = {} + local _cacheBG = {} + local _cacheFG = {} + + local emptySpaceLine + local emptyColorLines = {} + + local function createEmptyLines() + emptySpaceLine = rep(" ", width) + for n = 0, 15 do + local nColor = 2 ^ n + local sHex = tHex[nColor] + emptyColorLines[nColor] = rep(sHex, width) + end + end + ---- + createEmptyLines() + + local function recreateWindowArray() + createEmptyLines() + local emptyText = emptySpaceLine + local emptyFG = emptyColorLines[colors.white] + local emptyBG = emptyColorLines[colors.black] + for currentY = 1, height do + cacheT[currentY] = sub(cacheT[currentY] == nil and emptyText or cacheT[currentY] .. emptyText:sub(1, width - cacheT[currentY]:len()), 1, width) + cacheFG[currentY] = sub(cacheFG[currentY] == nil and emptyFG or cacheFG[currentY] .. emptyFG:sub(1, width - cacheFG[currentY]:len()), 1, width) + cacheBG[currentY] = sub(cacheBG[currentY] == nil and emptyBG or cacheBG[currentY] .. emptyBG:sub(1, width - cacheBG[currentY]:len()), 1, width) + end + end + recreateWindowArray() + + local function setText(x, y, text) + if (y >= 1) and (y <= height) then + if (x + text:len() > 0) and (x <= width) then + local oldCache = cacheT[y] + local newCache + local nEnd = x + #text - 1 + + if (x < 1) then + local startN = 1 - x + 1 + local endN = width - x + 1 + text = sub(text, startN, endN) + elseif (nEnd > width) then + local endN = width - x + 1 + text = sub(text, 1, endN) + end + + if (x > 1) then + local endN = x - 1 + newCache = sub(oldCache, 1, endN) .. text + else + newCache = text + end + if nEnd < width then + newCache = newCache .. sub(oldCache, nEnd + 1, width) + end + cacheT[y] = newCache + end + end + end + + local function setBG(x, y, colorStr) + if (y >= 1) and (y <= height) then + if (x + colorStr:len() > 0) and (x <= width) then + local oldCache = cacheBG[y] + local newCache + local nEnd = x + #colorStr - 1 + + if (x < 1) then + colorStr = sub(colorStr, 1 - x + 1, width - x + 1) + elseif (nEnd > width) then + colorStr = sub(colorStr, 1, width - x + 1) + end + + if (x > 1) then + newCache = sub(oldCache, 1, x - 1) .. colorStr + else + newCache = colorStr + end + if nEnd < width then + newCache = newCache .. sub(oldCache, nEnd + 1, width) + end + cacheBG[y] = newCache + end + end + end + + local function setFG(x, y, colorStr) + if (y >= 1) and (y <= height) then + if (x + colorStr:len() > 0) and (x <= width) then + local oldCache = cacheFG[y] + local newCache + local nEnd = x + #colorStr - 1 + + if (x < 1) then + local startN = 1 - x + 1 + local endN = width - x + 1 + colorStr = sub(colorStr, startN, endN) + elseif (nEnd > width) then + local endN = width - x + 1 + colorStr = sub(colorStr, 1, endN) + end + + if (x > 1) then + local endN = x - 1 + newCache = sub(oldCache, 1, endN) .. colorStr + else + newCache = colorStr + end + if nEnd < width then + newCache = newCache .. sub(oldCache, nEnd + 1, width) + end + cacheFG[y] = newCache + end + end + end + + local drawHelper = { + setSize = function(w, h) + width, height = w, h + recreateWindowArray() + end, + + setMirror = function(mirror) + mirrorTerm = mirror + end, + setBG = function(x, y, colorStr) + setBG(x, y, colorStr) + end; + + setText = function(x, y, text) + setText(x, y, text) + end; + + setFG = function(x, y, colorStr) + setFG(x, y, colorStr) + end; + + drawBackgroundBox = function(x, y, width, height, bgCol) + for n = 1, height do + setBG(x, y + (n - 1), rep(tHex[bgCol], width)) + end + end; + drawForegroundBox = function(x, y, width, height, fgCol) + for n = 1, height do + setFG(x, y + (n - 1) ,rep(tHex[fgCol], width)) + end + end; + drawTextBox = function(x, y, width, height, symbol) + for n = 1, height do + setText(x, y + (n - 1), rep(symbol, width)) + end + end; + writeText = function(x, y, text, bgCol, fgCol) + if(text~=nil)then + setText(x, y, text) + if(bgCol~=nil)and(bgCol~=false)then + setBG(x, y, rep(tHex[bgCol], text:len())) + end + if(fgCol~=nil)and(fgCol~=false)then + setFG(x, y, rep(tHex[fgCol], text:len())) + end + end + end; + + update = function() + local xC, yC = terminal.getCursorPos() + local isBlinking = false + if (terminal.getCursorBlink ~= nil) then + isBlinking = terminal.getCursorBlink() + end + terminal.setCursorBlink(false) + if(mirrorTerm~=nil)then terminal.setCursorBlink(false) end + for n = 1, height do + terminal.setCursorPos(1, n) + terminal.blit(cacheT[n], cacheFG[n], cacheBG[n]) + if(mirrorTerm~=nil)then + mirrorTerm.setCursorPos(1, n) + mirrorTerm.blit(cacheT[n], cacheFG[n], cacheBG[n]) + end + end + terminal.setBackgroundColor(colors.black) + terminal.setCursorBlink(isBlinking) + terminal.setCursorPos(xC, yC) + if(mirrorTerm~=nil)then + mirrorTerm.setBackgroundColor(colors.black) + mirrorTerm.setCursorBlink(isBlinking) + mirrorTerm.setCursorPos(xC, yC) + end + + end; + + setTerm = function(newTerm) + terminal = newTerm; + end; + } + return drawHelper +end \ No newline at end of file diff --git a/src/Basalt/libraries/basaltEvent.lua b/src/Basalt/libraries/basaltEvent.lua new file mode 100644 index 0000000..80bcd55 --- /dev/null +++ b/src/Basalt/libraries/basaltEvent.lua @@ -0,0 +1,35 @@ +return function() + local events = {} + local index = {} + + local event = { + registerEvent = function(self, _event, func) + if (events[_event] == nil) then + events[_event] = {} + index[_event] = 1 + end + events[_event][index[_event]] = func + index[_event] = index[_event] + 1 + return index[_event] - 1 + end; + + removeEvent = function(self, _event, index) + events[_event][index[_event]] = nil + end; + + sendEvent = function(self, _event, ...) + local returnValue + if (events[_event] ~= nil) then + for _, value in pairs(events[_event]) do + local val = value(...) + if(val==false)then + returnValue = val + end + end + end + return returnValue + end; + } + event.__index = event + return event +end \ No newline at end of file diff --git a/src/Basalt/libraries/basaltLogs.lua b/src/Basalt/libraries/basaltLogs.lua new file mode 100644 index 0000000..4edcd5a --- /dev/null +++ b/src/Basalt/libraries/basaltLogs.lua @@ -0,0 +1,20 @@ +local logDir = "" +local logFileName = "basaltLog.txt" + +local defaultLogType = "Debug" + +fs.delete(logDir~="" and logDir.."/"..logFileName or logFileName) + +local mt = { + __call = function(_,text, typ) + if(text==nil)then return end + local dirStr = logDir~="" and logDir.."/"..logFileName or logFileName + local handle = fs.open(dirStr, fs.exists(dirStr) and "a" or "w") + handle.writeLine("[Basalt]["..(typ and typ or defaultLogType).."]: "..tostring(text)) + handle.close() + end, +} + +return setmetatable({}, mt) + +--Work in progress \ No newline at end of file diff --git a/src/Basalt/libraries/geometricPoints.lua b/src/Basalt/libraries/geometricPoints.lua new file mode 100644 index 0000000..01800a2 --- /dev/null +++ b/src/Basalt/libraries/geometricPoints.lua @@ -0,0 +1,197 @@ +local function line(x1,y1,x2,y2) + local points = {} + if x1 == x2 and y1 == y2 then return {x=x1,y=x2} end + local minX = math.min(x1, x2) + local maxX, minY, maxY + if minX == x1 then minY,maxX,maxY = y1,x2,y2 + else minY,maxX,maxY = y2,x1,y1 end + local xDiff,yDiff = maxX - minX,maxY - minY + if xDiff > math.abs(yDiff) then + local y = minY + local dy = yDiff / xDiff + for x = minX, maxX do + table.insert(points,{x=x,y=math.floor(y + 0.5)}) + y = y + dy + end + else + local x,dx = minX,xDiff / yDiff + if maxY >= minY then + for y = minY, maxY do + table.insert(points,{x=math.floor(x + 0.5),y=y}) + x = x + dx + end + else + for y = minY, maxY, -1 do + table.insert(points,{x=math.floor(x + 0.5),y=y}) + x = x - dx + end + end + end + return points +end + +local function filledCircle(xC, yC, r) + local points = {} + for x=-r, r+1 do + local dy = math.floor(math.sqrt(r*r - x*x)) + for y=-dy, dy+1 do + table.insert(points, {x=xC+x, y=yC+y}) + end + end + return points +end + +local function ellipse(xC, yC, r1, r2, filled) + local rx,ry = math.ceil(math.floor(r1-0.5)/2),math.ceil(math.floor(r2-0.5)/2) + local x,y=0,ry + local d1 = ((ry * ry) - (rx * rx * ry) + (0.25 * rx * rx)) + local dx = 2*ry^2*x + local dy = 2*rx^2*y + local points = {} + while dx < dy do + table.insert(points,{x=x+xC,y=y+yC}) + table.insert(points,{x=-x+xC,y=y+yC}) + table.insert(points,{x=x+xC,y=-y+yC}) + table.insert(points,{x=-x+xC,y=-y+yC}) + if filled then + for y=-y+yC+1,y+yC-1 do + table.insert(points,{x=x+xC,y=y}) + table.insert(points,{x=-x+xC,y=y}) + end + end + if d1 < 0 then + x = x + 1 + dx = dx + 2*ry^2 + d1 = d1 + dx + ry^2 + else + x,y = x+1,y-1 + dx = dx + 2*ry^2 + dy = dy - 2*rx^2 + d1 = d1 + dx - dy + ry^2 + end + end + local d2 = (((ry * ry) * ((x + 0.5) * (x + 0.5))) + ((rx * rx) * ((y - 1) * (y - 1))) - (rx * rx * ry * ry)) + while y >= 0 do + table.insert(points,{x=x+xC,y=y+yC}) + table.insert(points,{x=-x+xC,y=y+yC}) + table.insert(points,{x=x+xC,y=-y+yC}) + table.insert(points,{x=-x+xC,y=-y+yC}) + if filled then + for y=-y+yC,y+yC do + table.insert(points,{x=x+xC,y=y}) + table.insert(points,{x=-x+xC,y=y}) + end + end + if d2 > 0 then + y = y - 1 + dy = dy - 2*rx^2 + d2 = d2 + rx^2 - dy + else + y = y - 1 + x = x + 1 + dy = dy - 2*rx^2 + dx = dx + 2*ry^2 + d2 = d2 + dx - dy + rx^2 + end + end + return points +end + +local function circle(xC, yC, r, filled) + return ellipse(xC, yC, r, r, filled) +end + +return { +circle = function(x, y, radius, filled) + return circle(x, y, radius, filled) +end, + +rectangle = function(x1, y1, x2, y2, filled) + local points = {} + if(filled)then + for y=y1,y2 do + for x=x1,x2 do + table.insert(points, {x=x,y=y}) + end + end + else + for y=y1,y2 do + for x=x1,x2 do + if(x==x1)or(x==x2)or(y==y1)or(y==y2)then + table.insert(points, {x=x,y=y}) + end + end + end + end + return points +end, + +triangle = function(x1, y1, x2, y2, x3, y3, filled) + local function drawFlatTopTriangle(points,x1,y1,x2,y2,x3,y3) + local m1 = (x3 - x1) / (y3 - y1) + local m2 = (x3 - x2) / (y3 - y2) + local yStart = math.ceil(y1 - 0.5) + local yEnd = math.ceil(y3 - 0.5)-1 + for y = yStart, yEnd do + local px1 = m1 * (y + 0.5 - y1) + x1 + local px2 = m2 * (y + 0.5 - y2) + x2 + local xStart = math.ceil(px1 - 0.5) + local xEnd = math.ceil(px2 - 0.5) + for x=xStart,xEnd do + table.insert(points,{x=x,y=y}) + end + end + end + + local function drawFlatBottomTriangle(points,x1,y1,x2,y2,x3,y3) + local m1 = (x2 - x1) / (y2 - y1) + local m2 = (x3 - x1) / (y3 - y1) + local yStart = math.ceil(y1-0.5) + local yEnd = math.ceil(y3-0.5)-1 + for y = yStart, yEnd do + local px1 = m1 * (y + 0.5 - y1) + x1 + local px2 = m2 * (y + 0.5 - y1) + x1 + local xStart = math.ceil(px1 - 0.5) + local xEnd = math.ceil(px2 - 0.5) + for x=xStart,xEnd do + table.insert(points,{x=x,y=y}) + end + end + end + local points = {} + if(filled)then + if y2 < y1 then x1,y1,x2,y2 = x2,y2,x1,y1 end + if y3 < y2 then x2,y2,x3,y3 = x3,y3,x2,y2 end + if y2 < y2 then x1,y1,x2,y2 = x2,y2,x1,y1 end + if y1 == y2 then + if x2 < x1 then x1,y1,x2,y2 = x2,y2,x1,y1 end + drawFlatTopTriangle(points,x1,y1,x2,y2,x3,y3) + elseif y2 == y3 then + if x3 < x2 then x3,y3,x2,y2 = x2,y2,x3,y3 end + drawFlatBottomTriangle(points,x1,y1,x2,y2,x3,y3) + else + local alphaSplit = (y2-y1)/(y3-y1) + local x = x1 + ((x3 - x1) * alphaSplit) + local y = y1 + ((y3 - y1) * alphaSplit) + if x2 < x then + drawFlatBottomTriangle(points,x1,y1,x2,y2,x, y) + drawFlatTopTriangle(points,x2,y2,x,y,x3,y3) + else + drawFlatBottomTriangle(points,x1,y1,x,y,x1,y1) + drawFlatTopTriangle(points,x,y,x2,y2,x3,y3) + end + end + else + points = line(x1,y1,x2,y2) + for k,v in pairs(line(x2,y2,x3,y3))do table.insert(points, v) end + for k,v in pairs(line(x3,y3,x1,y1))do table.insert(points, v) end + end + return points +end, + +line = line, + +ellipse = function(xCenter, yCenter, radius1, radius2, filled) + return ellipse(xCenter, yCenter, radius1, radius2, filled) +end +} \ No newline at end of file diff --git a/src/Basalt/libraries/layout.lua b/src/Basalt/libraries/layout.lua new file mode 100644 index 0000000..ee29437 --- /dev/null +++ b/src/Basalt/libraries/layout.lua @@ -0,0 +1,147 @@ +local function newNode(name) + local node = {} + node.___value = nil + node.___name = name + node.___children = {} + node.___props = {} + + function node:value() return self.___value end + function node:setValue(val) self.___value = val end + function node:name() return self.___name end + function node:setName(name) self.___name = name end + function node:children() return self.___children end + function node:numChildren() return #self.___children end + function node:addChild(child) + if self[child:name()] ~= nil then + if type(self[child:name()].name) == "function" then + local tempTable = {} + table.insert(tempTable, self[child:name()]) + self[child:name()] = tempTable + end + table.insert(self[child:name()], child) + else + self[child:name()] = child + end + table.insert(self.___children, child) + end + + function node:properties() return self.___props end + function node:numProperties() return #self.___props end + function node:addProperty(name, value) + local lName = "@" .. name + if self[lName] ~= nil then + if type(self[lName]) == "string" then + local tempTable = {} + table.insert(tempTable, self[lName]) + self[lName] = tempTable + end + table.insert(self[lName], value) + else + self[lName] = value + end + table.insert(self.___props, { name = name, value = self[name] }) + end + + return node +end + +local XmlParser = {}; + +function XmlParser:ToXmlString(value) + value = string.gsub(value, "&", "&"); -- '&' -> "&" + value = string.gsub(value, "<", "<"); -- '<' -> "<" + value = string.gsub(value, ">", ">"); -- '>' -> ">" + value = string.gsub(value, "\"", """); -- '"' -> """ + value = string.gsub(value, "([^%w%&%;%p%\t% ])", + function(c) + return string.format("&#x%X;", string.byte(c)) + end); + return value; +end + +function XmlParser:FromXmlString(value) + value = string.gsub(value, "&#x([%x]+)%;", + function(h) + return string.char(tonumber(h, 16)) + end); + value = string.gsub(value, "&#([0-9]+)%;", + function(h) + return string.char(tonumber(h, 10)) + end); + value = string.gsub(value, """, "\""); + value = string.gsub(value, "'", "'"); + value = string.gsub(value, ">", ">"); + value = string.gsub(value, "<", "<"); + value = string.gsub(value, "&", "&"); + return value; +end + +function XmlParser:ParseArgs(node, s) + string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a) + node:addProperty(w, self:FromXmlString(a)) + end) +end + +function XmlParser:ParseXmlText(xmlText) + local stack = {} + local top = newNode() + table.insert(stack, top) + local ni, c, label, xarg, empty + local i, j = 1, 1 + while true do + ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w_:]+)(.-)(%/?)>", i) + if not ni then break end + local text = string.sub(xmlText, i, ni - 1); + if not string.find(text, "^%s*$") then + local lVal = (top:value() or "") .. self:FromXmlString(text) + stack[#stack]:setValue(lVal) + end + if empty == "/" then -- empty element tag + local lNode = newNode(label) + self:ParseArgs(lNode, xarg) + top:addChild(lNode) + elseif c == "" then -- start tag + local lNode = newNode(label) + self:ParseArgs(lNode, xarg) + table.insert(stack, lNode) + top = lNode + else -- end tag + local toclose = table.remove(stack) -- remove top + + top = stack[#stack] + if #stack < 1 then + error("XmlParser: nothing to close with " .. label) + end + if toclose:name() ~= label then + error("XmlParser: trying to close " .. toclose.name .. " with " .. label) + end + top:addChild(toclose) + end + i = j + 1 + end + local text = string.sub(xmlText, i); + if #stack > 1 then + error("XmlParser: unclosed " .. stack[#stack]:name()) + end + return top +end + +function XmlParser:loadFile(xmlFilename, base) + if not base then + base = system.ResourceDirectory + end + + local path = system.pathForFile(xmlFilename, base) + local hFile, err = io.open(path, "r"); + + if hFile and not err then + local xmlText = hFile:read("*a"); -- read file content + io.close(hFile); + return self:ParseXmlText(xmlText), nil; + else + print(err) + return nil + end +end + +return XmlParser \ No newline at end of file diff --git a/src/Basalt/libraries/miniBDF.lua b/src/Basalt/libraries/miniBDF.lua new file mode 100644 index 0000000..6e5ddb0 --- /dev/null +++ b/src/Basalt/libraries/miniBDF.lua @@ -0,0 +1,304 @@ +---Converts a given table to a blit char. +---@param str string Something like: "100110" for \153 +---@return string sChar Matching counter part +---@return boolean bReversed Swaped text & background color? +local function toBlit(str) + if type(str) ~= "string" or #str < 6 then + str = "000000" + elseif str:sub(1,1) == '#' then + return str:sub(2,2), false + end + local t = {} + for i=1,6 do + t[#t+1] = tonumber(str:sub(i,i)) or 0 + end + + if t[6]==1 then + for i=1,5 do + t[i] = 1-t[i] + end end + local n = 128 + for i=0,4 do + n = n+t[i+1]*2^i + end + + return string.char(n), (t[6] == 1) +end + +---Converts a given char to a table, +---representing pixels in the current font. +---@param font table The font, generated with bdf.loadBDF(...) +---@param char string The char (e.g. "f") +---@return table char The char in the current font, as table +local function drawChar(font, char) + local char = font.bitmap[char:byte()] + -- Return empty + if not char then + if not font.bitmap[63] then + local width,height = font.width,font.height + local tmp = (' '):rep(width) + local char = {} + for y=1,height do + char[#char+1] = tmp + end + return char + else + char = font.bitmap[63] + end + end + + -- Get bitmap + local result = {} + local j=0 + for i=#char,1,-1 do + result[font.width-j-char.bound.offy-font.offy] = char[i] + j=j+1 + end + -- Fill gaps + for i=1,font.height do + if not result[i] then + result[i] = ('0'):rep(font.width) + else + if char.bound.offx > 0 then + result[i] = ('0'):rep(char.bound.offx)..result[i] + end + if #result[i] < font.width then + result[i] =result[i]..('0'):rep(font.width-#result[i]) + end + end + end + + return result +end + +---Same as drawChar but with strings. +---@param font The font, generated with bdf.loadBDF(...) +---@param str string The string (e.g. "Hello") +---@return table str The string in the current font, as table +local function drawString(font, str) + local string = {} + for x=1,#str do + local char = font:drawChar(str:sub(x)) + for y=1,#char do + if not string[y] then string[y] = "" end + string[y] = string[y]..char[y] + end + end + return string +end + +local function needsSpace(len,expected) + local add = 0 + while math.floor((len+add)/expected) ~= ((len+add)/expected) do + add = add+1 + end + return add +end + +local blit = {[1]='0',[2]='1',[4]='2',[8]='3',[16]='4',[32]='5',[64]='6',[128]='7',[256]='8',[512]='9',[1024]='a',[2048]='b',[4096]='c',[8192]='d',[16384]='e',[32768]='f' } +---Converts a bitmap table into a one represented by box chars. +---@param str table The bitmap (could be via font:drawChar("...")) +---@param bg number The Background color +---@param fg number The Background color +---@return table blits The result +local function compress(str, bg,fg) + local addX = needsSpace(#str[1],2) + local addY = needsSpace(#str,3) + if addX > 0 then + for i=1,#str do + str[i] = str[i]..('0'):rep(addX) + end + end + if addY > 0 then + for i=1,addY do + str[#str+1] = ('0'):rep(#str[1]) + end + end + + local blits = { + ch={}, + bg={}, + fg={} + } + for y=1,#str/3 do + blits.ch[y] = "" + blits.bg[y] = "" + blits.fg[y] = "" + end + + for x=1,#str[1]/2 do + for y=1,#str/3 do + local char,invert = toBlit( + str[y*3-2]:sub(x*2-1,x*2).. + str[y*3-1]:sub(x*2-1,x*2).. + str[y*3-0]:sub(x*2-1,x*2) + ) + blits.ch[y] = blits.ch[y]..char + local c1,c2 = blit[bg],blit[fg] + if invert then + c2,c1 = blit[bg],blit[fg] + end + blits.bg[y] = blits.bg[y]..c1 + blits.fg[y] = blits.fg[y]..c2 + end + end + return blits +end + + +local data = { + FONT = function(font,line) + font.name = line + end, + FONTBOUNDINGBOX = function(font,line) + local type={"width","height","offx","offy"} + local i=1 + for word in string.gmatch(line, '([^ ]+)') do + if i > #type then return end + font[type[i]] = tonumber(word) or 0 + i=i+1 + end + end, + CHARSET_REGISTRY = function(font,line) + if line:find("ISO") and line:find("8859") then + font.validCharReg = font.validCharReg+1 + end + end, + CHARSET_ENCODING = function(font,line) + if line:find("\"1\"") then + font.validCharReg = font.validCharReg+1 + end + end, + CHARS = function(font,line) + local num = tonumber(line) or 0 + if num == 256 then + font.validCharReg = font.validCharReg+1 + end + end, + STARTCHAR = function(font,line) + font._CUR_CHAR = {name=line} + end, + ENCODING = function(font,line) + if not font._CUR_CHAR then return end + font._CUR_CHAR.byte = tonumber(line) or 0 + end, + BBX = function(font, line) + local i=1 + local b = {"width","height","offx","offy"} + font._CUR_CHAR.bound = {} + for word in string.gmatch(line, '([^ ]+)') do + if i > #b then return end + font._CUR_CHAR.bound[b[i]] = tonumber(word) or 0 + i=i+1 + end + end, + BITMAP = function(font,_) + font.isBitmap = true + end +} + +---Parses a .BDF font file into a in lua readable table. +---@param path string The path to the .BDF file +---@return table font The result +local function loadBDF(path) + if not (fs.exists(path) and not fs.isDir(path)) then + return printError("Could not load: \'"..path.."\'!") + end + + local f = fs.open(path, 'r') + local font = {bitmap={},validCharReg=0,isBitmap=false} + + local line = f.readLine() + repeat + if font.isBitmap then + if line:find("ENDCHAR") then + -- Finishing + font.isBitmap = false + local byte = font._CUR_CHAR.byte + font._CUR_CHAR.byte = nil + font.bitmap[byte] = {} + for k,v in pairs(font._CUR_CHAR) do + font.bitmap[byte][k] = v + end + font._CUR_CHAR = nil + else + local num = tonumber("0x"..line) + if not num then return end + + local function ffs(value) + if value == 0 then return 0 end + local pos = 0; + while bit32.band(value, 1) == 0 do + value = bit32.rshift(value, 1); + pos = pos + 1 + end + return pos + end + + local l = "" + local w = math.ceil(math.floor(math.log(num) / math.log(2)) / 8) * 8 + for i = ffs(num) or 0, w do + l = l..bit32.band(bit32.rshift(num, i-1), 1) + end + l = l:reverse() + local w = font._CUR_CHAR.bound.width + if #l > w then + l = l:sub(1,w) + elseif #l < w then + l = l..('0'):rep(w-#l) + end + + font._CUR_CHAR[#font._CUR_CHAR+1] = l + end + else + local label = line:match('([^ ]+)') + if data[label] then + data[label](font, line:sub(#label+2)) + end + end + line = f.readLine() + until line == nil + + f.close() + font.isBitmap = nil + font.validCharReg = (font.validCharReg == 3) + font.drawChar = drawChar + font.drawString = drawString + return font +end + +---Checks for errors a font may have. Should return 0. +---The bigger the results number, the more errors it has. +---@param font table The font that will be checked. +---@return number errors The amount of errors the font has. +---@return table messages The error messages +local function checkFont(font) + local msg = {} + local errorCounter = 0 + if type(font.name) ~= "string" then + errorCounter = errorCounter+1 + table.insert(msg,"Font has no name.") + elseif type(font.width) ~= "number" then + errorCounter = errorCounter+1 + table.insert(msg,"Font has no global width.") + elseif type(font.height) ~= "number" then + errorCounter = errorCounter+1 + table.insert(msg,"Font has no global height.") + elseif not font.validCharReg then + errorCounter = errorCounter+1 + table.insert(msg,"Font uses not supported format.") + elseif type(font.bitmap) ~= "table" then + errorCounter = errorCounter+1 + table.insert(msg,"Font has no bitmaps (nothing to render)") + elseif #font.bitmap ~= 255 then + errorCounter = errorCounter+1 + table.insert(msg,"Font may have missing chars.") + end + return errorCounter,msg +end + +return { + loadBDF = loadBDF, + checkFont = checkFont, + compress = compress +} \ No newline at end of file diff --git a/src/Basalt/libraries/process.lua b/src/Basalt/libraries/process.lua new file mode 100644 index 0000000..8a7248e --- /dev/null +++ b/src/Basalt/libraries/process.lua @@ -0,0 +1,63 @@ +local processes = {} +local process = {} +local processId = 0 + +function process:new(path, window, ...) + local args = {...} + local newP = setmetatable({ path = path }, { __index = self }) + newP.window = window + newP.processId = processId + if(type(path)=="string")then + newP.coroutine = coroutine.create(function() + shell.execute(path, table.unpack(args)) + end) + elseif(type(path)=="function")then + newP.coroutine = coroutine.create(function() + path(table.unpack(args)) + end) + else + return + end + processes[processId] = newP + processId = processId + 1 + return newP +end + +function process:resume(event, ...) + term.redirect(self.window) + if(self.filter~=nil)then + if(event~=self.filter)then return end + self.filter=nil + end + local ok, result = coroutine.resume(self.coroutine, event, ...) + if ok then + self.filter = result + else + error(result) + end +end + +function process:isDead() + if (self.coroutine ~= nil) then + if (coroutine.status(self.coroutine) == "dead") then + table.remove(processes, self.processId) + return true + end + else + return true + end + return false +end + +function process:getStatus() + if (self.coroutine ~= nil) then + return coroutine.status(self.coroutine) + end + return nil +end + +function process:start() + coroutine.resume(self.coroutine) +end + +return process \ No newline at end of file diff --git a/src/Basalt/libraries/tHex.lua b/src/Basalt/libraries/tHex.lua new file mode 100644 index 0000000..bd070ca --- /dev/null +++ b/src/Basalt/libraries/tHex.lua @@ -0,0 +1,18 @@ +return { -- copy paste is a very important feature + [colors.white] = "0", + [colors.orange] = "1", + [colors.magenta] = "2", + [colors.lightBlue] = "3", + [colors.yellow] = "4", + [colors.lime] = "5", + [colors.pink] = "6", + [colors.gray] = "7", + [colors.lightGray] = "8", + [colors.cyan] = "9", + [colors.purple] = "a", + [colors.blue] = "b", + [colors.brown] = "c", + [colors.green] = "d", + [colors.red] = "e", + [colors.black] = "f", +} \ No newline at end of file diff --git a/src/Basalt/libraries/utils.lua b/src/Basalt/libraries/utils.lua new file mode 100644 index 0000000..f9844ce --- /dev/null +++ b/src/Basalt/libraries/utils.lua @@ -0,0 +1,120 @@ +local splitString = function(str, sep) + if sep == nil then + sep = "%s" + end + local t={} + for v in string.gmatch(str, "([^"..sep.."]+)") do + table.insert(t, v) + end + return t +end + +return { +getTextHorizontalAlign = function(text, width, textAlign, replaceChar) + text = string.sub(text, 1, width) + local offset = width - string.len(text) + if (textAlign == "right") then + text = string.rep(replaceChar or " ", offset) .. text + elseif (textAlign == "center") then + text = string.rep(replaceChar or " ", math.floor(offset / 2)) .. text .. string.rep(replaceChar or " ", math.floor(offset / 2)) + text = text .. (string.len(text) < width and (replaceChar or " ") or "") + else + text = text .. string.rep(replaceChar or " ", offset) + end + return text +end, + +getTextVerticalAlign = function(h, textAlign) + local offset = 0 + if (textAlign == "center") then + offset = math.ceil(h / 2) + if (offset < 1) then + offset = 1 + end + end + if (textAlign == "bottom") then + offset = h + end + if(offset<1)then offset=1 end + return offset +end, + +rpairs = function(t) + return function(t, i) + i = i - 1 + if i ~= 0 then + return i, t[i] + end + end, t, #t + 1 +end, + +tableCount = function(t) + local n = 0 + if(t~=nil)then + for k,v in pairs(t)do + n = n + 1 + end + end + return n +end, + +splitString = splitString, + +createText = function(str, width) + local uniqueLines = splitString(str, "\n") + local lines = {} + for k,v in pairs(uniqueLines)do + local line = "" + local words = splitString(v, " ") + for a,b in pairs(words)do + if(#line+#b <= width)then + line = line=="" and b or line.." "..b + if(a==#words)then table.insert(lines, line) end + else + table.insert(lines, line) + line = b:sub(1,width) + if(a==#words)then table.insert(lines, line) end + end + end + end + return lines +end, + +getValueFromXML = function(name, tab) + local var + if(type(tab)~="table")then return end + if(tab[name]~=nil)then + if(type(tab[name])=="table")then + if(tab[name].value~=nil)then + var = tab[name]:value() + end + end + end + if(var==nil)then var = tab["@"..name] end + + if(var=="true")then + var = true + elseif(var=="false")then + var = false + elseif(tonumber(var)~=nil)then + var = tonumber(var) + end + return var +end, + +numberFromString = function(str) + return load("return " .. str)() +end, + +uuid = function() + local random = math.random + local function uuid() + local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) + return string.format('%x', v) + end) + end + return uuid() +end, +} \ No newline at end of file diff --git a/src/Basalt/loadObjects.lua b/src/Basalt/loadObjects.lua new file mode 100644 index 0000000..bb600dd --- /dev/null +++ b/src/Basalt/loadObjects.lua @@ -0,0 +1,21 @@ +local _OBJECTS = {} +if(packaged)then + for k,v in pairs(getProject("objects"))do + _OBJECTS[k] = v() + end + return _OBJECTS +end + +local args = table.pack(...) +local dir = fs.getDir(args[2] or "Basalt") +if(dir==nil)then + error("Unable to find directory "..args[2].." please report this bug to our discord.") +end + +for _,v in pairs(fs.list(fs.combine(dir, "objects")))do + if(v~="example.lua")then + local name = v:gsub(".lua", "") + _OBJECTS[name] = require(name) + end +end +return _OBJECTS \ No newline at end of file diff --git a/src/Basalt/main.lua b/src/Basalt/main.lua new file mode 100644 index 0000000..28fc5b3 --- /dev/null +++ b/src/Basalt/main.lua @@ -0,0 +1,357 @@ +local basaltEvent = require("basaltEvent")() +local Frame = require("Frame") +local theme = require("theme") +local utils = require("utils") +local log = require("basaltLogs") +local uuid = utils.uuid +local createText = utils.createText + + +local baseTerm = term.current() +local version = "1.6.1" +local debugger = true + +local projectDirectory = fs.getDir(table.pack(...)[2] or "") + +local activeKey, frames, monFrames, variables, schedules = {}, {}, {}, {}, {} +local mainFrame, activeFrame, focusedObject, updaterActive + +local basalt = {} + +if not term.isColor or not term.isColor() then + error('Basalt requires an advanced (golden) computer to run.', 0) +end + +local function stop() + updaterActive = false + baseTerm.clear() + baseTerm.setCursorPos(1, 1) +end + +local setVariable = function(name, var) + variables[name] = var +end + +local getVariable = function(name) + return variables[name] +end + +local setTheme = function(_theme) + theme = _theme +end + +local getTheme = function(name) + return theme[name] +end + +local bInstance = { + getMainFrame = function() + return mainFrame + end, + + setVariable = setVariable, + getVariable = getVariable, + getTheme = getTheme, + + setMainFrame = function(mFrame) + mainFrame = mFrame + end, + + getActiveFrame = function() + return activeFrame + end, + + setActiveFrame = function(aFrame) + activeFrame = aFrame + end, + + getFocusedObject = function() + return focusedObject + end, + + setFocusedObject = function(focused) + focusedObject = focused + end, + + getMonitorFrame = function(name) + return monFrames[name] + end, + + setMonitorFrame = function(name, frame) + if(mainFrame == frame)then mainFrame = nil end + monFrames[name] = frame + end, + + getBaseTerm = function() + return baseTerm + end, + + stop = stop, + newFrame = Frame, + + getDirectory = function() + return projectDirectory + end +} + +local basaltError = function(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 = createText("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 = {} + for n=1,#schedules do + if(schedules[n]~=nil)then + if (coroutine.status(schedules[n]) == "suspended")then + local ok, result = coroutine.resume(schedules[n], event, p1, p2, p3, p4) + if not(ok)then + basaltError(result) + end + else + table.insert(finished, n) + end + end + end + for n=1,#finished do + table.remove(schedules, finished[n]-(n-1)) + end + end +end + +local function drawFrames() + if(updaterActive==false)then return end + if(mainFrame~=nil)then + mainFrame:draw() + mainFrame:updateTerm() + end + for _,v in pairs(monFrames)do + v:draw() + v:updateTerm() + end +end + +local function basaltUpdateEvent(event, p1, p2, p3, p4) + if(basaltEvent:sendEvent("basaltEventCycle", event, p1, p2, p3, p4)==false)then return end + if(mainFrame~=nil)then + if (event == "mouse_click") then + mainFrame:mouseHandler(p1, p2, p3, false) + activeFrame = mainFrame + elseif (event == "mouse_drag") then + mainFrame:dragHandler(p1, p2, p3, p4) + activeFrame = mainFrame + elseif (event == "mouse_up") then + mainFrame:mouseUpHandler(p1, p2, p3, p4) + activeFrame = mainFrame + elseif (event == "mouse_scroll") then + mainFrame:scrollHandler(p1, p2, p3, p4) + activeFrame = mainFrame + end + end + if(event == "monitor_touch") then + if(monFrames[p1]~=nil)then + monFrames[p1]:mouseHandler(1, p2, p3, true) + activeFrame = monFrames[p1] + end + end + + if(event == "char")then + if(activeFrame~=nil)then + activeFrame:charHandler(p1) + end + end + if(event == "key_up")then + if(activeFrame~=nil)then + activeFrame:keyUpHandler(p1) + end + activeKey[p1] = false + end + if(event == "key")then + if(activeFrame~=nil)then + activeFrame:keyHandler(p1, p2) + end + activeKey[p1] = true + end + if(event == "terminate")then + if(activeFrame~=nil)then + activeFrame:eventHandler(event) + if(updaterActive==false)then return end + end + end + if(event~="mouse_click")and(event~="mouse_up")and(event~="mouse_scroll")and(event~="mouse_drag")and(event~="key")and(event~="key_up")and(event~="char")and(event~="terminate")then + for k, v in pairs(frames) do + v:eventHandler(event, p1, p2, p3, p4) + end + end + handleSchedules(event, p1, p2, p3, p4) + drawFrames() +end + +basalt = { + logging = false, + setTheme = setTheme, + getTheme = getTheme, + drawFrames = drawFrames, + getVersion = function() + return version + end, + + setVariable = setVariable, + getVariable = getVariable, + + setBaseTerm = function(_baseTerm) + baseTerm = _baseTerm + end, + + log = function(...) + log(...) + end, + + autoUpdate = function(isActive) + updaterActive = isActive + if(isActive==nil)then updaterActive = true end + local function f() + drawFrames() + while updaterActive do + basaltUpdateEvent(os.pullEventRaw()) + end + end + local ok, err = xpcall(f, debug.traceback) + if not(ok)then + basaltError(err) + return + end + end, + + update = function(event, p1, p2, p3, p4) + if (event ~= nil) then + local ok, err = xpcall(basaltUpdateEvent, debug.traceback, event, p1, p2, p3, p4) + if not(ok)then + basaltError(err) + return + end + end + end, + + stop = stop, + stopUpdate = stop, + + isKeyDown = function(key) + if(activeKey[key]==nil)then return false end + return activeKey[key]; + end, + + getFrame = function(name) + for _, value in pairs(frames) do + if (value.name == name) then + return value + end + end + end, + + getActiveFrame = function() + return activeFrame + end, + + setActiveFrame = function(frame) + if (frame:getType() == "Frame") then + activeFrame = frame + return true + end + return false + end, + + onEvent = function(...) + for _,v in pairs(table.pack(...))do + if(type(v)=="function")then + basaltEvent:registerEvent("basaltEventCycle", v) + end + end + end, + + schedule = function(f) + assert(f~="function", "Schedule needs a function in order to work!") + return function(...) + local co = coroutine.create(f) + local ok, result = coroutine.resume(co, ...) + if(ok)then + table.insert(schedules, co) + else + basaltError(result) + end + end + end, + + createFrame = function(name) + name = name or uuid() + for _, v in pairs(frames) do + if (v.name == name) then + return nil + end + end + local newFrame = Frame(name,nil,nil,bInstance) + newFrame:init() + table.insert(frames, newFrame) + if(mainFrame==nil)and(newFrame:getName()~="basaltDebuggingFrame")then + newFrame:show() + end + return newFrame + end, + + removeFrame = function(name) + frames[name] = nil + end, + + setProjectDir = function(dir) + projectDirectory = dir + end, + + debug = function(...) + local args = { ... } + if(mainFrame==nil)then print(...) return end + if (mainFrame.name ~= "basaltDebuggingFrame") then + if (mainFrame ~= basalt.debugFrame) then + basalt.debugLabel:setParent(mainFrame) + end + end + local str = "" + for key, value in pairs(args) do + str = str .. tostring(value) .. (#args ~= key and ", " or "") + end + basalt.debugLabel:setText("[Debug] " .. str) + for k,v in pairs(createText(str, basalt.debugList:getWidth()))do + basalt.debugList:addItem(v) + end + if (basalt.debugList:getItemCount() > 50) then + basalt.debugList:removeItem(1) + end + basalt.debugList:setValue(basalt.debugList:getItem(basalt.debugList:getItemCount())) + if(basalt.debugList.getItemCount() > basalt.debugList:getHeight())then + basalt.debugList:setOffset(basalt.debugList:getItemCount() - basalt.debugList:getHeight()) + end + basalt.debugLabel:show() + end, +} + +basalt.debugFrame = basalt.createFrame("basaltDebuggingFrame"):showBar():setBackground(colors.lightGray):setBar("Debug", colors.black, colors.gray) +basalt.debugFrame:addButton("back"):setAnchor("topRight"):setSize(1, 1):setText("\22"):onClick(function() if(basalt.oldFrame~=nil)then basalt.oldFrame:show() end end):setBackground(colors.red):show() +basalt.debugList = basalt.debugFrame:addList("debugList"):setSize("parent.w - 2", "parent.h - 3"):setPosition(2, 3):setScrollable(true):show() +basalt.debugLabel = basalt.debugFrame:addLabel("debugLabel"):onClick(function() basalt.oldFrame = mainFrame basalt.debugFrame:show() end):setBackground(colors.black):setForeground(colors.white):setAnchor("bottomLeft"):ignoreOffset():setZIndex(20):show() + +return basalt diff --git a/src/Basalt/objects/Animation.lua b/src/Basalt/objects/Animation.lua new file mode 100644 index 0000000..48d9d97 --- /dev/null +++ b/src/Basalt/objects/Animation.lua @@ -0,0 +1,471 @@ +local xmlValue = require("utils").getValueFromXML +local basaltEvent = require("basaltEvent") + +local floor,sin,cos,pi = math.floor,math.sin,math.cos,math.pi + +local lerp = function(s, e, pct) + return s + (e - s) * pct +end + +local linear = function (t) + return t +end + +local flip = function (t) + return 1 - t +end + +local easeIn = function (t) + return t * t * t +end + +local easeOut = function(t) + return flip(easeIn(flip(t))) +end + +local easeInOut = function(t) + return lerp(easeIn(t), easeOut(t), t) +end + +local easeOutSine = function(t) + return sin((t * pi) / 2); +end + +local easeInSine = function(t) + return flip(cos((t * pi) / 2)) +end + +local easeInOutSine = function(t) + return -(cos(pi * x) - 1) / 2 +end + +local lerp = { + linear = linear, + lerp = lerp, + flip=flip, + easeIn=easeIn, + easeOut=easeOut, + easeInOut=easeInOut, + easeOutSine = easeOutSine, + easeInSine = easeInSine, + easeInOutSine = easeInOutSine, +} + +local activeAnimations = {} + +return function(name) + local object = {} + local objectType = "Animation" + + local timerObj + + local animations = {} + local animationTime = 0 + local animationActive = false + local index = 1 + local infinitePlay = false + + local eventSystem = basaltEvent() + + local nextWaitTimer = 0 + local lastFunc + local loop=false + local autoDestroy = false + local mode = "easeOut" + + local _OBJ + + local function call(tab) + for k,v in pairs(tab)do + v(object, animations[index].t, index) + end + end + + local function onPlay(self) + if(index==1)then self:animationStartHandler() end + if (animations[index] ~= nil) then + call(animations[index].f) + animationTime = animations[index].t + end + index = index + 1 + if(animations[index]==nil)then + if(infinitePlay)then + index = 1 + animationTime = 0 + else + self:animationDoneHandler() + return + end + end + if (animations[index].t > 0) then + timerObj = os.startTimer(animations[index].t - animationTime) + else + onPlay(self) + end + end + + local function addAnimationPart(time, f) + for n=1,#animations do + if(animations[n].t==time)then + table.insert(animations[n].f, f) + return + end + end + for n=1,#animations do + if(animations[n].t>time)then + if(animations[n-1]~=nil)then + if(animations[n-1].t=d-0.01)then + if(activeAnimations[typ][name]==self)then + activeAnimations[typ][name] = nil + end + end + end + end) + end + end; + + object = { + name = name, + getType = function(self) + return objectType + end; + + getBaseFrame = function(self) + if(self.parent~=nil)then + return self.parent:getBaseFrame() + end + return self + end; + + setMode = function(self, newMode) + mode = newMode + return self + end, + + generateXMLEventFunction = function(self, func, val) + local createF = function(str) + if(str:sub(1,1)=="#")then + local o = self:getBaseFrame():getDeepObject(str:sub(2,str:len())) + if(o~=nil)and(o.internalObjetCall~=nil)then + func(self,function()o:internalObjetCall()end) + end + else + func(self,self:getBaseFrame():getVariable(str)) + end + end + if(type(val)=="string")then + createF(val) + elseif(type(val)=="table")then + for k,v in pairs(val)do + createF(v) + end + end + return self + end, + + setValuesByXMLData = function(self, data) + loop = xmlValue("loop", data)==true and true or false + if(xmlValue("object", data)~=nil)then + local o = self:getBaseFrame():getDeepObject(xmlValue("object", data)) + if(o==nil)then + o = self:getBaseFrame():getVariable(xmlValue("object", data)) + end + if(o~=nil)then + self:setObject(o) + end + end + if(data["move"]~=nil)then + local x = xmlValue("x", data["move"]) + local y = xmlValue("y", data["move"]) + local duration = xmlValue("duration", data["move"]) + local time = xmlValue("time", data["move"]) + self:move(x, y, duration, time) + end + if(data["size"]~=nil)then + local w = xmlValue("width", data["size"]) + local h = xmlValue("height", data["size"]) + local duration = xmlValue("duration", data["size"]) + local time = xmlValue("time", data["size"]) + self:size(w, h, duration, time) + end + if(data["offset"]~=nil)then + local x = xmlValue("x", data["offset"]) + local y = xmlValue("y", data["offset"]) + local duration = xmlValue("duration", data["offset"]) + local time = xmlValue("time", data["offset"]) + self:offset(x, y, duration, time) + end + if(data["textColor"]~=nil)then + local duration = xmlValue("duration", data["textColor"]) + local timer = xmlValue("time", data["textColor"]) + local t = {} + local tab = data["textColor"]["color"] + if(tab~=nil)then + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + table.insert(t, colors[v:value()]) + end + end + if(duration~=nil)and(#t>0)then + self:changeTextColor(duration, timer or 0, table.unpack(t)) + end + end + if(data["background"]~=nil)then + local duration = xmlValue("duration", data["background"]) + local timer = xmlValue("time", data["background"]) + local t = {} + local tab = data["background"]["color"] + if(tab~=nil)then + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + table.insert(t, colors[v:value()]) + end + end + if(duration~=nil)and(#t>0)then + self:changeBackground(duration, timer or 0, table.unpack(t)) + end + end + if(data["text"]~=nil)then + local duration = xmlValue("duration", data["text"]) + local timer = xmlValue("time", data["text"]) + local t = {} + local tab = data["text"]["text"] + if(tab~=nil)then + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + table.insert(t, v:value()) + end + end + if(duration~=nil)and(#t>0)then + self:changeText(duration, timer or 0, table.unpack(t)) + end + end + if(xmlValue("onDone", data)~=nil)then self:generateXMLEventFunction(self.onDone, xmlValue("onDone", data)) end + if(xmlValue("onStart", data)~=nil)then self:generateXMLEventFunction(self.onDone, xmlValue("onStart", data)) end + if(xmlValue("autoDestroy", data)~=nil)then + if(xmlValue("autoDestroy", data))then + autoDestroy = true + end + end + mode = xmlValue("mode", data) or mode + if(xmlValue("play", data)~=nil)then if(xmlValue("play", data))then self:play(loop) end end + return self + end, + + getZIndex = function(self) + return 1 + end; + + getName = function(self) + return self.name + end; + + setObject = function(self, obj) + _OBJ = obj + return self + end; + + move = function(self, x, y, duration, timer, obj) + _OBJ = obj or _OBJ + predefinedLerp(x,y,duration,timer or 0,_OBJ.getPosition,_OBJ.setPosition, "position", self) + return self + end, + + offset = function(self, x, y, duration, timer, obj) + _OBJ = obj or _OBJ + predefinedLerp(x,y,duration,timer or 0,_OBJ.getOffset,_OBJ.setOffset, "offset", self) + return self + end, + + size = function(self, w, h, duration, timer, obj) + _OBJ = obj or _OBJ + predefinedLerp(w,h,duration,timer or 0,_OBJ.getSize,_OBJ.setSize, "size", self) + return self + end, + + changeText = function(self, duration, timer, ...) + local text = {...} + timer = timer or 0 + _OBJ = obj or _OBJ + for n=1,#text do + addAnimationPart(timer+n*(duration/#text), function() + _OBJ.setText(_OBJ, text[n]) + end) + end + return self + end, + + changeBackground = function(self, duration, timer, ...) + local colors = {...} + timer = timer or 0 + _OBJ = obj or _OBJ + for n=1,#colors do + addAnimationPart(timer+n*(duration/#colors), function() + _OBJ.setBackground(_OBJ, colors[n]) + end) + end + return self + end, + + changeTextColor = function(self, duration, timer, ...) + local colors = {...} + timer = timer or 0 + _OBJ = obj or _OBJ + for n=1,#colors do + addAnimationPart(timer+n*(duration/#colors), function() + _OBJ.setForeground(_OBJ, colors[n]) + end) + end + return self + end, + + add = function(self, func, wait) + lastFunc = func + addAnimationPart((wait or nextWaitTimer) + (animations[#animations]~=nil and animations[#animations].t or 0), func) + return self + end; + + wait = function(self, wait) + nextWaitTimer = wait + return self + end; + + rep = function(self, reps) + if(lastFunc~=nil)then + for n = 1, reps or 1 do + addAnimationPart((wait or nextWaitTimer) + (animations[#animations]~=nil and animations[#animations].t or 0), lastFunc) + end + end + return self + end; + + onDone = function(self, f) + eventSystem:registerEvent("animation_done", f) + return self + end, + + onStart = function(self, f) + eventSystem:registerEvent("animation_start", f) + return self + end, + + setAutoDestroy = function(self, destroy) + autoDestroy = destroy~=nil and destroy or true + return self + end, + + animationDoneHandler = function(self) + eventSystem:sendEvent("animation_done", self) + self.parent:removeEvent("other_event", self) + if(autoDestroy)then + self.parent:removeObject(self) + self = nil + end + end; + + animationStartHandler = function(self) + eventSystem:sendEvent("animation_start", self) + end; + + clear = function(self) + animations = {} + lastFunc = nil + nextWaitTimer = 0 + index = 1 + animationTime = 0 + infinitePlay = false + return self + end; + + play = function(self, infinite) + self:cancel() + animationActive = true + infinitePlay = infinite and true or false + index = 1 + animationTime = 0 + if (animations[index] ~= nil) then + if (animations[index].t > 0) then + timerObj = os.startTimer(animations[index].t) + else + onPlay(self) + end + else + self:animationDoneHandler() + end + self.parent:addEvent("other_event", self) + return self + end; + + cancel = function(self) + if(timerObj~=nil)then + os.cancelTimer(timerObj) + infinitePlay = false + end + animationActive = false + self.parent:removeEvent("other_event", self) + return self + end; + + internalObjetCall = function(self) + self:play(loop) + end, + + eventHandler = function(self, event, tObj) + if(animationActive)then + if (event == "timer") and (tObj == timerObj) then + if (animations[index] ~= nil) then + onPlay(self) + else + self:animationDoneHandler() + end + end + end + end; + } + object.__index = object + + return object +end \ No newline at end of file diff --git a/src/Basalt/objects/Breakline.lua b/src/Basalt/objects/Breakline.lua new file mode 100644 index 0000000..01c8f42 --- /dev/null +++ b/src/Basalt/objects/Breakline.lua @@ -0,0 +1,109 @@ +local Object = require("Object") +local xmlValue = require("utils").getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "Breakline" + + base.width = 15 + base.height = 1 + + base:setValue(1) + base:setZIndex(2) + + local dirType = "vertical" + local length = 1 + + local function mouseEvent(self, button, x, y) + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local w,h = self:getSize() + if (dirType == "horizontal") then + for _index = 0, w do + if (obx + _index == x) and (oby <= y) and (oby + h > y) then + index = math.min(_index + 1, w - (symbolSize - 1)) + self:setValue(maxValue / w * (index)) + self:updateDraw() + end + end + end + if (dirType == "vertical") then + for _index = 0, h do + if (oby + _index == y) and (obx <= x) and (obx + w > x) then + index = math.min(_index + 1, h - (symbolSize - 1)) + self:setValue(maxValue / h * (index)) + self:updateDraw() + end + end + end + end + + local object = { + getType = function(self) + return objectType + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("length", data)~=nil)then self:setLength(xmlValue("length", data)) end + if(xmlValue("dirType", data)~=nil)then self:setDirType(xmlValue("dirType", data):lower()) end + end, + + getLength = function(self) + return length + end, + + setLength = function(self, newLen) + if type(newLen)=="number" then + length = newLen + elseif type(newLen)=="string" then + length = self.parent:newDynamicValue(self, newLen) + self.parent:recalculateDynamicValues() + end + self:updateDraw() + return self + end; + + getDirType = function(self) + return dirType + end; + + setDirType = function(self, _typ) + if _typ:lower() == "horizontal" + or _typ:lower() == "vertical" then + dirType = _typ:lower() + end + self:updateDraw() + return self + end; + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + self.bgColor = self.parent:getBackground() + + local obx, oby = self:getAnchorPosition() + local length = length + if type(length) == "table" then + length = length[1] + end + if (dirType == "horizontal") then + self:getSize(length,1) + self.parent:writeText(obx, oby, ('\140'):rep(length), self.bgColor, self.fgColor) + elseif (dirType == "vertical") then + self:getSize(1,length) + for i=1,length do + self.parent:writeText(obx, oby+i-1, '\149', self.bgColor, self.fgColor) + end + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getBackground() + self.fgColor = self.parent:getTheme("Breakline") + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Button.lua b/src/Basalt/objects/Button.lua new file mode 100644 index 0000000..577f593 --- /dev/null +++ b/src/Basalt/objects/Button.lua @@ -0,0 +1,71 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = utils.getValueFromXML +local tHex = require("tHex") + +return function(name) + -- Button + local base = Object(name) + local objectType = "Button" + local textHorizontalAlign = "center" + local textVerticalAlign = "center" + + base:setZIndex(5) + base:setValue("Button") + base.width = 12 + base.height = 3 + + local object = { + init = function(self) + self.bgColor = self.parent:getTheme("ButtonBG") + self.fgColor = self.parent:getTheme("ButtonText") + end, + getType = function(self) + return objectType + end; + setHorizontalAlign = function(self, pos) + textHorizontalAlign = pos + self:updateDraw() + return self + end; + + setVerticalAlign = function(self, pos) + textVerticalAlign = pos + self:updateDraw() + return self + end; + + setText = function(self, text) + base:setValue(text) + self:updateDraw() + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("text", data)~=nil)then self:setText(xmlValue("text", data)) end + if(xmlValue("horizontalAlign", data)~=nil)then textHorizontalAlign = xmlValue("horizontalAlign", data) end + if(xmlValue("verticalAlign", data)~=nil)then textVerticalAlign = xmlValue("verticalAlign", data) end + return self + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + local verticalAlign = utils.getTextVerticalAlign(h, textVerticalAlign) + + for n = 1, h do + if (n == verticalAlign) then + self.parent:setText(obx, oby + (n - 1), utils.getTextHorizontalAlign(self:getValue(), w, textHorizontalAlign)) + self.parent:setFG(obx, oby + (n - 1), utils.getTextHorizontalAlign(tHex[self.fgColor]:rep(self:getValue():len()), w, textHorizontalAlign)) + end + end + end + end + end, + + } + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Checkbox.lua b/src/Basalt/objects/Checkbox.lua new file mode 100644 index 0000000..5ef5834 --- /dev/null +++ b/src/Basalt/objects/Checkbox.lua @@ -0,0 +1,83 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = utils.getValueFromXML + +return function(name) + -- Checkbox + local base = Object(name) + local objectType = "Checkbox" + + base:setZIndex(5) + base:setValue(false) + base.width = 1 + base.height = 1 + + local symbol = "\42" + + local object = { + + getType = function(self) + return objectType + end; + + setSymbol = function(self, sym) + symbol = sym + self:updateDraw() + return self + end, + + mouseHandler = function(self, button, x, y) + if (base.mouseHandler(self, button, x, y)) then + if(button == 1)then + if (self:getValue() ~= true) and (self:getValue() ~= false) then + self:setValue(false) + else + self:setValue(not self:getValue()) + end + self:updateDraw() + return true + end + end + return false + end, + + touchHandler = function(self, x, y) + return self:mouseHandler(1, x, y) + end, + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("checked", data)~=nil)then if(xmlValue("checked", data))then self:setValue(true) else self:setValue(false) end end + return self + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + local verticalAlign = utils.getTextVerticalAlign(h, "center") + if(self.bgColor~=false)then self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) end + for n = 1, h do + if (n == verticalAlign) then + if (self:getValue() == true) then + self.parent:writeText(obx, oby + (n - 1), utils.getTextHorizontalAlign(symbol, w, "center"), self.bgColor, self.fgColor) + else + self.parent:writeText(obx, oby + (n - 1), utils.getTextHorizontalAlign(" ", w, "center"), self.bgColor, self.fgColor) + end + end + end + end + end + end, + + init = function(self) + base.init(self) + self.bgColor = self.parent:getTheme("CheckboxBG") + self.fgColor = self.parent:getTheme("CheckboxText") + self.parent:addEvent("mouse_click", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Dropdown.lua b/src/Basalt/objects/Dropdown.lua new file mode 100644 index 0000000..29706fd --- /dev/null +++ b/src/Basalt/objects/Dropdown.lua @@ -0,0 +1,241 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = require("utils").getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "Dropdown" + base.width = 12 + base.height = 1 + base:setZIndex(6) + + local list = {} + local itemSelectedBG + local itemSelectedFG + local selectionColorActive = true + local align = "left" + local yOffset = 0 + + local dropdownW = 16 + local dropdownH = 6 + local closedSymbol = "\16" + local openedSymbol = "\31" + local isOpened = false + + local object = { + getType = function(self) + return objectType + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("selectionBG", data)~=nil)then itemSelectedBG = colors[xmlValue("selectionBG", data)] end + if(xmlValue("selectionFG", data)~=nil)then itemSelectedFG = colors[xmlValue("selectionFG", data)] end + if(xmlValue("dropdownWidth", data)~=nil)then dropdownW = xmlValue("dropdownWidth", data) end + if(xmlValue("dropdownHeight", data)~=nil)then dropdownH = xmlValue("dropdownHeight", data) end + if(xmlValue("offset", data)~=nil)then yOffset = xmlValue("offset", data) end + if(data["item"]~=nil)then + local tab = data["item"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + self:addItem(xmlValue("text", v), colors[xmlValue("bg", v)], colors[xmlValue("fg", v)]) + end + end + end, + + setOffset = function(self, yOff) + yOffset = yOff + self:updateDraw() + return self + end; + + getOffset = function(self) + return yOffset + end; + + addItem = function(self, text, bgCol, fgCol, ...) + table.insert(list, { text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + self:updateDraw() + return self + end; + + getAll = function(self) + return list + end; + + removeItem = function(self, index) + table.remove(list, index) + self:updateDraw() + return self + end; + + getItem = function(self, index) + return list[index] + end; + + getItemIndex = function(self) + local selected = self:getValue() + for key, value in pairs(list) do + if (value == selected) then + return key + end + end + end; + + clear = function(self) + list = {} + self:setValue({}) + self:updateDraw() + return self + end; + + getItemCount = function(self) + return #list + end; + + editItem = function(self, index, text, bgCol, fgCol, ...) + table.remove(list, index) + table.insert(list, index, { text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + self:updateDraw() + return self + end; + + selectItem = function(self, index) + self:setValue(list[index] or {}) + self:updateDraw() + return self + end; + + setSelectedItem = function(self, bgCol, fgCol, active) + itemSelectedBG = bgCol or self.bgColor + itemSelectedFG = fgCol or self.fgColor + selectionColorActive = active + self:updateDraw() + return self + end; + + setDropdownSize = function(self, width, height) + dropdownW, dropdownH = width, height + self:updateDraw() + return self + end, + + mouseHandler = function(self, button, x, y) + if (isOpened) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + if(button==1)then + if (#list > 0) then + for n = 1, dropdownH do + if (list[n + yOffset] ~= nil) then + if (obx <= x) and (obx + dropdownW > x) and (oby + n == y) then + self:setValue(list[n + yOffset]) + self:updateDraw() + local val = self:getEventSystem():sendEvent("mouse_click", self, "mouse_click", dir, x, y) + if(val==false)then return val end + return true + end + end + end + end + end + end + if (base.mouseHandler(self, button, x, y)) then + isOpened = (not isOpened) + self:updateDraw() + return true + else + if(isOpened)then + self:updateDraw() + isOpened = false + end + return false + end + end, + + mouseUpHandler = function(self, button, x, y) + if (isOpened) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + if(button==1)then + if (#list > 0) then + for n = 1, dropdownH do + if (list[n + yOffset] ~= nil) then + if (obx <= x) and (obx + dropdownW > x) and (oby + n == y) then + isOpened = false + self:updateDraw() + local val = self:getEventSystem():sendEvent("mouse_up", self, "mouse_up", dir, x, y) + if(val==false)then return val end + return true + end + end + end + end + end + end + end, + + scrollHandler = function(self, dir, x, y) + if (isOpened)and(self:isFocused()) then + yOffset = yOffset + dir + if (yOffset < 0) then + yOffset = 0 + end + if (dir == 1) then + if (#list > dropdownH) then + if (yOffset > #list - dropdownH) then + yOffset = #list - dropdownH + end + else + yOffset = math.min(#list - 1, 0) + end + end + local val = self:getEventSystem():sendEvent("mouse_scroll", self, "mouse_scroll", dir, x, y) + if(val==false)then return val end + self:updateDraw() + return true + end + end, + + draw = function(self) + if (base.draw(self)) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if (self.parent ~= nil) then + if(self.bgColor~=false)then self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) end + local val = self:getValue() + local text = utils.getTextHorizontalAlign((val~=nil and val.text or ""), w, align):sub(1, w - 1) .. (isOpened and openedSymbol or closedSymbol) + self.parent:writeText(obx, oby, text, self.bgColor, self.fgColor) + + if (isOpened) then + for n = 1, dropdownH do + if (list[n + yOffset] ~= nil) then + if (list[n + yOffset] == val) then + if (selectionColorActive) then + self.parent:writeText(obx, oby + n, utils.getTextHorizontalAlign(list[n + yOffset].text, dropdownW, align), itemSelectedBG, itemSelectedFG) + else + self.parent:writeText(obx, oby + n, utils.getTextHorizontalAlign(list[n + yOffset].text, dropdownW, align), list[n + yOffset].bgCol, list[n + yOffset].fgCol) + end + else + self.parent:writeText(obx, oby + n, utils.getTextHorizontalAlign(list[n + yOffset].text, dropdownW, align), list[n + yOffset].bgCol, list[n + yOffset].fgCol) + end + end + end + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("DropdownBG") + self.fgColor = self.parent:getTheme("DropdownText") + itemSelectedBG = self.parent:getTheme("SelectionBG") + itemSelectedFG = self.parent:getTheme("SelectionText") + if(self.parent~=nil)then + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_up", self) + self.parent:addEvent("mouse_scroll", self) + end + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Graphic.lua b/src/Basalt/objects/Graphic.lua new file mode 100644 index 0000000..52be81d --- /dev/null +++ b/src/Basalt/objects/Graphic.lua @@ -0,0 +1,471 @@ +local Object = require("Object") +local geometric = require("geometricPoints") +local tHex = require("tHex") +local xmlValue = require("utils").getValueFromXML + +local sub,len,max,min = string.sub,string.len,math.max,math.min + +return function(name) + -- Graphic + local base = Object(name) + local objectType = "Graphic" + base:setZIndex(2) + + local graphicObjects = {} + local graphic = {} + local shrinkedGraphic = {} + local isGraphicShrinked = false + local xOffset, yOffset = 0, 0 + local dragable = false + local xMouse,yMouse + local w, h = 40, 15 + local canvasSizeChanged = false + + local tColourLookup = {} + for n=1,16 do + tColourLookup[ string.byte( "0123456789abcdef",n,n ) ] = 2^(n-1) + end + + local function stringToTable(str) + local t = {} + for i = 1, #str do + t[i] = str:sub(i, i) + end + return t + end + + local function setBG(x, y, width, height, colorStr) + if (y >= 1) and (y <= height) then + if (x + len(colorStr) > 0) and (x <= width) then + local oldCache = graphic[y] + local newCache + local nEnd = x + #colorStr - 1 + + if (x < 1) then + colorStr = sub(colorStr, 1 - x + 1, width - x + 1) + elseif (nEnd > width) then + colorStr = sub(colorStr, 1, width - x + 1) + end + + if (x > 1) then + newCache = sub(oldCache, 1, x - 1) .. colorStr + else + newCache = colorStr + end + if nEnd < width then + newCache = newCache .. sub(oldCache, nEnd + 1, width) + end + graphic[y] = newCache + end + end + end + + local function redrawCanvasSize() + local w,h = w,h + if(isGraphicShrinked)then w = w*2 h = h*3 end + for y=1,h do + if(graphic[y]~=nil)then + if(w>graphic[y]:len())then + graphic[y] = graphic[y]..(tHex[base.bgColor]):rep(w-graphic[y]:len()) + else + graphic[y] = graphic[y]:sub(1,w) + end + else + graphic[y] = (tHex[base.bgColor]):rep(w) + end + end + end + redrawCanvasSize() + + + local function shrink() + local function parseLine( tImageArg, sLine ) + local tLine = {} + for x=1,sLine:len() do + tLine[x] = tColourLookup[ string.byte(sLine,x,x) ] or 0 + end + table.insert( tImageArg, tLine ) + end + function parseImage( sRawData ) + if type( sRawData ) ~= "string" then + error( "bad argument #1 (expected string, got " .. type( sRawData ) .. ")" ) + end + local tImage = {} + for sLine in ( sRawData .. "\n" ):gmatch( "(.-)\n" ) do + parseLine( tImage, sLine ) + end + return tImage + end + + local rawImg = "" + for y=1,#graphic do + if(y==#graphic)then + rawImg = rawImg..graphic[y] + else + rawImg = rawImg..graphic[y].."\n" + end + end + local img = parseImage(rawImg) + -- shrinkSystem is copy pasted (and slightly changed) from blittle by Bomb Bloke: http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/ + local relations = { [0] = { 8, 4, 3, 6, 5 }, { 4, 14, 8, 7 }, { 6, 10, 8, 7 }, { 9, 11, 8, 0 }, { 1, 14, 8, 0 }, { 13, 12, 8, 0 }, { 2, 10, 8, 0 }, { 15, 8, 10, 11, 12, 14 }, + { 0, 7, 1, 9, 2, 13 }, { 3, 11, 8, 7 }, { 2, 6, 7, 15 }, { 9, 3, 7, 15 }, { 13, 5, 7, 15 }, { 5, 12, 8, 7 }, { 1, 4, 7, 15 }, { 7, 10, 11, 12, 14 } } + + local colourNum, exponents, colourChar = {}, {}, {} + for i = 0, 15 do + exponents[2 ^ i] = i + end + do + local hex = "0123456789abcdef" + for i = 1, 16 do + colourNum[hex:sub(i, i)] = i - 1 + colourNum[i - 1] = hex:sub(i, i) + colourChar[hex:sub(i, i)] = 2 ^ (i - 1) + colourChar[2 ^ (i - 1)] = hex:sub(i, i) + + local thisRel = relations[i - 1] + for i = 1, #thisRel do + thisRel[i] = 2 ^ thisRel[i] + end + end + end + + local function getBestColourMatch(usage) + local lastCol = relations[exponents[usage[#usage][1]]] + + for j = 1, #lastCol do + local thisRelation = lastCol[j] + for i = 1, #usage - 1 do + if usage[i][1] == thisRelation then + return i + end + end + end + + return 1 + end + + local function colsToChar(pattern, totals) + if not totals then + local newPattern = {} + totals = {} + for i = 1, 6 do + local thisVal = pattern[i] + local thisTot = totals[thisVal] + totals[thisVal], newPattern[i] = thisTot and (thisTot + 1) or 1, thisVal + end + pattern = newPattern + end + + local usage = {} + for key, value in pairs(totals) do + usage[#usage + 1] = { key, value } + end + + if #usage > 1 then + -- Reduce the chunk to two colours: + while #usage > 2 do + table.sort(usage, function(a, b) + return a[2] > b[2] + end) + local matchToInd, usageLen = getBestColourMatch(usage), #usage + local matchFrom, matchTo = usage[usageLen][1], usage[matchToInd][1] + for i = 1, 6 do + if pattern[i] == matchFrom then + pattern[i] = matchTo + usage[matchToInd][2] = usage[matchToInd][2] + 1 + end + end + usage[usageLen] = nil + end + + -- Convert to character. Adapted from oli414's function: + -- http://www.computercraft.info/forums2/index.php?/topic/25340-cc-176-easy-drawing-characters/ + local data = 128 + for i = 1, #pattern - 1 do + if pattern[i] ~= pattern[6] then + data = data + 2 ^ (i - 1) + end + end + return string.char(data), colourChar[usage[1][1] == pattern[6] and usage[2][1] or usage[1][1]], colourChar[pattern[6]] + else + -- Solid colour character: + return "\128", colourChar[pattern[1]], colourChar[pattern[1]] + end + end + + local results, width, height, bgCol = { {}, {}, {} }, 0, #img + #img % 3, base.bgColor or colors.black + for i = 1, #img do + if #img[i] > width then + width = #img[i] + end + end + + for y = 0, height - 1, 3 do + local cRow, tRow, bRow, counter = {}, {}, {}, 1 + + for x = 0, width - 1, 2 do + -- Grab a 2x3 chunk: + local pattern, totals = {}, {} + + for yy = 1, 3 do + for xx = 1, 2 do + pattern[#pattern + 1] = (img[y + yy] and img[y + yy][x + xx]) and (img[y + yy][x + xx] == 0 and bgCol or img[y + yy][x + xx]) or bgCol + totals[pattern[#pattern]] = totals[pattern[#pattern]] and (totals[pattern[#pattern]] + 1) or 1 + end + end + + cRow[counter], tRow[counter], bRow[counter] = colsToChar(pattern, totals) + counter = counter + 1 + end + + results[1][#results[1] + 1], results[2][#results[2] + 1], results[3][#results[3] + 1] = table.concat(cRow), table.concat(tRow), table.concat(bRow) + end + + results.width, results.height = #results[1][1], #results[1] + + shrinkedGraphic = results + end + + local function redraw() + local w,h = w,h + if(isGraphicShrinked)then w = w*2 h = h*3 end + for k,v in pairs(graphicObjects)do + for a,b in pairs(v[1])do + setBG(b.x, b.y, w, h, v[2]) + end + end + if(isGraphicShrinked)then + shrink() + end + end + + local object = { + init = function(self) + self.bgColor = self.parent:getTheme("GraphicBG") + end, + + getType = function(self) + return objectType + end; + + setSize = function(self, width, height, rel) + base.setSize(self, width, height, rel) + if not(canvasSizeChanged)then + w = width + h = height + redrawCanvasSize() + end + redraw() + return self + end, + + setOffset = function(self, x, y) + xOffset = x or xOffset + yOffset = y or yOffset + return self + end, + + setCanvasSize = function(self, width, height) + w,h = width,height + canvasSizeChanged = true + redrawCanvasSize() + return self + end, + + clearCanvas = function(self) + graphicObjects = {} + graphic = {} + redrawCanvasSize() + end, + + getOffset = function(self) + return xOffset,yOffset + end, + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("text", data)~=nil)then self:setText(xmlValue("text", data)) end + if(xmlValue("xOffset", data)~=nil)then self:setOffset(xmlValue("xOffset", data), yOffset) end + if(xmlValue("yOffset", data)~=nil)then self:setOffset(xOffset, xmlValue("yOffset", data)) end + if(xmlValue("wCanvas", data)~=nil)then w = xmlValue("wCanvas", data) end + if(xmlValue("hCanvas", data)~=nil)then h = xmlValue("hCanvas", data) end + if(xmlValue("shrink", data)~=nil)then if(xmlValue("shrink", data))then self:shrink() end end + if(xmlValue("dragable", data)~=nil)then if(xmlValue("dragable", data))then dragable = true end end + if(data["ellipse"]~=nil)then + local tab = data["ellipse"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + local col = colors[xmlValue("color", v)] + local rad1 = xmlValue("radius", v) + local rad2 = xmlValue("radius2", v) + local x = xmlValue("x", v) + local y = xmlValue("y", v) + local filled = xmlValue("filled", v) + self:addEllipse(col, rad1, rad2, x, y, filled) + end + end + if(data["circle"]~=nil)then + local tab = data["circle"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + local col = colors[xmlValue("color", v)] + local rad = tonumber(xmlValue("radius", v)) + local x = tonumber(xmlValue("x", v)) + local y = tonumber(xmlValue("y", v)) + local filled = xmlValue("filled", v) + self:addCircle(col, rad, x, y, filled) + end + end + if(data["line"]~=nil)then + local tab = data["line"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + local col = colors[xmlValue("color", v)] + local x = tonumber(xmlValue("x", v)) + local x2 = tonumber(xmlValue("x2", v)) + local y = tonumber(xmlValue("y", v)) + local y2 = tonumber(xmlValue("y2", v)) + self:addLine(col, x, y, x2, y2) + end + end + + if(data["rectangle"]~=nil)then + local tab = data["rectangle"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + local col = colors[xmlValue("color", v)] + local x = tonumber(xmlValue("x", v)) + local x2 = tonumber(xmlValue("x2", v)) + local y = tonumber(xmlValue("y", v)) + local y2 = tonumber(xmlValue("y2", v)) + local filled = xmlValue("filled", v)=="true" and true or false + self:addRectangle(col, x, y, x2, y2, filled) + end + end + if(data["triangle"]~=nil)then + local tab = data["triangle"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + local col = colors[xmlValue("color", v)] + local x = tonumber(xmlValue("x", v)) + local x2 = tonumber(xmlValue("x2", v)) + local x3 = tonumber(xmlValue("x2", v)) + local y = tonumber(xmlValue("y", v)) + local y2 = tonumber(xmlValue("y2", v)) + local y3 = tonumber(xmlValue("y3", v)) + local filled = xmlValue("filled", v) + self:addTriangle(col, x, y, x2, y2, x3, y3, filled) + end + end + + return self + end, + + addCircle = function(self, color, rad, x, y, filled) + local col = tHex[color] + table.insert(graphicObjects, {geometric.circle(x or 1, y or 1, rad, filled), tHex[color]}) + redraw() + return self + end; + + addEllipse = function(self, color, rad, rad2, x, y, filled) + table.insert(graphicObjects, {geometric.ellipse(x or 1, y or 1, rad, rad2, filled), tHex[color]}) + redraw() + return self + end; + + addLine = function(self, color, x1, y1, x2, y2) + table.insert(graphicObjects, {geometric.line(x1 or 1, y1 or 1, x2 or 1, y2 or 1), tHex[color]}) + redraw() + return self + end; + + addTriangle = function(self, color, x1, y1, x2, y2, x3, y3, filled) + table.insert(graphicObjects, {geometric.triangle(x1 or 1, y1 or 1, x2 or 1, y2 or 1, x3 or 1, y3 or 1, filled), tHex[color]}) + redraw() + return self + end; + + addRectangle = function(self, color, x1, y1, x2, y2, filled) + table.insert(graphicObjects, {geometric.rectangle(x1 or 1, y1 or 1, x2 or 1, y2 or 1, filled), tHex[color]}) + redraw() + return self + end; + + shrink = function(self) + isGraphicShrinked = true + redrawCanvasSize() + shrink() + return self + end, + + setDragable = function(self, drag) + dragable = drag == true and true or false + return self + end, + + mouseHandler = function(self, event, button, x, y) + if(base.mouseHandler(self, event, button, x, y))then + if(dragable)then + if(event=="mouse_click")then + xMouse,yMouse = x,y + end + + if(event=="mouse_drag")then + if(xMouse~=nil)and(yMouse~=nil)then + xOffset = max(min(xOffset+xMouse-x, w-self:getWidth()),0) + xMouse = x + yOffset = max(min(yOffset+yMouse-y, h-self:getHeight()),0) + yMouse = y + end + end + end + return true + end + return false + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if(self.bgColor~=false)then + self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) + end + if (isGraphicShrinked) then + -- this is copy pasted (and slightly changed) from blittle by Bomb Bloke: http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/ + local t, tC, bC = shrinkedGraphic[1], shrinkedGraphic[2], shrinkedGraphic[3] + for i = 1, shrinkedGraphic.height do + local x, y = obx+xOffset, oby + i - 1 + yOffset + if(y>oby-1)and(y<=oby+h-1)and(x<=w+obx)then + local tI = t[i] + local xpos,substart,subend = max(x, obx), max(1 - x + 1, 1), min(w - (x-obx), w) + if type(tI) == "string" then + self.parent:setText(xpos, y, sub(tI, substart, subend)) + self.parent:setFG(xpos, y, sub(tC[i], substart, subend)) + self.parent:setBG(xpos, y, sub(bC[i], substart, subend)) + elseif type(tI) == "table" then + self.parent:setText(xpos, y, sub(tI[2], substart, subend)) + self.parent:setFG(xpos, y, sub(tC[i], substart, subend)) + self.parent:setBG(xpos, y, sub(bC[i], substart, subend)) + end + end + end + else + for i = 1, #graphic do + local x, y = obx+xOffset, oby + i - 1 + yOffset + if(y>oby-1)and(y<=oby+h-1)and(x<=w+obx)then + local xpos,substart,subend = max(x, obx), max(1 - x + 1, 1), min(w - (x-obx), w) + self.parent:setBG(xpos, y, sub(graphic[i],substart,subend)) + end + end + end + end + self:setVisualChanged(false) + end + end; + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Image.lua b/src/Basalt/objects/Image.lua new file mode 100644 index 0000000..24ff76e --- /dev/null +++ b/src/Basalt/objects/Image.lua @@ -0,0 +1,202 @@ +local Object = require("Object") +local xmlValue = require("utils").getValueFromXML + +return function(name) + -- Image + local base = Object(name) + local objectType = "Image" + base:setZIndex(2) + local image + local shrinkedImage + local imageGotShrinked = false + + local function shrink() + -- shrinkSystem is copy pasted (and slightly changed) from blittle by Bomb Bloke: http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/ + local relations = { [0] = { 8, 4, 3, 6, 5 }, { 4, 14, 8, 7 }, { 6, 10, 8, 7 }, { 9, 11, 8, 0 }, { 1, 14, 8, 0 }, { 13, 12, 8, 0 }, { 2, 10, 8, 0 }, { 15, 8, 10, 11, 12, 14 }, + { 0, 7, 1, 9, 2, 13 }, { 3, 11, 8, 7 }, { 2, 6, 7, 15 }, { 9, 3, 7, 15 }, { 13, 5, 7, 15 }, { 5, 12, 8, 7 }, { 1, 4, 7, 15 }, { 7, 10, 11, 12, 14 } } + + local colourNum, exponents, colourChar = {}, {}, {} + for i = 0, 15 do + exponents[2 ^ i] = i + end + do + local hex = "0123456789abcdef" + for i = 1, 16 do + colourNum[hex:sub(i, i)] = i - 1 + colourNum[i - 1] = hex:sub(i, i) + colourChar[hex:sub(i, i)] = 2 ^ (i - 1) + colourChar[2 ^ (i - 1)] = hex:sub(i, i) + + local thisRel = relations[i - 1] + for i = 1, #thisRel do + thisRel[i] = 2 ^ thisRel[i] + end + end + end + + local function getBestColourMatch(usage) + local lastCol = relations[exponents[usage[#usage][1]]] + + for j = 1, #lastCol do + local thisRelation = lastCol[j] + for i = 1, #usage - 1 do + if usage[i][1] == thisRelation then + return i + end + end + end + + return 1 + end + + local function colsToChar(pattern, totals) + if not totals then + local newPattern = {} + totals = {} + for i = 1, 6 do + local thisVal = pattern[i] + local thisTot = totals[thisVal] + totals[thisVal], newPattern[i] = thisTot and (thisTot + 1) or 1, thisVal + end + pattern = newPattern + end + + local usage = {} + for key, value in pairs(totals) do + usage[#usage + 1] = { key, value } + end + + if #usage > 1 then + -- Reduce the chunk to two colours: + while #usage > 2 do + table.sort(usage, function(a, b) + return a[2] > b[2] + end) + local matchToInd, usageLen = getBestColourMatch(usage), #usage + local matchFrom, matchTo = usage[usageLen][1], usage[matchToInd][1] + for i = 1, 6 do + if pattern[i] == matchFrom then + pattern[i] = matchTo + usage[matchToInd][2] = usage[matchToInd][2] + 1 + end + end + usage[usageLen] = nil + end + + -- Convert to character. Adapted from oli414's function: + -- http://www.computercraft.info/forums2/index.php?/topic/25340-cc-176-easy-drawing-characters/ + local data = 128 + for i = 1, #pattern - 1 do + if pattern[i] ~= pattern[6] then + data = data + 2 ^ (i - 1) + end + end + return string.char(data), colourChar[usage[1][1] == pattern[6] and usage[2][1] or usage[1][1]], colourChar[pattern[6]] + else + -- Solid colour character: + return "\128", colourChar[pattern[1]], colourChar[pattern[1]] + end + end + + local results, width, height, bgCol = { {}, {}, {} }, 0, #image + #image % 3, base.bgColor or colors.black + for i = 1, #image do + if #image[i] > width then + width = #image[i] + end + end + + for y = 0, height - 1, 3 do + local cRow, tRow, bRow, counter = {}, {}, {}, 1 + + for x = 0, width - 1, 2 do + -- Grab a 2x3 chunk: + local pattern, totals = {}, {} + + for yy = 1, 3 do + for xx = 1, 2 do + pattern[#pattern + 1] = (image[y + yy] and image[y + yy][x + xx]) and (image[y + yy][x + xx] == 0 and bgCol or image[y + yy][x + xx]) or bgCol + totals[pattern[#pattern]] = totals[pattern[#pattern]] and (totals[pattern[#pattern]] + 1) or 1 + end + end + + cRow[counter], tRow[counter], bRow[counter] = colsToChar(pattern, totals) + counter = counter + 1 + end + + results[1][#results[1] + 1], results[2][#results[2] + 1], results[3][#results[3] + 1] = table.concat(cRow), table.concat(tRow), table.concat(bRow) + end + + results.width, results.height = #results[1][1], #results[1] + + shrinkedImage = results + end + + local object = { + init = function(self) + self.bgColor = self.parent:getTheme("ImageBG") + end, + getType = function(self) + return objectType + end; + + loadImage = function(self, path) + image = paintutils.loadImage(path) + imageGotShrinked = false + self:updateDraw() + return self + end; + + + shrink = function(self) + shrink() + imageGotShrinked = true + self:updateDraw() + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("shrink", data)~=nil)then if(xmlValue("shrink", data))then self:shrink() end end + if(xmlValue("path", data)~=nil)then self:loadImage(xmlValue("path", data)) end + return self + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + if (image ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if (imageGotShrinked) then + -- this is copy pasted (and slightly changed) from blittle by Bomb Bloke: http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/ + local t, tC, bC = shrinkedImage[1], shrinkedImage[2], shrinkedImage[3] + for i = 1, shrinkedImage.height do + local tI = t[i] + if type(tI) == "string" then + self.parent:setText(obx, oby + i - 1, tI) + self.parent:setFG(obx, oby + i - 1, tC[i]) + self.parent:setBG(obx, oby + i - 1, bC[i]) + elseif type(tI) == "table" then + self.parent:setText(obx, oby + i - 1, tI[2]) + self.parent:setFG(obx, oby + i - 1, tC[i]) + self.parent:setBG(obx, oby + i - 1, bC[i]) + end + end + else + for yPos = 1, math.min(#image, h) do + local line = image[yPos] + for xPos = 1, math.min(#line, w) do + if line[xPos] > 0 then + self.parent:drawBackgroundBox(obx + xPos - 1, oby + yPos - 1, 1, 1, line[xPos]) + end + end + end + end + end + end + end + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Input.lua b/src/Basalt/objects/Input.lua new file mode 100644 index 0000000..4533aca --- /dev/null +++ b/src/Basalt/objects/Input.lua @@ -0,0 +1,368 @@ +local Object = require("Object") +local utils = require("utils") +local log = require("basaltLogs") +local xmlValue = utils.getValueFromXML + +return function(name) + -- Input + local base = Object(name) + local objectType = "Input" + + local inputType = "text" + local inputLimit = 0 + base:setZIndex(5) + base:setValue("") + base.width = 10 + base.height = 1 + + local textX = 1 + local wIndex = 1 + + local defaultText = "" + local defaultBGCol + local defaultFGCol + local showingText = defaultText + local internalValueChange = false + + local object = { + getType = function(self) + return objectType + end; + + setInputType = function(self, iType) + if (iType == "password") or (iType == "number") or (iType == "text") then + inputType = iType + end + self:updateDraw() + return self + end; + + setDefaultText = function(self, text, fCol, bCol) + defaultText = text + defaultBGCol = bCol or defaultBGCol + defaultFGCol = fCol or defaultFGCol + if (self:isFocused()) then + showingText = "" + else + showingText = defaultText + end + self:updateDraw() + return self + end; + + getInputType = function(self) + return inputType + end; + + setValue = function(self, val) + base.setValue(self, tostring(val)) + if not (internalValueChange) then + if(self:isFocused())then + textX = tostring(val):len() + 1 + wIndex = math.max(1, textX-self:getWidth()+1) + local obx, oby = self:getAnchorPosition() + self.parent:setCursor(true, obx + textX - wIndex, oby+math.floor(self.height/2), self.fgColor) + end + end + self:updateDraw() + return self + end; + + getValue = function(self) + local val = base.getValue(self) + return inputType == "number" and tonumber(val) or val + end; + + setInputLimit = function(self, limit) + inputLimit = tonumber(limit) or inputLimit + self:updateDraw() + return self + end; + + getInputLimit = function(self) + return inputLimit + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + local dBG,dFG + if(xmlValue("defaultBG", data)~=nil)then dBG = xmlValue("defaultBG", data) end + if(xmlValue("defaultFG", data)~=nil)then dFG = xmlValue("defaultFG", data) end + if(xmlValue("default", data)~=nil)then self:setDefaultText(xmlValue("default", data), dFG~=nil and colors[dFG], dBG~=nil and colors[dBG]) end + if(xmlValue("limit", data)~=nil)then self:setInputLimit(xmlValue("limit", data)) end + if(xmlValue("type", data)~=nil)then self:setInputType(xmlValue("type", data)) end + return self + end, + + getFocusHandler = function(self) + base.getFocusHandler(self) + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + showingText = "" + if(defaultText~="")then + self:updateDraw() + end + self.parent:setCursor(true, obx + textX - wIndex, oby+math.max(math.ceil(self:getHeight()/2-1, 1)), self.fgColor) + end + end; + + loseFocusHandler = function(self) + base.loseFocusHandler(self) + if (self.parent ~= nil) then + showingText = defaultText + if(defaultText~="")then + self:updateDraw() + end + self.parent:setCursor(false) + end + end; + + keyHandler = function(self, key) + if (base.keyHandler(self, key)) then + local w,h = self:getSize() + internalValueChange = true + if (key == keys.backspace) then + -- on backspace + local text = tostring(base.getValue()) + if (textX > 1) then + self:setValue(text:sub(1, textX - 2) .. text:sub(textX, text:len())) + if (textX > 1) then + textX = textX - 1 + end + if (wIndex > 1) then + if (textX < wIndex) then + wIndex = wIndex - 1 + end + end + end + end + if (key == keys.enter) then + -- on enter + if (self.parent ~= nil) then + --self.parent:removeFocusedObject(self) + end + end + if (key == keys.right) then + -- right arrow + local tLength = tostring(base.getValue()):len() + textX = textX + 1 + + if (textX > tLength) then + textX = tLength + 1 + end + if (textX < 1) then + textX = 1 + end + if (textX < wIndex) or (textX >= w + wIndex) then + wIndex = textX - w + 1 + end + if (wIndex < 1) then + wIndex = 1 + end + end + + if (key == keys.left) then + -- left arrow + textX = textX - 1 + if (textX >= 1) then + if (textX < wIndex) or (textX >= w + wIndex) then + wIndex = textX + end + end + if (textX < 1) then + textX = 1 + end + if (wIndex < 1) then + wIndex = 1 + end + end + local obx, oby = self:getAnchorPosition() + local val = tostring(base.getValue()) + local cursorX = (textX <= val:len() and textX - 1 or val:len()) - (wIndex - 1) + + if (cursorX > self.x + w - 1) then + cursorX = self.x + w - 1 + end + if (self.parent ~= nil) then + self.parent:setCursor(true, obx + cursorX, oby+math.max(math.ceil(h/2-1, 1)), self.fgColor) + end + self:updateDraw() + internalValueChange = false + return true + end + return false + end, + + charHandler = function(self, char) + if (base.charHandler(self, char)) then + internalValueChange = true + local w,h = self:getSize() + local text = base.getValue() + if (text:len() < inputLimit or inputLimit <= 0) then + if (inputType == "number") then + local cache = text + if (char == ".") or (tonumber(char) ~= nil) then + self:setValue(text:sub(1, textX - 1) .. char .. text:sub(textX, text:len())) + textX = textX + 1 + end + if (tonumber(base.getValue()) == nil) then + self:setValue(cache) + end + else + self:setValue(text:sub(1, textX - 1) .. char .. text:sub(textX, text:len())) + textX = textX + 1 + end + if (textX >= w + wIndex) then + wIndex = wIndex + 1 + end + end + local obx, oby = self:getAnchorPosition() + local val = tostring(base.getValue()) + local cursorX = (textX <= val:len() and textX - 1 or val:len()) - (wIndex - 1) + + local x = self:getX() + if (cursorX > x + w - 1) then + cursorX = x + w - 1 + end + if (self.parent ~= nil) then + self.parent:setCursor(true, obx + cursorX, oby+math.max(math.ceil(h/2-1, 1)), self.fgColor) + end + internalValueChange = false + self:updateDraw() + return true + end + return false + end, + + mouseHandler = function(self, button, x, y) + if(base.mouseHandler(self, button, x, y))then + local ax, ay = self:getAnchorPosition() + local obx, oby = self:getAbsolutePosition(ax, ay) + local w, h = self:getSize() + textX = x - obx + wIndex + local text = base.getValue() + if (textX > text:len()) then + textX = text:len() + 1 + end + if (textX < wIndex) then + wIndex = textX - 1 + if (wIndex < 1) then + wIndex = 1 + end + end + self.parent:setCursor(true, obx + textX - wIndex, oby+math.max(math.ceil(h/2-1, 1)), self.fgColor) + return true + end + end, + + dragHandler = function(self, btn, x, y, xOffset, yOffset) + if(self:isFocused())then + if(self:isCoordsInObject(x, y))then + if(base.dragHandler(self, btn, x, y, xOffset, yOffset))then + return true + end + end + self.parent:removeFocusedObject() + end + end, + + eventHandler = function(self, event, paste, p2, p3, p4) + if(base.eventHandler(self, event, paste, p2, p3, p4))then + if(event=="paste")then + if(self:isFocused())then + local text = base.getValue() + local w, h = self:getSize() + internalValueChange = true + if (inputType == "number") then + local cache = text + if (paste == ".") or (tonumber(paste) ~= nil) then + self:setValue(text:sub(1, textX - 1) .. paste .. text:sub(textX, text:len())) + textX = textX + paste:len() + end + if (tonumber(base.getValue()) == nil) then + self:setValue(cache) + end + else + self:setValue(text:sub(1, textX - 1) .. paste .. text:sub(textX, text:len())) + textX = textX + paste:len() + end + if (textX >= w + wIndex) then + wIndex = (textX+1)-w + end + + local obx, oby = self:getAnchorPosition() + local val = tostring(base.getValue()) + local cursorX = (textX <= val:len() and textX - 1 or val:len()) - (wIndex - 1) + + local x = self:getX() + if (cursorX > x + w - 1) then + cursorX = x + w - 1 + end + if (self.parent ~= nil) then + self.parent:setCursor(true, obx + cursorX, oby+math.max(math.ceil(h/2-1, 1)), self.fgColor) + end + self:updateDraw() + internalValueChange = false + end + end + end + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + local verticalAlign = utils.getTextVerticalAlign(h, "center") + + if(self.bgColor~=false)then self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) end + for n = 1, h do + if (n == verticalAlign) then + local val = tostring(base.getValue()) + local bCol = self.bgColor + local fCol = self.fgColor + local text + if (val:len() <= 0) then + text = showingText + bCol = defaultBGCol or bCol + fCol = defaultFGCol or fCol + end + + text = showingText + if (val ~= "") then + text = val + end + text = text:sub(wIndex, w + wIndex - 1) + local space = w - text:len() + if (space < 0) then + space = 0 + end + if (inputType == "password") and (val ~= "") then + text = string.rep("*", text:len()) + end + text = text .. string.rep(self.bgSymbol, space) + self.parent:writeText(obx, oby + (n - 1), text, bCol, fCol) + end + end + if(self:isFocused())then + self.parent:setCursor(true, obx + textX - wIndex, oby+math.max(math.ceil(self:getHeight()/2-1, 1)), self.fgColor) + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("InputBG") + self.fgColor = self.parent:getTheme("InputText") + if(self.parent~=nil)then + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("key", self) + self.parent:addEvent("char", self) + self.parent:addEvent("other_event", self) + self.parent:addEvent("mouse_drag", self) + end + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Label.lua b/src/Basalt/objects/Label.lua new file mode 100644 index 0000000..71ff284 --- /dev/null +++ b/src/Basalt/objects/Label.lua @@ -0,0 +1,162 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = utils.getValueFromXML +local createText = utils.createText +local tHex = require("tHex") +local bdf = require("miniBDF") + +return function(name) + -- Label + local base = Object(name) + local objectType = "Label" + + base:setZIndex(3) + + local autoSize = true + base:setValue("Label") + base.width = 5 + + local textHorizontalAlign = "left" + local textVerticalAlign = "top" + local fontsize = 0 + local font = "" + + local fgColChanged,bgColChanged = false,false + + local object = { + getType = function(self) + return objectType + end; + + setText = function(self, text) + text = tostring(text) + base:setValue(text) + if (autoSize) then + self.width = text:len() + end + self:updateDraw() + return self + end; + + setBackground = function(self, col) + base.setBackground(self, col) + bgColChanged = true + self:updateDraw() + return self + end, + + setForeground = function(self, col) + base.setForeground(self, col) + fgColChanged = true + self:updateDraw() + return self + end, + + setTextAlign = function(self, hor, vert) + textHorizontalAlign = hor or textHorizontalAlign + textVerticalAlign = vert or textVerticalAlign + self:updateDraw() + return self + end; + + setFontSize = function(self, size) + if(size>0)and(size<=4)then + fontsize = size-1 or 0 + end + self:updateDraw() + return self + end; + + getFontSize = function(self) + return fontsize+1 + end; + + setFont = function(self, name) + if fontBDF[name] then + font = fontBDF[name] + end + self:updateDraw() + return self + end; + + getFont = function(self) + return font + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("text", data)~=nil)then self:setText(xmlValue("text", data)) end + if(xmlValue("verticalAlign", data)~=nil)then textVerticalAlign = xmlValue("verticalAlign", data) end + if(xmlValue("horizontalAlign", data)~=nil)then textHorizontalAlign = xmlValue("horizontalAlign", data) end + if(xmlValue("size", data)~=nil)then self:setFontSize(xmlValue("size", data)) end + if(xmlValue("font", data)~=nil)then self:setFont(xmlValue("font", data)) end + return self + end, + + setSize = function(self, width, height, rel) + base.setSize(self, width, height, rel) + autoSize = false + self:updateDraw() + return self + end; + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + local verticalAlign = utils.getTextVerticalAlign(h, textVerticalAlign) + if(fontsize==0)then + if not(autoSize)then + local text = createText(self:getValue(), self:getWidth()) + for k,v in pairs(text)do + self.parent:writeText(obx, oby+k-1, v, self.bgColor, self.fgColor) + end + else + self.parent:writeText(obx, oby, self:getValue(), self.bgColor, self.fgColor) + end + else + local blit = {[1]='0',[2]='1',[4]='2',[8]='3',[16]='4',[32]='5',[64]='6',[128]='7',[256]='8',[512]='9',[1024]='a',[2048]='b',[4096]='c',[8192]='d',[16384]='e',[32768]='f' } + local tData + if font.drawString then + tData = font:drawString(self:getValue()) + tData = bdf.compress(tData, self.bgColor or colors.lightGray, self.fgColor) + else + local txt = self:getValue() + tData = { + ch={txt}, + bg={blit[self.bgColor]:rep(#txt)}, + fg={blit[self.fgColor]:rep(#txt)} + } + end + if(autoSize)then + self:setSize(#tData.ch[1], #tData.ch-1) + end + local oX, oY = self.parent:getSize() + local cX, cY = #tData.ch[1], #tData.ch + obx = obx or math.floor((oX - cX) / 2) + 1 + oby = oby or math.floor((oY - cY) / 2) + 1 + + for i = 1, cY do + self.parent:setFG(obx, oby + i - 2, tData.fg[i]) + self.parent:setBG(obx, oby + i - 2, tData.bg[i]) + self.parent:setText(obx, oby + i - 2, tData.ch[i]) + end + end + end + end + end, + init = function(self) + if(base.init(self))then + self.bgColor = self.parent:getTheme("LabelBG") + self.fgColor = self.parent:getTheme("LabelText") + if(self.parent.bgColor==colors.black)and(self.fgColor==colors.black)then + self.fgColor = colors.lightGray + end + end + end + + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/List.lua b/src/Basalt/objects/List.lua new file mode 100644 index 0000000..b89683b --- /dev/null +++ b/src/Basalt/objects/List.lua @@ -0,0 +1,213 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = utils.getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "List" + base.width = 16 + base.height = 6 + base:setZIndex(5) + + local list = {} + local itemSelectedBG + local itemSelectedFG + local selectionColorActive = true + local align = "left" + local yOffset = 0 + local scrollable = true + + local object = { + getType = function(self) + return objectType + end; + + addItem = function(self, text, bgCol, fgCol, ...) + table.insert(list, { text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + if (#list == 1) then + self:setValue(list[1]) + end + self:updateDraw() + return self + end; + + setOffset = function(self, yOff) + yOffset = yOff + self:updateDraw() + return self + end; + + getOffset = function(self) + return yOffset + end; + + removeItem = function(self, index) + table.remove(list, index) + self:updateDraw() + return self + end; + + getItem = function(self, index) + return list[index] + end; + + getAll = function(self) + return list + end; + + getItemIndex = function(self) + local selected = self:getValue() + for key, value in pairs(list) do + if (value == selected) then + return key + end + end + end; + + clear = function(self) + list = {} + self:setValue({}) + self:updateDraw() + return self + end; + + getItemCount = function(self) + return #list + end; + + editItem = function(self, index, text, bgCol, fgCol, ...) + table.remove(list, index) + table.insert(list, index, { text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + self:updateDraw() + return self + end; + + selectItem = function(self, index) + self:setValue(list[index] or {}) + self:updateDraw() + return self + end; + + setSelectedItem = function(self, bgCol, fgCol, active) + itemSelectedBG = bgCol or self.bgColor + itemSelectedFG = fgCol or self.fgColor + selectionColorActive = active~=nil and active or true + self:updateDraw() + return self + end; + + setScrollable = function(self, scroll) + scrollable = scroll + if(scroll==nil)then scrollable = true end + self:updateDraw() + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("selectionBG", data)~=nil)then itemSelectedBG = colors[xmlValue("selectionBG", data)] end + if(xmlValue("selectionFG", data)~=nil)then itemSelectedFG = colors[xmlValue("selectionFG", data)] end + if(xmlValue("scrollable", data)~=nil)then if(xmlValue("scrollable", data))then self:setScrollable(true) else self:setScrollable(false) end end + if(xmlValue("offset", data)~=nil)then yOffset = xmlValue("offset", data) end + if(data["item"]~=nil)then + local tab = data["item"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + self:addItem(xmlValue("text", v), colors[xmlValue("bg", v)], colors[xmlValue("fg", v)]) + end + end + return self + end, + + scrollHandler = function(self, dir, x, y) + if(base.scrollHandler(self, dir, x, y))then + if(scrollable)then + local w,h = self:getSize() + yOffset = yOffset + dir + if (yOffset < 0) then + yOffset = 0 + end + if (dir >= 1) then + if (#list > h) then + if (yOffset > #list - h) then + yOffset = #list - h + end + if (yOffset >= #list) then + yOffset = #list - 1 + end + else + yOffset = yOffset - 1 + end + end + self:updateDraw() + end + return true + end + return false + end, + + mouseHandler = function(self, button, x, y) + if(base.mouseHandler(self, button, x, y))then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local w,h = self:getSize() + if (#list > 0) then + for n = 1, h do + if (list[n + yOffset] ~= nil) then + if (obx <= x) and (obx + w > x) and (oby + n - 1 == y) then + self:setValue(list[n + yOffset]) + self:updateDraw() + end + end + end + end + return true + end + return false + end, + + dragHandler = function(self, button, x, y) + return self:mouseHandler(button, x, y) + end, + + touchHandler = function(self, x, y) + return self:mouseHandler(1, x, y) + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if(self.bgColor~=false)then + self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) + end + for n = 1, h do + if (list[n + yOffset] ~= nil) then + if (list[n + yOffset] == self:getValue()) then + if (selectionColorActive) then + self.parent:writeText(obx, oby + n - 1, utils.getTextHorizontalAlign(list[n + yOffset].text, w, align), itemSelectedBG, itemSelectedFG) + else + self.parent:writeText(obx, oby + n - 1, utils.getTextHorizontalAlign(list[n + yOffset].text, w, align), list[n + yOffset].bgCol, list[n + yOffset].fgCol) + end + else + self.parent:writeText(obx, oby + n - 1, utils.getTextHorizontalAlign(list[n + yOffset].text, w, align), list[n + yOffset].bgCol, list[n + yOffset].fgCol) + end + end + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("ListBG") + self.fgColor = self.parent:getTheme("ListText") + itemSelectedBG = self.parent:getTheme("SelectionBG") + itemSelectedFG = self.parent:getTheme("SelectionText") + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_drag", self) + self.parent:addEvent("mouse_scroll", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Menubar.lua b/src/Basalt/objects/Menubar.lua new file mode 100644 index 0000000..0e28f5a --- /dev/null +++ b/src/Basalt/objects/Menubar.lua @@ -0,0 +1,241 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = utils.getValueFromXML +local tHex = require("tHex") + +return function(name) + local base = Object(name) + local objectType = "Menubar" + local object = {} + + base.width = 30 + base.height = 1 + base:setZIndex(5) + + local list = {} + local itemSelectedBG + local itemSelectedFG + local selectionColorActive = true + local align = "left" + local itemOffset = 0 + local space = 1 + local scrollable = false + + local function maxScroll() + local mScroll = 0 + local xPos = 0 + local w = object:getWidth() + for n = 1, #list do + if (xPos + list[n].text:len() + space * 2 > w) then + if(xPos < w)then + mScroll = mScroll + (list[n].text:len() + space * 2-(w - xPos)) + else + mScroll = mScroll + list[n].text:len() + space * 2 + end + end + xPos = xPos + list[n].text:len() + space * 2 + + end + return mScroll + end + + object = { + getType = function(self) + return objectType + end; + + addItem = function(self, text, bgCol, fgCol, ...) + table.insert(list, { text = tostring(text), bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + if (#list == 1) then + self:setValue(list[1]) + end + self:updateDraw() + return self + end; + + getAll = function(self) + return list + end; + + getItemIndex = function(self) + local selected = self:getValue() + for key, value in pairs(list) do + if (value == selected) then + return key + end + end + end; + + clear = function(self) + list = {} + self:setValue({}) + self:updateDraw() + return self + end; + + setSpace = function(self, _space) + space = _space or space + self:updateDraw() + return self + end; + + setOffset = function(self, offset) + itemOffset = offset or 0 + if (itemOffset < 0) then + itemOffset = 0 + end + + local mScroll = maxScroll() + if (itemOffset > mScroll) then + itemOffset = mScroll + end + self:updateDraw() + return self + end; + + getOffset = function(self) + return itemOffset + end; + + setScrollable = function(self, scroll) + scrollable = scroll + if(scroll==nil)then scrollable = true end + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("selectionBG", data)~=nil)then itemSelectedBG = colors[xmlValue("selectionBG", data)] end + if(xmlValue("selectionFG", data)~=nil)then itemSelectedFG = colors[xmlValue("selectionFG", data)] end + if(xmlValue("scrollable", data)~=nil)then if(xmlValue("scrollable", data))then self:setScrollable(true) else self:setScrollable(false) end end + if(xmlValue("offset", data)~=nil)then self:setOffset(xmlValue("offset", data)) end + if(xmlValue("space", data)~=nil)then space = xmlValue("space", data) end + if(data["item"]~=nil)then + local tab = data["item"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + self:addItem(xmlValue("text", v), colors[xmlValue("bg", v)], colors[xmlValue("fg", v)]) + end + end + return self + end, + + removeItem = function(self, index) + table.remove(list, index) + self:updateDraw() + return self + end; + + getItem = function(self, index) + return list[index] + end; + + getItemCount = function(self) + return #list + end; + + editItem = function(self, index, text, bgCol, fgCol, ...) + table.remove(list, index) + table.insert(list, index, { text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + self:updateDraw() + return self + end; + + selectItem = function(self, index) + self:setValue(list[index] or {}) + self:updateDraw() + return self + end; + + setSelectedItem = function(self, bgCol, fgCol, active) + itemSelectedBG = bgCol or self.bgColor + itemSelectedFG = fgCol or self.fgColor + selectionColorActive = active + self:updateDraw() + return self + end; + + mouseHandler = function(self, button, x, y) + if(base.mouseHandler(self, button, x, y))then + local objX, objY = self:getAbsolutePosition(self:getAnchorPosition()) + local w,h = self:getSize() + local xPos = 0 + for n = 1, #list do + if (list[n] ~= nil) then + if (objX + xPos <= x + itemOffset) and (objX + xPos + list[n].text:len() + (space*2) > x + itemOffset) and (objY == y) then + self:setValue(list[n]) + self:getEventSystem():sendEvent(event, self, event, 0, x, y, list[n]) + end + xPos = xPos + list[n].text:len() + space * 2 + end + end + self:updateDraw() + return true + end + return false + end, + + scrollHandler = function(self, dir, x, y) + if(base.scrollHandler(self, dir, x, y))then + if(scrollable)then + itemOffset = itemOffset + dir + if (itemOffset < 0) then + itemOffset = 0 + end + + local mScroll = maxScroll() + + if (itemOffset > mScroll) then + itemOffset = mScroll + end + self:updateDraw() + end + return true + end + return false + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if(self.bgColor~=false)then + self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) + end + local text = "" + local textBGCol = "" + local textFGCol = "" + for _, v in pairs(list) do + local newItem = (" "):rep(space) .. v.text .. (" "):rep(space) + text = text .. newItem + if(v == self:getValue())then + textBGCol = textBGCol .. tHex[itemSelectedBG or v.bgCol or self.bgColor]:rep(newItem:len()) + textFGCol = textFGCol .. tHex[itemSelectedFG or v.FgCol or self.fgColor]:rep(newItem:len()) + else + textBGCol = textBGCol .. tHex[v.bgCol or self.bgColor]:rep(newItem:len()) + textFGCol = textFGCol .. tHex[v.FgCol or self.fgColor]:rep(newItem:len()) + end + end + + self.parent:setText(obx, oby, text:sub(itemOffset+1, w+itemOffset)) + self.parent:setBG(obx, oby, textBGCol:sub(itemOffset+1, w+itemOffset)) + self.parent:setFG(obx, oby, textFGCol:sub(itemOffset+1, w+itemOffset)) + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("MenubarBG") + self.fgColor = self.parent:getTheme("MenubarText") + itemSelectedBG = self.parent:getTheme("SelectionBG") + itemSelectedFG = self.parent:getTheme("SelectionText") + + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_scroll", self) + + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Pane.lua b/src/Basalt/objects/Pane.lua new file mode 100644 index 0000000..cfe33b7 --- /dev/null +++ b/src/Basalt/objects/Pane.lua @@ -0,0 +1,28 @@ +local Object = require("Object") +local log = require("basaltLogs") + +return function(name) + -- Pane + local base = Object(name) + local objectType = "Pane" + + local object = { + getType = function(self) + return objectType + end; + + setBackground = function(self, col, sym, symC) + base.setBackground(self, col, sym, symC) + return self + end, + + init = function(self) + if(base.init(self))then + self.bgColor = self.parent:getTheme("PaneBG") + self.fgColor = self.parent:getTheme("PaneBG") + end + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Program.lua b/src/Basalt/objects/Program.lua new file mode 100644 index 0000000..6a5df05 --- /dev/null +++ b/src/Basalt/objects/Program.lua @@ -0,0 +1,756 @@ +local Object = require("Object") +local tHex = require("tHex") +local process = require("process") +local xmlValue = require("utils").getValueFromXML +local log = require("basaltLogs") + +local sub = string.sub + +return function(name, parent) + local base = Object(name) + local objectType = "Program" + base:setZIndex(5) + local object + local cachedPath + + local function createBasaltWindow(x, y, width, height, self) + local xCursor, yCursor = 1, 1 + local bgColor, fgColor = colors.black, colors.white + local cursorBlink = false + local visible = false + + local cacheT = {} + local cacheBG = {} + local cacheFG = {} + + local tPalette = {} + + local emptySpaceLine + local emptyColorLines = {} + + for i = 0, 15 do + local c = 2 ^ i + tPalette[c] = { parent:getBasaltInstance().getBaseTerm().getPaletteColour(c) } + end + + local function createEmptyLines() + emptySpaceLine = (" "):rep(width) + for n = 0, 15 do + local nColor = 2 ^ n + local sHex = tHex[nColor] + emptyColorLines[nColor] = sHex:rep(width) + end + end + + local function recreateWindowArray() + createEmptyLines() + local emptyText = emptySpaceLine + local emptyFG = emptyColorLines[colors.white] + local emptyBG = emptyColorLines[colors.black] + for n = 1, height do + cacheT[n] = sub(cacheT[n] == nil and emptyText or cacheT[n] .. emptyText:sub(1, width - cacheT[n]:len()), 1, width) + cacheFG[n] = sub(cacheFG[n] == nil and emptyFG or cacheFG[n] .. emptyFG:sub(1, width - cacheFG[n]:len()), 1, width) + cacheBG[n] = sub(cacheBG[n] == nil and emptyBG or cacheBG[n] .. emptyBG:sub(1, width - cacheBG[n]:len()), 1, width) + end + base.updateDraw(base) + end + recreateWindowArray() + + local function updateCursor() + if xCursor >= 1 and yCursor >= 1 and xCursor <= width and yCursor <= height then + --parentTerminal.setCursorPos(xCursor + x - 1, yCursor + y - 1) + else + --parentTerminal.setCursorPos(0, 0) + end + --parentTerminal.setTextColor(fgColor) + end + + local function internalBlit(sText, sTextColor, sBackgroundColor) + -- copy pasti strikes again (cc: window.lua) + local nStart = xCursor + local nEnd = nStart + #sText - 1 + if yCursor >= 1 and yCursor <= height then + if nStart <= width and nEnd >= 1 then + -- Modify line + if nStart == 1 and nEnd == width then + cacheT[yCursor] = sText + cacheFG[yCursor] = sTextColor + cacheBG[yCursor] = sBackgroundColor + else + local sClippedText, sClippedTextColor, sClippedBackgroundColor + if nStart < 1 then + local nClipStart = 1 - nStart + 1 + local nClipEnd = width - nStart + 1 + sClippedText = sub(sText, nClipStart, nClipEnd) + sClippedTextColor = sub(sTextColor, nClipStart, nClipEnd) + sClippedBackgroundColor = sub(sBackgroundColor, nClipStart, nClipEnd) + elseif nEnd > width then + local nClipEnd = width - nStart + 1 + sClippedText = sub(sText, 1, nClipEnd) + sClippedTextColor = sub(sTextColor, 1, nClipEnd) + sClippedBackgroundColor = sub(sBackgroundColor, 1, nClipEnd) + else + sClippedText = sText + sClippedTextColor = sTextColor + sClippedBackgroundColor = sBackgroundColor + end + + local sOldText = cacheT[yCursor] + local sOldTextColor = cacheFG[yCursor] + local sOldBackgroundColor = cacheBG[yCursor] + local sNewText, sNewTextColor, sNewBackgroundColor + if nStart > 1 then + local nOldEnd = nStart - 1 + sNewText = sub(sOldText, 1, nOldEnd) .. sClippedText + sNewTextColor = sub(sOldTextColor, 1, nOldEnd) .. sClippedTextColor + sNewBackgroundColor = sub(sOldBackgroundColor, 1, nOldEnd) .. sClippedBackgroundColor + else + sNewText = sClippedText + sNewTextColor = sClippedTextColor + sNewBackgroundColor = sClippedBackgroundColor + end + if nEnd < width then + local nOldStart = nEnd + 1 + sNewText = sNewText .. sub(sOldText, nOldStart, width) + sNewTextColor = sNewTextColor .. sub(sOldTextColor, nOldStart, width) + sNewBackgroundColor = sNewBackgroundColor .. sub(sOldBackgroundColor, nOldStart, width) + end + + cacheT[yCursor] = sNewText + cacheFG[yCursor] = sNewTextColor + cacheBG[yCursor] = sNewBackgroundColor + end + object:updateDraw() + end + xCursor = nEnd + 1 + if (visible) then + updateCursor() + end + end + end + + local function setText(_x, _y, text) + if (text ~= nil) then + local gText = cacheT[_y] + if (gText ~= nil) then + cacheT[_y] = sub(gText:sub(1, _x - 1) .. text .. gText:sub(_x + (text:len()), width), 1, width) + end + end + object:updateDraw() + end + + local function setBG(_x, _y, colorStr) + if (colorStr ~= nil) then + local gBG = cacheBG[_y] + if (gBG ~= nil) then + cacheBG[_y] = sub(gBG:sub(1, _x - 1) .. colorStr .. gBG:sub(_x + (colorStr:len()), width), 1, width) + end + end + object:updateDraw() + end + + local function setFG(_x, _y, colorStr) + if (colorStr ~= nil) then + local gFG = cacheFG[_y] + if (gFG ~= nil) then + cacheFG[_y] = sub(gFG:sub(1, _x - 1) .. colorStr .. gFG:sub(_x + (colorStr:len()), width), 1, width) + end + end + object:updateDraw() + end + + local setTextColor = function(color) + if type(color) ~= "number" then + error("bad argument #1 (expected number, got " .. type(color) .. ")", 2) + elseif tHex[color] == nil then + error("Invalid color (got " .. color .. ")", 2) + end + fgColor = color + end + + local setBackgroundColor = function(color) + if type(color) ~= "number" then + error("bad argument #1 (expected number, got " .. type(color) .. ")", 2) + elseif tHex[color] == nil then + error("Invalid color (got " .. color .. ")", 2) + end + bgColor = color + end + + local setPaletteColor = function(colour, r, g, b) + -- have to work on + if type(colour) ~= "number" then + error("bad argument #1 (expected number, got " .. type(colour) .. ")", 2) + end + + if tHex[colour] == nil then + error("Invalid color (got " .. colour .. ")", 2) + end + + local tCol + if type(r) == "number" and g == nil and b == nil then + tCol = { colours.rgb8(r) } + tPalette[colour] = tCol + else + if type(r) ~= "number" then + error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) + end + if type(g) ~= "number" then + error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) + end + if type(b) ~= "number" then + error("bad argument #4 (expected number, got " .. type(b) .. ")", 2) + end + + tCol = tPalette[colour] + tCol[1] = r + tCol[2] = g + tCol[3] = b + end + end + + local getPaletteColor = function(colour) + if type(colour) ~= "number" then + error("bad argument #1 (expected number, got " .. type(colour) .. ")", 2) + end + if tHex[colour] == nil then + error("Invalid color (got " .. colour .. ")", 2) + end + local tCol = tPalette[colour] + return tCol[1], tCol[2], tCol[3] + end + + local basaltwindow = { + setCursorPos = function(_x, _y) + if type(_x) ~= "number" then + error("bad argument #1 (expected number, got " .. type(_x) .. ")", 2) + end + if type(_y) ~= "number" then + error("bad argument #2 (expected number, got " .. type(_y) .. ")", 2) + end + xCursor = math.floor(_x) + yCursor = math.floor(_y) + if (visible) then + updateCursor() + end + end; + + getCursorPos = function() + return xCursor, yCursor + end; + + setCursorBlink = function(blink) + if type(blink) ~= "boolean" then + error("bad argument #1 (expected boolean, got " .. type(blink) .. ")", 2) + end + cursorBlink = blink + end; + + getCursorBlink = function() + return cursorBlink + end; + + + getPaletteColor = getPaletteColor, + getPaletteColour = getPaletteColor, + + setBackgroundColor = setBackgroundColor, + setBackgroundColour = setBackgroundColor, + + setTextColor = setTextColor, + setTextColour = setTextColor, + + setPaletteColor = setPaletteColor, + setPaletteColour = setPaletteColor, + + getBackgroundColor = function() + return bgColor + end; + getBackgroundColour = function() + return bgColor + end; + + getSize = function() + return width, height + end; + + getTextColor = function() + return fgColor + end; + getTextColour = function() + return fgColor + end; + + basalt_resize = function(_width, _height) + width, height = _width, _height + recreateWindowArray() + end; + + basalt_reposition = function(_x, _y) + x, y = _x, _y + end; + + basalt_setVisible = function(vis) + visible = vis + end; + + drawBackgroundBox = function(_x, _y, _width, _height, bgCol) + for n = 1, _height do + setBG(_x, _y + (n - 1), tHex[bgCol]:rep(_width)) + end + end; + drawForegroundBox = function(_x, _y, _width, _height, fgCol) + for n = 1, _height do + setFG(_x, _y + (n - 1), tHex[fgCol]:rep(_width)) + end + end; + drawTextBox = function(_x, _y, _width, _height, symbol) + for n = 1, _height do + setText(_x, _y + (n - 1), symbol:rep(_width)) + end + end; + + writeText = function(_x, _y, text, bgCol, fgCol) + bgCol = bgCol or bgColor + fgCol = fgCol or fgColor + setText(x, _y, text) + setBG(_x, _y, tHex[bgCol]:rep(text:len())) + setFG(_x, _y, tHex[fgCol]:rep(text:len())) + end; + + basalt_update = function() + if (parent ~= nil) then + for n = 1, height do + parent:setText(x, y + (n - 1), cacheT[n]) + parent:setBG(x, y + (n - 1), cacheBG[n]) + parent:setFG(x, y + (n - 1), cacheFG[n]) + end + end + end; + + scroll = function(offset) + if type(offset) ~= "number" then + error("bad argument #1 (expected number, got " .. type(offset) .. ")", 2) + end + if offset ~= 0 then + local sEmptyText = emptySpaceLine + local sEmptyTextColor = emptyColorLines[fgColor] + local sEmptyBackgroundColor = emptyColorLines[bgColor] + for newY = 1, height do + local y = newY + offset + if y >= 1 and y <= height then + cacheT[newY] = cacheT[y] + cacheBG[newY] = cacheBG[y] + cacheFG[newY] = cacheFG[y] + else + cacheT[newY] = sEmptyText + cacheFG[newY] = sEmptyTextColor + cacheBG[newY] = sEmptyBackgroundColor + end + end + end + if (visible) then + updateCursor() + end + end; + + + isColor = function() + return parent:getBasaltInstance().getBaseTerm().isColor() + end; + + isColour = function() + return parent:getBasaltInstance().getBaseTerm().isColor() + end; + + write = function(text) + text = tostring(text) + if (visible) then + internalBlit(text, tHex[fgColor]:rep(text:len()), tHex[bgColor]:rep(text:len())) + end + end; + + clearLine = function() + if (visible) then + setText(1, yCursor, (" "):rep(width)) + setBG(1, yCursor, tHex[bgColor]:rep(width)) + setFG(1, yCursor, tHex[fgColor]:rep(width)) + end + if (visible) then + updateCursor() + end + end; + + clear = function() + for n = 1, height do + setText(1, n, (" "):rep(width)) + setBG(1, n, tHex[bgColor]:rep(width)) + setFG(1, n, tHex[fgColor]:rep(width)) + end + if (visible) then + updateCursor() + end + end; + + blit = function(text, fgcol, bgcol) + if type(text) ~= "string" then + error("bad argument #1 (expected string, got " .. type(text) .. ")", 2) + end + if type(fgcol) ~= "string" then + error("bad argument #2 (expected string, got " .. type(fgcol) .. ")", 2) + end + if type(bgcol) ~= "string" then + error("bad argument #3 (expected string, got " .. type(bgcol) .. ")", 2) + end + if #fgcol ~= #text or #bgcol ~= #text then + error("Arguments must be the same length", 2) + end + if (visible) then + internalBlit(text, fgcol, bgcol) + end + end + + + } + + return basaltwindow + end + + base.width = 30 + base.height = 12 + local pWindow = createBasaltWindow(1, 1, base.width, base.height) + local curProcess + local paused = false + local queuedEvent = {} + + local function updateCursor(self) + local xCur, yCur = pWindow.getCursorPos() + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then + self.parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) + end + end + + local function mouseEvent(self, event, p1, x, y) + if (curProcess == nil) then + return false + end + if not (curProcess:isDead()) then + if not (paused) then + local absX, absY = self:getAbsolutePosition(self:getAnchorPosition(nil, nil, true)) + curProcess:resume(event, p1, x-absX+1, y-absY+1) + updateCursor(self) + end + end + end + + local function keyEvent(self, event, key, isHolding) + if (curProcess == nil) then + return false + end + if not (curProcess:isDead()) then + if not (paused) then + if (self.draw) then + curProcess:resume(event, key, isHolding) + updateCursor(self) + end + end + end + end + + object = { + getType = function(self) + return objectType + end; + + show = function(self) + base.show(self) + pWindow.setBackgroundColor(self.bgColor) + pWindow.setTextColor(self.fgColor) + pWindow.basalt_setVisible(true) + return self + end; + + hide = function(self) + base.hide(self) + pWindow.basalt_setVisible(false) + return self + end; + + setPosition = function(self, x, y, rel) + base.setPosition(self, x, y, rel) + pWindow.basalt_reposition(self:getAnchorPosition()) + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("path", data)~=nil)then cachedPath = xmlValue("path", data) end + if(xmlValue("execute", data)~=nil)then if(xmlValue("execute", data))then if(cachedPath~=nil)then self:execute(cachedPath) end end end + end, + + getBasaltWindow = function() + return pWindow + end; + + getBasaltProcess = function() + return curProcess + end; + + setSize = function(self, width, height, rel) + base.setSize(self, width, height, rel) + pWindow.basalt_resize(self:getWidth(), self:getHeight()) + return self + end; + + getStatus = function(self) + if (curProcess ~= nil) then + return curProcess:getStatus() + end + return "inactive" + end; + + execute = function(self, path, ...) + cachedPath = path or cachedPath + curProcess = process:new(cachedPath, pWindow, ...) + pWindow.setBackgroundColor(colors.black) + pWindow.setTextColor(colors.white) + pWindow.clear() + pWindow.setCursorPos(1, 1) + pWindow.setBackgroundColor(self.bgColor) + pWindow.setTextColor(self.fgColor) + pWindow.basalt_setVisible(true) + curProcess:resume() + paused = false + if(self.parent~=nil)then + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_up", self) + self.parent:addEvent("mouse_drag", self) + self.parent:addEvent("mouse_scroll", self) + self.parent:addEvent("key", self) + self.parent:addEvent("key_up", self) + self.parent:addEvent("char", self) + self.parent:addEvent("other_event", self) + end + return self + end; + + stop = function(self) + if (curProcess ~= nil) then + if not (curProcess:isDead()) then + curProcess:resume("terminate") + if (curProcess:isDead()) then + if (self.parent ~= nil) then + self.parent:setCursor(false) + end + end + end + end + self.parent:removeEvents(self) + return self + end; + + pause = function(self, p) + paused = p or (not paused) + if (curProcess ~= nil) then + if not (curProcess:isDead()) then + if not (paused) then + self:injectEvents(queuedEvent) + queuedEvent = {} + end + end + end + return self + end; + + isPaused = function(self) + return paused + end; + + injectEvent = function(self, event, p1, p2, p3, p4, ign) + if (curProcess ~= nil) then + if not (curProcess:isDead()) then + if (paused == false) or (ign) then + curProcess:resume(event, p1, p2, p3, p4) + else + table.insert(queuedEvent, { event = event, args = { p1, p2, p3, p4 } }) + end + end + end + return self + end; + + getQueuedEvents = function(self) + return queuedEvent + end; + + updateQueuedEvents = function(self, events) + queuedEvent = events or queuedEvent + return self + end; + + injectEvents = function(self, events) + if (curProcess ~= nil) then + if not (curProcess:isDead()) then + for _, value in pairs(events) do + curProcess:resume(value.event, table.unpack(value.args)) + end + end + end + return self + end; + + mouseHandler = function(self, button, x, y) + if (base.mouseHandler(self, button, x, y)) then + mouseEvent(self, "mouse_click", button, x, y) + return true + end + return false + end, + + mouseUpHandler = function(self, button, x, y) + if (base.mouseUpHandler(self, button, x, y)) then + mouseEvent(self, "mouse_up", button, x, y) + return true + end + return false + end, + + scrollHandler = function(self, dir, x, y) + if (base.scrollHandler(self, dir, x, y)) then + mouseEvent(self, "mouse_scroll", dir, x, y) + return true + end + return false + end, + + dragHandler = function(self, button, x, y) + if (base.dragHandler(self, button, x, y)) then + mouseEvent(self, "mouse_drag", button, x, y) + return true + end + return false + end, + + keyHandler = function(self, key, isHolding) + if(base.keyHandler(self, key, isHolding))then + keyEvent(self, "key", key, isHolding) + return true + end + return false + end, + + keyUpHandler = function(self, key) + if(base.keyUpHandler(self, key))then + keyEvent(self, "key_up", key) + return true + end + return false + end, + + charHandler = function(self, char) + if(base.charHandler(self, char))then + keyEvent(self, "char", char) + return true + end + return false + end, + + getFocusHandler = function(self) + base.getFocusHandler(self) + if (curProcess ~= nil) then + if not (curProcess:isDead()) then + if not (paused) then + if (self.parent ~= nil) then + local xCur, yCur = pWindow.getCursorPos() + local obx, oby = self:getAnchorPosition() + if (self.parent ~= nil) then + local w,h = self:getSize() + if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then + self.parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) + end + end + end + end + end + end + end, + + loseFocusHandler = function(self) + base.loseFocusHandler(self) + if (curProcess ~= nil) then + if not (curProcess:isDead()) then + if (self.parent ~= nil) then + self.parent:setCursor(false) + end + end + end + end, + + eventHandler = function(self, event, p1, p2, p3, p4) + if(base.eventHandler(self, event, p1, p2, p3, p4))then + if (curProcess == nil) then + return + end + if(event=="dynamicValueEvent")then + local w, h = pWindow.getSize() + local pW, pH = self:getSize() + if(w~=pW)or(h~=pH)then + pWindow.basalt_resize(pW, pH) + if not (curProcess:isDead()) then + curProcess:resume("term_resize") + end + end + pWindow.basalt_reposition(self:getAnchorPosition()) + + end + if not (curProcess:isDead()) then + if not (paused) then + if(event ~= "terminate") then + curProcess:resume(event, p1, p2, p3, p4) + end + if (self:isFocused()) then + local obx, oby = self:getAnchorPosition() + local xCur, yCur = pWindow.getCursorPos() + if (self.parent ~= nil) then + local w,h = self:getSize() + if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then + self.parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) + end + end + + if (event == "terminate") then + log(self:isFocused()) + curProcess:resume(event) + self.parent:setCursor(false) + return true + end + end + else + table.insert(queuedEvent, { event = event, args = { p1, p2, p3, p4 } }) + end + end + return false + end + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + pWindow.basalt_reposition(obx, oby) + pWindow.basalt_update() + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("ProgramBG") + end, + + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Progressbar.lua b/src/Basalt/objects/Progressbar.lua new file mode 100644 index 0000000..ab5974c --- /dev/null +++ b/src/Basalt/objects/Progressbar.lua @@ -0,0 +1,120 @@ +local Object = require("Object") +local xmlValue = require("utils").getValueFromXML + +return function(name) + -- Checkbox + local base = Object(name) + local objectType = "Progressbar" + + local progress = 0 + + base:setZIndex(5) + base:setValue(false) + base.width = 25 + base.height = 1 + + local activeBarColor + local activeBarSymbol = "" + local activeBarSymbolCol = colors.white + local bgBarSymbol = "" + local direction = 0 + + local object = { + init = function(self) + self.bgColor = self.parent:getTheme("ProgressbarBG") + self.fgColor = self.parent:getTheme("ProgressbarText") + activeBarColor = self.parent:getTheme("ProgressbarActiveBG") + end, + getType = function(self) + return objectType + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("direction", data)~=nil)then direction = xmlValue("direction", data) end + if(xmlValue("progressColor", data)~=nil)then activeBarColor = colors[xmlValue("progressColor", data)] end + if(xmlValue("progressSymbol", data)~=nil)then activeBarSymbol = xmlValue("progressSymbol", data) end + if(xmlValue("backgroundSymbol", data)~=nil)then bgBarSymbol = xmlValue("backgroundSymbol", data) end + if(xmlValue("progressSymbolColor", data)~=nil)then activeBarSymbolCol = colors[xmlValue("progressSymbolColor", data)] end + if(xmlValue("onDone", data)~=nil)then self:generateXMLEventFunction(self.onProgressDone, xmlValue("onDone", data)) end + return self + end, + + setDirection = function(self, dir) + direction = dir + self:updateDraw() + return self + end; + + setProgressBar = function(self, color, symbol, symbolcolor) + activeBarColor = color or activeBarColor + activeBarSymbol = symbol or activeBarSymbol + activeBarSymbolCol = symbolcolor or activeBarSymbolCol + self:updateDraw() + return self + end; + + setBackgroundSymbol = function(self, symbol) + bgBarSymbol = symbol:sub(1, 1) + self:updateDraw() + return self + end; + + setProgress = function(self, value) + if (value >= 0) and (value <= 100) and (progress ~= value) then + progress = value + self:setValue(progress) + if (progress == 100) then + self:progressDoneHandler() + end + end + self:updateDraw() + return self + end; + + getProgress = function(self) + return progress + end; + + onProgressDone = function(self, f) + self:registerEvent("progress_done", f) + return self + end; + + progressDoneHandler = function(self) + self:sendEvent("progress_done", self) + end; + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if(self.bgColor~=false)then self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) end + if(bgBarSymbol~="")then self.parent:drawTextBox(obx, oby, w, h, bgBarSymbol) end + if(self.fgColor~=false)then self.parent:drawForegroundBox(obx, oby, w, h, self.fgColor) end + if (direction == 1) then + self.parent:drawBackgroundBox(obx, oby, w, h / 100 * progress, activeBarColor) + self.parent:drawForegroundBox(obx, oby, w, h / 100 * progress, activeBarSymbolCol) + self.parent:drawTextBox(obx, oby, w, h / 100 * progress, activeBarSymbol) + elseif (direction == 2) then + self.parent:drawBackgroundBox(obx, oby + math.ceil(h - h / 100 * progress), w, h / 100 * progress, activeBarColor) + self.parent:drawForegroundBox(obx, oby + math.ceil(h - h / 100 * progress), w, h / 100 * progress, activeBarSymbolCol) + self.parent:drawTextBox(obx, oby + math.ceil(h - h / 100 * progress), w, h / 100 * progress, activeBarSymbol) + elseif (direction == 3) then + self.parent:drawBackgroundBox(obx + math.ceil(w - w / 100 * progress), oby, w / 100 * progress, h, activeBarColor) + self.parent:drawForegroundBox(obx + math.ceil(w - w / 100 * progress), oby, w / 100 * progress, h, activeBarSymbolCol) + self.parent:drawTextBox(obx + math.ceil(w - w / 100 * progress), oby, w / 100 * progress, h, activeBarSymbol) + else + self.parent:drawBackgroundBox(obx, oby, w / 100 * progress, h, activeBarColor) + self.parent:drawForegroundBox(obx, oby, w / 100 * progress, h, activeBarSymbolCol) + self.parent:drawTextBox(obx, oby, w / 100 * progress, h, activeBarSymbol) + end + end + end + end, + + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Radio.lua b/src/Basalt/objects/Radio.lua new file mode 100644 index 0000000..82854b1 --- /dev/null +++ b/src/Basalt/objects/Radio.lua @@ -0,0 +1,167 @@ +local Object = require("Object") +local utils = require("utils") +local xmlValue = utils.getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "Radio" + base.width = 8 + base:setZIndex(5) + + local list = {} + local itemSelectedBG + local itemSelectedFG + local boxSelectedBG + local boxSelectedFG + local boxNotSelectedBG + local boxNotSelectedFG + local selectionColorActive = true + local symbol = "\7" + local align = "left" + + local object = { + getType = function(self) + return objectType + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("selectionBG", data)~=nil)then itemSelectedBG = colors[xmlValue("selectionBG", data)] end + if(xmlValue("selectionFG", data)~=nil)then itemSelectedFG = colors[xmlValue("selectionFG", data)] end + if(xmlValue("boxBG", data)~=nil)then boxSelectedBG = colors[xmlValue("boxBG", data)] end + if(xmlValue("inactiveBoxBG", data)~=nil)then boxNotSelectedBG = colors[xmlValue("inactiveBoxBG", data)] end + if(xmlValue("inactiveBoxFG", data)~=nil)then boxNotSelectedFG = colors[xmlValue("inactiveBoxFG", data)] end + if(xmlValue("boxFG", data)~=nil)then boxSelectedFG = colors[xmlValue("boxFG", data)] end + if(xmlValue("symbol", data)~=nil)then symbol = xmlValue("symbol", data) end + if(data["item"]~=nil)then + local tab = data["item"] + if(tab.properties~=nil)then tab = {tab} end + for k,v in pairs(tab)do + self:addItem(xmlValue("text", v), xmlValue("x", v), xmlValue("y", v), colors[xmlValue("bg", v)], colors[xmlValue("fg", v)]) + end + end + return self + end, + + addItem = function(self, text, x, y, bgCol, fgCol, ...) + table.insert(list, { x = x or 1, y = y or 1, text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + if (#list == 1) then + self:setValue(list[1]) + end + self:updateDraw() + return self + end; + + getAll = function(self) + return list + end; + + removeItem = function(self, index) + table.remove(list, index) + self:updateDraw() + return self + end; + + getItem = function(self, index) + return list[index] + end; + + getItemIndex = function(self) + local selected = self:getValue() + for key, value in pairs(list) do + if (value == selected) then + return key + end + end + end; + + clear = function(self) + list = {} + self:setValue({}) + self:updateDraw() + return self + end; + + getItemCount = function(self) + return #list + end; + + editItem = function(self, index, text, x, y, bgCol, fgCol, ...) + table.remove(list, index) + table.insert(list, index, { x = x or 1, y = y or 1, text = text, bgCol = bgCol or self.bgColor, fgCol = fgCol or self.fgColor, args = { ... } }) + self:updateDraw() + return self + end; + + selectItem = function(self, index) + self:setValue(list[index] or {}) + self:updateDraw() + return self + end; + + setActiveSymbol = function(self, sym) + symbol = sym:sub(1,1) + self:updateDraw() + return self + end, + + setSelectedItem = function(self, bgCol, fgCol, boxBG, boxFG, active) + itemSelectedBG = bgCol or itemSelectedBG + itemSelectedFG = fgCol or itemSelectedFG + boxSelectedBG = boxBG or boxSelectedBG + boxSelectedFG = boxFG or boxSelectedFG + selectionColorActive = active~=nil and active or true + self:updateDraw() + return self + end; + + mouseHandler = function(self, button, x, y) + if (#list > 0) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + for _, value in pairs(list) do + if (obx + value.x - 1 <= x) and (obx + value.x - 1 + value.text:len() + 1 >= x) and (oby + value.y - 1 == y) then + self:setValue(value) + local val = self:getEventSystem():sendEvent("mouse_click", self, "mouse_click", button, x, y) + if(val==false)then return val end + if(self.parent~=nil)then + self.parent:setFocusedObject(self) + end + self:updateDraw() + return true + end + end + end + return false + end; + + draw = function(self) + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + for _, value in pairs(list) do + if (value == self:getValue()) then + if (align == "left") then + self.parent:writeText(value.x + obx - 1, value.y + oby - 1, symbol, boxSelectedBG, boxSelectedFG) + self.parent:writeText(value.x + 2 + obx - 1, value.y + oby - 1, value.text, itemSelectedBG, itemSelectedFG) + end + else + self.parent:drawBackgroundBox(value.x + obx - 1, value.y + oby - 1, 1, 1, boxNotSelectedBG or self.bgColor) + self.parent:writeText(value.x + 2 + obx - 1, value.y + oby - 1, value.text, value.bgCol, value.fgCol) + end + end + return true + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("MenubarBG") + self.fgColor = self.parent:getTheme("MenubarFG") + itemSelectedBG = self.parent:getTheme("SelectionBG") + itemSelectedFG = self.parent:getTheme("SelectionText") + boxSelectedBG = self.parent:getTheme("MenubarBG") + boxSelectedFG = self.parent:getTheme("MenubarText") + self.parent:addEvent("mouse_click", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Scrollbar.lua b/src/Basalt/objects/Scrollbar.lua new file mode 100644 index 0000000..dbe46ed --- /dev/null +++ b/src/Basalt/objects/Scrollbar.lua @@ -0,0 +1,186 @@ +local Object = require("Object") +local xmlValue = require("utils").getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "Scrollbar" + + base.width = 1 + base.height = 8 + base:setValue(1) + base:setZIndex(2) + + local barType = "vertical" + local symbol = " " + local symbolColor + local bgSymbol = "\127" + local maxValue = base.height + local index = 1 + local symbolSize = 1 + + local function mouseEvent(self, button, x, y) + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local w,h = self:getSize() + if (barType == "horizontal") then + for _index = 0, w do + if (obx + _index == x) and (oby <= y) and (oby + h > y) then + index = math.min(_index + 1, w - (symbolSize - 1)) + self:setValue(maxValue / w * (index)) + self:updateDraw() + end + end + end + if (barType == "vertical") then + for _index = 0, h do + if (oby + _index == y) and (obx <= x) and (obx + w > x) then + index = math.min(_index + 1, h - (symbolSize - 1)) + self:setValue(maxValue / h * (index)) + self:updateDraw() + end + end + end + end + + local object = { + getType = function(self) + return objectType + end; + + setSymbol = function(self, _symbol) + symbol = _symbol:sub(1, 1) + self:updateDraw() + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("maxValue", data)~=nil)then maxValue = xmlValue("maxValue", data) end + if(xmlValue("backgroundSymbol", data)~=nil)then bgSymbol = xmlValue("backgroundSymbol", data):sub(1,1) end + if(xmlValue("symbol", data)~=nil)then symbol = xmlValue("symbol", data):sub(1,1) end + if(xmlValue("barType", data)~=nil)then barType = xmlValue("barType", data):lower() end + if(xmlValue("symbolSize", data)~=nil)then self:setSymbolSize(xmlValue("symbolSize", data)) end + if(xmlValue("symbolColor", data)~=nil)then symbolColor = colors[xmlValue("symbolColor", data)] end + if(xmlValue("index", data)~=nil)then self:setIndex(xmlValue("index", data)) end + end, + + setIndex = function(self, _index) + index = _index + if (index < 1) then + index = 1 + end + local w,h = self:getSize() + index = math.min(index, (barType == "vertical" and h or w) - (symbolSize - 1)) + self:setValue(maxValue / (barType == "vertical" and h or w) * index) + self:updateDraw() + return self + end, + + getIndex = function(self) + return index + end, + + setSymbolSize = function(self, size) + symbolSize = tonumber(size) or 1 + local w,h = self:getSize() + if (barType == "vertical") then + self:setValue(index - 1 * (maxValue / (h - (symbolSize - 1))) - (maxValue / (h - (symbolSize - 1)))) + elseif (barType == "horizontal") then + self:setValue(index - 1 * (maxValue / (w - (symbolSize - 1))) - (maxValue / (w - (symbolSize - 1)))) + end + self:updateDraw() + return self + end; + + setMaxValue = function(self, val) + maxValue = val + self:updateDraw() + return self + end; + + setBackgroundSymbol = function(self, _bgSymbol) + bgSymbol = string.sub(_bgSymbol, 1, 1) + self:updateDraw() + return self + end; + + setSymbolColor = function(self, col) + symbolColor = col + self:updateDraw() + return self + end; + + setBarType = function(self, _typ) + barType = _typ:lower() + self:updateDraw() + return self + end; + + mouseHandler = function(self, button, x, y) + if (base.mouseHandler(self, button, x, y)) then + mouseEvent(self, button, x, y) + return true + end + return false + end, + + dragHandler = function(self, button, x, y) + if (base.dragHandler(self, button, x, y)) then + mouseEvent(self, button, x, y) + return true + end + return false + end, + + scrollHandler = function(self, dir, x, y) + if(base.scrollHandler(self, dir, x, y))then + local w,h = self:getSize() + index = index + dir + if (index < 1) then + index = 1 + end + index = math.min(index, (barType == "vertical" and h or w) - (symbolSize - 1)) + self:setValue(maxValue / (barType == "vertical" and h or w) * index) + self:updateDraw() + end + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if (barType == "horizontal") then + self.parent:writeText(obx, oby, bgSymbol:rep(index - 1), self.bgColor, self.fgColor) + self.parent:writeText(obx + index - 1, oby, symbol:rep(symbolSize), symbolColor, symbolColor) + self.parent:writeText(obx + index + symbolSize - 1, oby, bgSymbol:rep(w - (index + symbolSize - 1)), self.bgColor, self.fgColor) + end + + if (barType == "vertical") then + for n = 0, h - 1 do + if (index == n + 1) then + for curIndexOffset = 0, math.min(symbolSize - 1, h) do + self.parent:writeText(obx, oby + n + curIndexOffset, symbol, symbolColor, symbolColor) + end + else + if (n + 1 < index) or (n + 1 > index - 1 + symbolSize) then + self.parent:writeText(obx, oby + n, bgSymbol, self.bgColor, self.fgColor) + end + end + end + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("ScrollbarBG") + self.fgColor = self.parent:getTheme("ScrollbarText") + symbolColor = self.parent:getTheme("ScrollbarSymbolColor") + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_drag", self) + self.parent:addEvent("mouse_scroll", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Slider.lua b/src/Basalt/objects/Slider.lua new file mode 100644 index 0000000..4a71c6a --- /dev/null +++ b/src/Basalt/objects/Slider.lua @@ -0,0 +1,186 @@ +local Object = require("Object") +local log = require("basaltLogs") +local xmlValue = require("utils").getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "Slider" + + base.width = 8 + base.height = 1 + base:setValue(1) + + local barType = "horizontal" + local symbol = " " + local symbolColor + local bgSymbol = "\140" + local maxValue = base.width + local index = 1 + local symbolSize = 1 + + local function mouseEvent(self, button, x, y) + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local w,h = self:getSize() + if (barType == "horizontal") then + for _index = 0, w do + if (obx + _index == x) and (oby <= y) and (oby + h > y) then + index = math.min(_index + 1, w - (symbolSize - 1)) + self:setValue(maxValue / w * (index)) + self:updateDraw() + end + end + end + if (barType == "vertical") then + for _index = 0, h do + if (oby + _index == y) and (obx <= x) and (obx + w > x) then + index = math.min(_index + 1, h - (symbolSize - 1)) + self:setValue(maxValue / h * (index)) + self:updateDraw() + end + end + end + end + + local object = { + getType = function(self) + return objectType + end; + + setSymbol = function(self, _symbol) + symbol = _symbol:sub(1, 1) + self:updateDraw() + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("maxValue", data)~=nil)then maxValue = xmlValue("maxValue", data) end + if(xmlValue("backgroundSymbol", data)~=nil)then bgSymbol = xmlValue("backgroundSymbol", data):sub(1,1) end + if(xmlValue("barType", data)~=nil)then barType = xmlValue("barType", data):lower() end + if(xmlValue("symbol", data)~=nil)then symbol = xmlValue("symbol", data):sub(1,1) end + if(xmlValue("symbolSize", data)~=nil)then self:setSymbolSize(xmlValue("symbolSize", data)) end + if(xmlValue("symbolColor", data)~=nil)then symbolColor = colors[xmlValue("symbolColor", data)] end + if(xmlValue("index", data)~=nil)then self:setIndex(xmlValue("index", data)) end + end, + + setIndex = function(self, _index) + index = _index + if (index < 1) then + index = 1 + end + local w,h = self:getSize() + index = math.min(index, (barType == "vertical" and h or w) - (symbolSize - 1)) + self:setValue(maxValue / (barType == "vertical" and h or w) * index) + self:updateDraw() + return self + end, + + getIndex = function(self) + return index + end, + + setSymbolSize = function(self, size) + symbolSize = tonumber(size) or 1 + if (barType == "vertical") then + self:setValue(index - 1 * (maxValue / (h - (symbolSize - 1))) - (maxValue / (h - (symbolSize - 1)))) + elseif (barType == "horizontal") then + self:setValue(index - 1 * (maxValue / (w - (symbolSize - 1))) - (maxValue / (w - (symbolSize - 1)))) + end + self:updateDraw() + return self + end; + + setMaxValue = function(self, val) + maxValue = val + return self + end; + + setBackgroundSymbol = function(self, _bgSymbol) + bgSymbol = string.sub(_bgSymbol, 1, 1) + self:updateDraw() + return self + end; + + setSymbolColor = function(self, col) + symbolColor = col + self:updateDraw() + return self + end; + + setBarType = function(self, _typ) + barType = _typ:lower() + self:updateDraw() + return self + end; + + mouseHandler = function(self, button, x, y) + if (base.mouseHandler(self, button, x, y)) then + mouseEvent(self, button, x, y) + return true + end + return false + end, + + dragHandler = function(self, button, x, y) + if (base.dragHandler(self, button, x, y)) then + mouseEvent(self, button, x, y) + return true + end + return false + end, + + scrollHandler = function(self, dir, x, y) + if(base.scrollHandler(self, dir, x, y))then + local w,h = self:getSize() + index = index + dir + if (index < 1) then + index = 1 + end + index = math.min(index, (barType == "vertical" and h or w) - (symbolSize - 1)) + self:setValue(maxValue / (barType == "vertical" and h or w) * index) + self:updateDraw() + return true + end + return false + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if (barType == "horizontal") then + self.parent:writeText(obx, oby, bgSymbol:rep(index - 1), self.bgColor, self.fgColor) + self.parent:writeText(obx + index - 1, oby, symbol:rep(symbolSize), symbolColor, symbolColor) + self.parent:writeText(obx + index + symbolSize - 1, oby, bgSymbol:rep(w - (index + symbolSize - 1)), self.bgColor, self.fgColor) + end + + if (barType == "vertical") then + for n = 0, h - 1 do + if (index == n + 1) then + for curIndexOffset = 0, math.min(symbolSize - 1, h) do + self.parent:writeText(obx, oby + n + curIndexOffset, symbol, symbolColor, symbolColor) + end + else + if (n + 1 < index) or (n + 1 > index - 1 + symbolSize) then + self.parent:writeText(obx, oby + n, bgSymbol, self.bgColor, self.fgColor) + end + end + end + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("SliderBG") + self.fgColor = self.parent:getTheme("SliderText") + symbolColor = self.parent:getTheme("SliderSymbolColor") + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_drag", self) + self.parent:addEvent("mouse_scroll", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Switch.lua b/src/Basalt/objects/Switch.lua new file mode 100644 index 0000000..8eb4603 --- /dev/null +++ b/src/Basalt/objects/Switch.lua @@ -0,0 +1,87 @@ +local Object = require("Object") +local xmlValue = require("utils").getValueFromXML + +return function(name) + local base = Object(name) + local objectType = "Switch" + + base.width = 2 + base.height = 1 + base.bgColor = colors.lightGray + base.fgColor = colors.gray + base:setValue(false) + base:setZIndex(5) + + local bgSymbol = colors.black + local inactiveBG = colors.red + local activeBG = colors.green + + local object = { + getType = function(self) + return objectType + end; + + setSymbolColor = function(self, symbolColor) + bgSymbol = symbolColor + self:updateDraw() + return self + end; + + setActiveBackground = function(self, bgcol) + activeBG = bgcol + self:updateDraw() + return self + end; + + setInactiveBackground = function(self, bgcol) + inactiveBG = bgcol + self:updateDraw() + return self + end; + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(xmlValue("inactiveBG", data)~=nil)then inactiveBG = colors[xmlValue("inactiveBG", data)] end + if(xmlValue("activeBG", data)~=nil)then activeBG = colors[xmlValue("activeBG", data)] end + if(xmlValue("symbolColor", data)~=nil)then bgSymbol = colors[xmlValue("symbolColor", data)] end + + end, + + mouseHandler = function(self, button, x, y) + if (base.mouseHandler(self, button, x, y)) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + self:setValue(not self:getValue()) + self:updateDraw() + return true + end + end; + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + self.parent:drawBackgroundBox(obx, oby, w, h, self.bgColor) + if(self:getValue())then + self.parent:drawBackgroundBox(obx, oby, 1, h, activeBG) + self.parent:drawBackgroundBox(obx+1, oby, 1, h, bgSymbol) + else + self.parent:drawBackgroundBox(obx, oby, 1, h, bgSymbol) + self.parent:drawBackgroundBox(obx+1, oby, 1, h, inactiveBG) + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("SwitchBG") + self.fgColor = self.parent:getTheme("SwitchText") + bgSymbol = self.parent:getTheme("SwitchBGSymbol") + inactiveBG = self.parent:getTheme("SwitchInactive") + activeBG = self.parent:getTheme("SwitchActive") + self.parent:addEvent("mouse_click", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Textfield.lua b/src/Basalt/objects/Textfield.lua new file mode 100644 index 0000000..2516e01 --- /dev/null +++ b/src/Basalt/objects/Textfield.lua @@ -0,0 +1,620 @@ +local Object = require("Object") +local tHex = require("tHex") +local log = require("basaltLogs") +local xmlValue = require("utils").getValueFromXML + +local rep = string.rep + +return function(name) + local base = Object(name) + local objectType = "Textfield" + local hIndex, wIndex, textX, textY = 1, 1, 1, 1 + + local lines = { "" } + local bgLines = { "" } + local fgLines = { "" } + local keyWords = { } + local rules = { } + + base.width = 30 + base.height = 12 + base:setZIndex(5) + + local function stringGetPositions(str, word) + local pos = {} + if(str:len()>0)then + for w in string.gmatch(str, word)do + local s, e = string.find(str, w) + if(s~=nil)and(e~=nil)then + table.insert(pos,s) + table.insert(pos,e) + local startL = string.sub(str, 1, (s-1)) + local endL = string.sub(str, e+1, str:len()) + str = startL..(":"):rep(w:len())..endL + end + end + end + return pos + end + + local function updateColors(self, l) + l = l or textY + local fgLine = tHex[self.fgColor]:rep(fgLines[l]:len()) + local bgLine = tHex[self.bgColor]:rep(bgLines[l]:len()) + for k,v in pairs(rules)do + local pos = stringGetPositions(lines[l], v[1]) + if(#pos>0)then + for x=1,#pos/2 do + local xP = x*2 - 1 + if(v[2]~=nil)then + fgLine = fgLine:sub(1, pos[xP]-1)..tHex[v[2]]:rep(pos[xP+1]-(pos[xP]-1))..fgLine:sub(pos[xP+1]+1, fgLine:len()) + end + if(v[3]~=nil)then + bgLine = bgLine:sub(1, pos[xP]-1)..tHex[v[3]]:rep(pos[xP+1]-(pos[xP]-1))..bgLine:sub(pos[xP+1]+1, bgLine:len()) + end + end + end + end + for k,v in pairs(keyWords)do + for _,b in pairs(v)do + local pos = stringGetPositions(lines[l], b) + if(#pos>0)then + for x=1,#pos/2 do + local xP = x*2 - 1 + fgLine = fgLine:sub(1, pos[xP]-1)..tHex[k]:rep(pos[xP+1]-(pos[xP]-1))..fgLine:sub(pos[xP+1]+1, fgLine:len()) + end + end + end + end + fgLines[l] = fgLine + bgLines[l] = bgLine + self:updateDraw() + end + + local function updateAllColors(self) + for n=1,#lines do + updateColors(self, n) + end + end + + local object = { + getType = function(self) + return objectType + end; + + setBackground = function(self, bg) + base.setBackground(self, bg) + updateAllColors(self) + return self + end, + + setForeground = function(self, fg) + base.setForeground(self, fg) + updateAllColors(self) + return self + end, + + setValuesByXMLData = function(self, data) + base.setValuesByXMLData(self, data) + if(data["lines"]~=nil)then + local l = data["lines"]["line"] + if(l.properties~=nil)then l = {l} end + for k,v in pairs(l)do + self:addLine(v:value()) + end + end + if(data["keywords"]~=nil)then + for k,v in pairs(data["keywords"])do + if(colors[k]~=nil)then + local entry = v + if(entry.properties~=nil)then entry = {entry} end + local tab = {} + for a,b in pairs(entry)do + local keywordList = b["keyword"] + if(b["keyword"].properties~=nil)then keywordList = {b["keyword"]} end + for c,d in pairs(keywordList)do + table.insert(tab, d:value()) + end + end + self:addKeywords(colors[k], tab) + end + end + end + if(data["rules"]~=nil)then + if(data["rules"]["rule"]~=nil)then + local tab = data["rules"]["rule"] + if(data["rules"]["rule"].properties~=nil)then tab = {data["rules"]["rule"]} end + for k,v in pairs(tab)do + + if(xmlValue("pattern", v)~=nil)then + self:addRule(xmlValue("pattern", v), colors[xmlValue("fg", v)], colors[xmlValue("bg", v)]) + end + end + end + end + end, + + getLines = function(self) + return lines + end; + + getLine = function(self, index) + return lines[index] + end; + + editLine = function(self, index, text) + lines[index] = text or lines[index] + self:updateDraw() + return self + end; + + clear = function(self) + lines = {""} + bgLines = {""} + fgLines = {""} + hIndex, wIndex, textX, textY = 1, 1, 1, 1 + self:updateDraw() + return self + end, + + addLine = function(self, text, index) + if(text~=nil)then + if(#lines==1)and(lines[1]=="")then + lines[1] = text + bgLines[1] = tHex[self.bgColor]:rep(text:len()) + fgLines[1] = tHex[self.fgColor]:rep(text:len()) + return self + end + if (index ~= nil) then + table.insert(lines, index, text) + table.insert(bgLines, index, tHex[self.bgColor]:rep(text:len())) + table.insert(fgLines, tHex[self.fgColor]:rep(text:len())) + else + table.insert(lines, text) + table.insert(bgLines, tHex[self.bgColor]:rep(text:len())) + table.insert(fgLines, tHex[self.fgColor]:rep(text:len())) + end + end + self:updateDraw() + return self + end; + + addKeywords = function(self, color, tab) + if(keyWords[color]==nil)then + keyWords[color] = {} + end + for k,v in pairs(tab)do + table.insert(keyWords[color], v) + end + self:updateDraw() + return self + end; + + addRule = function(self, rule, fg, bg) + table.insert(rules, {rule, fg, bg}) + self:updateDraw() + return self + end; + + editRule = function(self, rule, fg, bg) + for k,v in pairs(rules)do + if(v[1]==rule)then + rules[k][2] = fg + rules[k][3] = bg + end + end + self:updateDraw() + return self + end; + + removeRule = function(self, rule) + for k,v in pairs(rules)do + if(v[1]==rule)then + table.remove(rules, k) + end + end + self:updateDraw() + return self + end; + + setKeywords = function(self, color, tab) + keyWords[color] = tab + self:updateDraw() + return self + end; + + removeLine = function(self, index) + table.remove(lines, index or #lines) + if (#lines <= 0) then + table.insert(lines, "") + end + self:updateDraw() + return self + end; + + getTextCursor = function(self) + return textX, textY + end; + + getFocusHandler = function(self) + base.getFocusHandler(self) + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + if (self.parent ~= nil) then + self.parent:setCursor(true, obx + textX - wIndex, oby + textY - hIndex, self.fgColor) + end + end + end; + + loseFocusHandler = function(self) + base.loseFocusHandler(self) + if (self.parent ~= nil) then + self.parent:setCursor(false) + end + end; + + keyHandler = function(self, key) + if (base.keyHandler(self, event, key)) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + if (key == keys.backspace) then + -- on backspace + if (lines[textY] == "") then + if (textY > 1) then + table.remove(lines, textY) + table.remove(fgLines, textY) + table.remove(bgLines, textY) + textX = lines[textY - 1]:len() + 1 + wIndex = textX - w + 1 + if (wIndex < 1) then + wIndex = 1 + end + textY = textY - 1 + end + elseif (textX <= 1) then + if (textY > 1) then + textX = lines[textY - 1]:len() + 1 + wIndex = textX - w + 1 + if (wIndex < 1) then + wIndex = 1 + end + lines[textY - 1] = lines[textY - 1] .. lines[textY] + fgLines[textY - 1] = fgLines[textY - 1] .. fgLines[textY] + bgLines[textY - 1] = bgLines[textY - 1] .. bgLines[textY] + table.remove(lines, textY) + table.remove(fgLines, textY) + table.remove(bgLines, textY) + textY = textY - 1 + end + else + lines[textY] = lines[textY]:sub(1, textX - 2) .. lines[textY]:sub(textX, lines[textY]:len()) + fgLines[textY] = fgLines[textY]:sub(1, textX - 2) .. fgLines[textY]:sub(textX, fgLines[textY]:len()) + bgLines[textY] = bgLines[textY]:sub(1, textX - 2) .. bgLines[textY]:sub(textX, bgLines[textY]:len()) + if (textX > 1) then + textX = textX - 1 + end + if (wIndex > 1) then + if (textX < wIndex) then + wIndex = wIndex - 1 + end + end + end + if (textY < hIndex) then + hIndex = hIndex - 1 + end + updateColors(self) + self:setValue("") + end + + if (key == keys.delete) then + -- on delete + if (textX > lines[textY]:len()) then + if (lines[textY + 1] ~= nil) then + lines[textY] = lines[textY] .. lines[textY + 1] + table.remove(lines, textY + 1) + table.remove(bgLines, textY + 1) + table.remove(fgLines, textY + 1) + end + else + lines[textY] = lines[textY]:sub(1, textX - 1) .. lines[textY]:sub(textX + 1, lines[textY]:len()) + fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. fgLines[textY]:sub(textX + 1, fgLines[textY]:len()) + bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. bgLines[textY]:sub(textX + 1, bgLines[textY]:len()) + end + updateColors(self) + end + + if (key == keys.enter) then + -- on enter + table.insert(lines, textY + 1, lines[textY]:sub(textX, lines[textY]:len())) + table.insert(fgLines, textY + 1, fgLines[textY]:sub(textX, fgLines[textY]:len())) + table.insert(bgLines, textY + 1, bgLines[textY]:sub(textX, bgLines[textY]:len())) + lines[textY] = lines[textY]:sub(1, textX - 1) + fgLines[textY] = fgLines[textY]:sub(1, textX - 1) + bgLines[textY] = bgLines[textY]:sub(1, textX - 1) + textY = textY + 1 + textX = 1 + wIndex = 1 + if (textY - hIndex >= h) then + hIndex = hIndex + 1 + end + self:setValue("") + end + + if (key == keys.up) then + -- arrow up + if (textY > 1) then + textY = textY - 1 + if (textX > lines[textY]:len() + 1) then + textX = lines[textY]:len() + 1 + end + if (wIndex > 1) then + if (textX < wIndex) then + wIndex = textX - w + 1 + if (wIndex < 1) then + wIndex = 1 + end + end + end + if (hIndex > 1) then + if (textY < hIndex) then + hIndex = hIndex - 1 + end + end + end + end + if (key == keys.down) then + -- arrow down + if (textY < #lines) then + textY = textY + 1 + if (textX > lines[textY]:len() + 1) then + textX = lines[textY]:len() + 1 + end + if (wIndex > 1) then + if (textX < wIndex) then + wIndex = textX - w + 1 + if (wIndex < 1) then + wIndex = 1 + end + end + end + if (textY >= hIndex + h) then + hIndex = hIndex + 1 + end + end + end + if (key == keys.right) then + -- arrow right + textX = textX + 1 + if (textY < #lines) then + if (textX > lines[textY]:len() + 1) then + textX = 1 + textY = textY + 1 + end + elseif (textX > lines[textY]:len()) then + textX = lines[textY]:len() + 1 + end + if (textX < 1) then + textX = 1 + end + if (textX < wIndex) or (textX >= w + wIndex) then + wIndex = textX - w + 1 + end + if (wIndex < 1) then + wIndex = 1 + end + + end + if (key == keys.left) then + -- arrow left + textX = textX - 1 + if (textX >= 1) then + if (textX < wIndex) or (textX >= w + wIndex) then + wIndex = textX + end + end + if (textY > 1) then + if (textX < 1) then + textY = textY - 1 + textX = lines[textY]:len() + 1 + wIndex = textX - w + 1 + end + end + if (textX < 1) then + textX = 1 + end + if (wIndex < 1) then + wIndex = 1 + end + end + + local cursorX = (textX <= lines[textY]:len() and textX - 1 or lines[textY]:len()) - (wIndex - 1) + if (cursorX > self.x + w - 1) then + cursorX = self.x + w - 1 + end + local cursorY = (textY - hIndex < h and textY - hIndex or textY - hIndex - 1) + if (cursorX < 1) then + cursorX = 0 + end + self.parent:setCursor(true, obx + cursorX, oby + cursorY, self.fgColor) + self:updateDraw() + return true + end + end, + + charHandler = function(self, char) + if(base.charHandler(self, char))then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + lines[textY] = lines[textY]:sub(1, textX - 1) .. char .. lines[textY]:sub(textX, lines[textY]:len()) + fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self.fgColor] .. fgLines[textY]:sub(textX, fgLines[textY]:len()) + bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self.bgColor] .. bgLines[textY]:sub(textX, bgLines[textY]:len()) + textX = textX + 1 + if (textX >= w + wIndex) then + wIndex = wIndex + 1 + end + updateColors(self) + self:setValue("") + + local cursorX = (textX <= lines[textY]:len() and textX - 1 or lines[textY]:len()) - (wIndex - 1) + if (cursorX > self.x + w - 1) then + cursorX = self.x + w - 1 + end + local cursorY = (textY - hIndex < h and textY - hIndex or textY - hIndex - 1) + if (cursorX < 1) then + cursorX = 0 + end + self.parent:setCursor(true, obx + cursorX, oby + cursorY, self.fgColor) + self:updateDraw() + return true + end + end, + + dragHandler = function(self, button, x, y) + if (base.dragHandler(self, button, x, y)) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local anchx, anchy = self:getAnchorPosition() + local w,h = self:getSize() + if (lines[y - oby + hIndex] ~= nil) then + if(anchx+w > anchx + x - (obx+1)+ wIndex)and(anchx < anchx + x - obx+ wIndex)then + textX = x - obx + wIndex + textY = y - oby + hIndex + if (textX > lines[textY]:len()) then + textX = lines[textY]:len() + 1 + end + if (textX < wIndex) then + wIndex = textX - 1 + if (wIndex < 1) then + wIndex = 1 + end + end + if (self.parent ~= nil) then + self.parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self.fgColor) + end + self:updateDraw() + end + end + return true + end + end, + + scrollHandler = function(self, dir, x, y) + if (base.scrollHandler(self, dir, x, y)) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local anchx, anchy = self:getAnchorPosition() + local w,h = self:getSize() + hIndex = hIndex + dir + if (hIndex > #lines - (h - 1)) then + hIndex = #lines - (h - 1) + end + + if (hIndex < 1) then + hIndex = 1 + end + + if (self.parent ~= nil) then + if (obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (oby + textY - hIndex >= oby and oby + textY - hIndex < oby + h) then + self.parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self.fgColor) + else + self.parent:setCursor(false) + end + end + self:updateDraw() + return true + end + end, + + mouseHandler = function(self, button, x, y) + if (base.mouseHandler(self, button, x, y)) then + local obx, oby = self:getAbsolutePosition(self:getAnchorPosition()) + local anchx, anchy = self:getAnchorPosition() + if (lines[y - oby + hIndex] ~= nil) then + textX = x - obx + wIndex + textY = y - oby + hIndex + if (textX > lines[textY]:len()) then + textX = lines[textY]:len() + 1 + end + if (textX < wIndex) then + wIndex = textX - 1 + if (wIndex < 1) then + wIndex = 1 + end + end + end + if (self.parent ~= nil) then + self.parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self.fgColor) + end + return true + end + end, + + eventHandler = function(self, event, paste, p2, p3, p4) + if(base.eventHandler(self, event, paste, p2, p3, p4))then + if(event=="paste")then + if(self:isFocused())then + local w, h = self:getSize() + lines[textY] = lines[textY]:sub(1, textX - 1) .. paste .. lines[textY]:sub(textX, lines[textY]:len()) + fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self.fgColor]:rep(paste:len()) .. fgLines[textY]:sub(textX, fgLines[textY]:len()) + bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self.bgColor]:rep(paste:len()) .. bgLines[textY]:sub(textX, bgLines[textY]:len()) + textX = textX + paste:len() + if (textX >= w + wIndex) then + wIndex = (textX+1)-w + end + local anchx, anchy = self:getAnchorPosition() + self.parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self.fgColor) + updateColors(self) + self:updateDraw() + end + end + end + end, + + draw = function(self) + if (base.draw(self)) then + if (self.parent ~= nil) then + local obx, oby = self:getAnchorPosition() + local w,h = self:getSize() + for n = 1, h do + local text = "" + local bg = "" + local fg = "" + if (lines[n + hIndex - 1] ~= nil) then + text = lines[n + hIndex - 1] + fg = fgLines[n + hIndex - 1] + bg = bgLines[n + hIndex - 1] + end + text = text:sub(wIndex, w + wIndex - 1) + bg = bg:sub(wIndex, w + wIndex - 1) + fg = fg:sub(wIndex, w + wIndex - 1) + local space = w - text:len() + if (space < 0) then + space = 0 + end + text = text .. rep(self.bgSymbol, space) + bg = bg .. rep(tHex[self.bgColor], space) + fg = fg .. rep(tHex[self.fgColor], space) + self.parent:setText(obx, oby + n - 1, text) + self.parent:setBG(obx, oby + n - 1, bg) + self.parent:setFG(obx, oby + n - 1, fg) + end + if(self:isFocused())then + local anchx, anchy = self:getAnchorPosition() + self.parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self.fgColor) + end + end + end + end, + + init = function(self) + self.bgColor = self.parent:getTheme("TextfieldBG") + self.fgColor = self.parent:getTheme("TextfieldText") + self.parent:addEvent("mouse_click", self) + self.parent:addEvent("mouse_scroll", self) + self.parent:addEvent("mouse_drag", self) + self.parent:addEvent("key", self) + self.parent:addEvent("char", self) + self.parent:addEvent("other_event", self) + end, + } + + return setmetatable(object, base) +end \ No newline at end of file diff --git a/src/Basalt/objects/Thread.lua b/src/Basalt/objects/Thread.lua new file mode 100644 index 0000000..8dd3f6f --- /dev/null +++ b/src/Basalt/objects/Thread.lua @@ -0,0 +1,99 @@ +local xmlValue = require("utils").getValueFromXML + +return function(name) + local object + local objectType = "Thread" + + local func + local cRoutine + local isActive = false + + local generateXMLEventFunction = function(self, str) + if(str:sub(1,1)=="#")then + local o = self:getBaseFrame():getDeepObject(str:sub(2,str:len())) + if(o~=nil)and(o.internalObjetCall~=nil)then + return (function()o:internalObjetCall()end) + end + else + return self:getBaseFrame():getVariable(str) + end + return self + end + + object = { + name = name, + getType = function(self) + return objectType + end; + getZIndex = function(self) + return 1 + end; + getName = function(self) + return self.name + end; + + getBaseFrame = function(self) + if(self.parent~=nil)then + return self.parent:getBaseFrame() + end + return self + end; + + setValuesByXMLData = function(self, data) + local f + if(xmlValue("thread", data)~=nil)then f = generateXMLEventFunction(self, xmlValue("thread", data)) end + if(xmlValue("start", data)~=nil)then if(xmlValue("start", data))and(f~=nil)then self:start(f) end end + return self + end, + + start = function(self, f) + if (f == nil) then + error("Function provided to thread is nil") + end + func = f + cRoutine = coroutine.create(func) + isActive = true + local ok, result = coroutine.resume(cRoutine) + if not (ok) then + if (result ~= "Terminated") then + error("Thread Error Occurred - " .. result) + end + end + self.parent:addEvent("other_event", self) + return self + end; + + getStatus = function(self, f) + if (cRoutine ~= nil) then + return coroutine.status(cRoutine) + end + return nil + end; + + stop = function(self, f) + isActive = false + self.parent:removeEvent("other_event", self) + return self + end; + + eventHandler = function(self, event, p1, p2, p3) + if (isActive) then + if (coroutine.status(cRoutine) ~= "dead") then + local ok, result = coroutine.resume(cRoutine, event, p1, p2, p3) + if not (ok) then + if (result ~= "Terminated") then + error("Thread Error Occurred - " .. result) + end + end + else + isActive = false + end + end + end; + + } + + object.__index = object + + return object +end \ No newline at end of file diff --git a/src/Basalt/objects/Timer.lua b/src/Basalt/objects/Timer.lua new file mode 100644 index 0000000..3e0c2a6 --- /dev/null +++ b/src/Basalt/objects/Timer.lua @@ -0,0 +1,116 @@ +local basaltEvent = require("basaltEvent") +local xmlValue = require("utils").getValueFromXML + +return function(name) + local objectType = "Timer" + + local timer = 0 + local savedRepeats = 0 + local repeats = 0 + local timerObj + local eventSystem = basaltEvent() + local timerIsActive = false + + local generateXMLEventFunction = function(self, func, val) + local createF = function(str) + if(str:sub(1,1)=="#")then + local o = self:getBaseFrame():getDeepObject(str:sub(2,str:len())) + if(o~=nil)and(o.internalObjetCall~=nil)then + func(self,function()o:internalObjetCall()end) + end + else + func(self,self:getBaseFrame():getVariable(str)) + end + end + if(type(val)=="string")then + createF(val) + elseif(type(val)=="table")then + for k,v in pairs(val)do + createF(v) + end + end + return self + end + + local object = { + name = name, + getType = function(self) + return objectType + end; + + setValuesByXMLData = function(self, data) + if(xmlValue("time", data)~=nil)then timer = xmlValue("time", data) end + if(xmlValue("repeat", data)~=nil)then savedRepeats = xmlValue("repeat", data) end + if(xmlValue("start", data)~=nil)then if(xmlValue("start", data))then self:start() end end + if(xmlValue("onCall", data)~=nil)then generateXMLEventFunction(self, self.onCall, xmlValue("onCall", data)) end + return self + end, + + getBaseFrame = function(self) + if(self.parent~=nil)then + return self.parent:getBaseFrame() + end + return self + end; + + getZIndex = function(self) + return 1 + end; + + getName = function(self) + return self.name + end; + + setTime = function(self, _timer, _repeats) + timer = _timer or 0 + savedRepeats = _repeats or 1 + return self + end; + + start = function(self) + if(timerIsActive)then + os.cancelTimer(timerObj) + end + repeats = savedRepeats + timerObj = os.startTimer(timer) + timerIsActive = true + self.parent:addEvent("other_event", self) + return self + end; + + isActive = function(self) + return timerIsActive + end; + + cancel = function(self) + if (timerObj ~= nil) then + os.cancelTimer(timerObj) + end + timerIsActive = false + self.parent:removeEvent("other_event", self) + return self + end; + + onCall = function(self, func) + eventSystem:registerEvent("timed_event", func) + return self + end; + + eventHandler = function(self, event, tObj) + if event == "timer" and tObj == timerObj and timerIsActive then + eventSystem:sendEvent("timed_event", self) + if (repeats >= 1) then + repeats = repeats - 1 + if (repeats >= 1) then + timerObj = os.startTimer(timer) + end + elseif (repeats == -1) then + timerObj = os.startTimer(timer) + end + end + end; + } + object.__index = object + + return object +end \ No newline at end of file diff --git a/src/Basalt/theme.lua b/src/Basalt/theme.lua new file mode 100644 index 0000000..39df3d2 --- /dev/null +++ b/src/Basalt/theme.lua @@ -0,0 +1,46 @@ +return { -- The default main theme for basalt! + Breakline = colors.white, + BasaltBG = colors.lightGray, + BasaltText = colors.black, + FrameBG = colors.gray, + FrameText = colors.black, + ButtonBG = colors.gray, + ButtonText = colors.black, + CheckboxBG = colors.gray, + CheckboxText = colors.black, + InputBG = colors.gray, + InputText = colors.black, + TextfieldBG = colors.gray, + TextfieldText = colors.black, + ListBG = colors.gray, + ListText = colors.black, + MenubarBG = colors.gray, + MenubarText = colors.black, + DropdownBG = colors.gray, + DropdownText = colors.black, + RadioBG = colors.gray, + RadioText = colors.black, + SelectionBG = colors.black, + SelectionText = colors.lightGray, + GraphicBG = colors.black, + ImageBG = colors.black, + PaneBG = colors.black, + ProgramBG = colors.black, + ProgressbarBG = colors.gray, + ProgressbarText = colors.black, + ProgressbarActiveBG = colors.black, + ScrollbarBG = colors.lightGray, + ScrollbarText = colors.gray, + ScrollbarSymbolColor = colors.black, + SliderBG = false, + SliderText = colors.gray, + SliderSymbolColor = colors.black, + SwitchBG = colors.lightGray, + SwitchText = colors.gray, + SwitchBGSymbol = colors.black, + SwitchInactive = colors.red, + SwitchActive = colors.green, + LabelBG = false, + LabelText = colors.black + +} \ No newline at end of file