Files
Basalt/Basalt/plugins/reactive.lua
2023-05-16 22:37:15 +10:00

414 lines
15 KiB
Lua

local XMLParser = require("xmlParser")
local utils = require("utils")
local uuid = utils.uuid
local function maybeExecuteScript(data, renderContext)
local script = XMLParser.XmlValue('script', data)
if (script ~= nil) then
load(script, nil, "t", renderContext.env)()
end
end
local function registerFunctionEvent(self, data, event, renderContext)
local eventEnv = renderContext.env
if(data:sub(1,1)=="$")then
local data = data:sub(2)
event(self, self:getBasalt().getVariable(data))
else
event(self, function(...)
eventEnv.event = {...}
local success, msg = pcall(load(data, nil, "t", eventEnv))
if not success then
error("XML Error: "..msg)
end
end)
end
end
local function registerFunctionEvents(self, data, events, renderContext)
for _, event in pairs(events) do
local expression = data.attributes[event]
if (expression ~= nil) then
registerFunctionEvent(self, expression .. "()", self[event], renderContext)
end
end
end
local currentEffect = nil
local clearEffectDependencies = function(effect)
for _, dependency in ipairs(effect.dependencies) do
for index, backlink in ipairs(dependency) do
if (backlink == effect) then
table.remove(dependency, index)
end
end
end
effect.dependencies = {};
end
return {
basalt = function(basalt)
local object = {
layout = function(path)
return {
path = path,
}
end,
reactive = function(initialValue)
local value = initialValue
local observerEffects = {}
local get = function()
if (currentEffect ~= nil) then
table.insert(observerEffects, currentEffect)
table.insert(currentEffect.dependencies, observerEffects)
end
return value
end
local set = function(newValue)
value = newValue
local observerEffectsCopy = {}
for index, effect in ipairs(observerEffects) do
observerEffectsCopy[index] = effect
end
for _, effect in ipairs(observerEffectsCopy) do
effect.execute()
end
end
return get, set
end,
untracked = function(getter)
local parentEffect = currentEffect
currentEffect = nil
local value = getter()
currentEffect = parentEffect
return value
end,
effect = function(effectFn)
local effect = {dependencies = {}}
local execute = function()
clearEffectDependencies(effect)
local parentEffect = currentEffect
currentEffect = effect
effectFn()
currentEffect = parentEffect
end
effect.execute = execute
effect.execute()
end,
derived = function(computeFn)
local getValue, setValue = basalt.reactive();
basalt.effect(function()
setValue(computeFn())
end)
return getValue;
end
}
return object
end,
VisualObject = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
renderContext.env[self:getName()] = self
for attribute, expression in pairs(data.attributes) do
local update = function()
local value = load("return " .. expression, nil, "t", renderContext.env)()
self:setProperty(attribute, value)
end
basalt.effect(update)
end
registerFunctionEvents(self, data, {
"onClick",
"onClickUp",
"onHover",
"onScroll",
"onDrag",
"onKey",
"onKeyUp",
"onRelease",
"onChar",
"onGetFocus",
"onLoseFocus",
"onResize",
"onReposition",
"onEvent",
"onLeave"
}, renderContext)
return self
end,
}
return object
end,
ChangeableObject = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
registerFunctionEvent(self, data, {
"onChange"
}, renderContext)
return self
end,
}
return object
end,
Container = function(base, basalt)
local lastXMLReferences = {}
local function xmlDefaultValues(data, obj, renderContext)
if (obj~=nil) then
obj:setValuesByXMLData(data, renderContext)
end
end
local function addXMLObjectType(node, addFn, self, renderContext)
if (node ~= nil) then
if (node.attributes ~= nil) then
node = {node}
end
for _, v in pairs(node) do
local obj = addFn(self, v["@id"] or uuid())
lastXMLReferences[obj:getName()] = obj
xmlDefaultValues(v, obj, renderContext)
end
end
end
local function insertChildLayout(self, layout, node, renderContext)
local updateFns = {}
for prop, expression in pairs(node.attributes) do
updateFns[prop] = basalt.derived(function()
return load("return " .. expression, nil, "t", renderContext.env)()
end)
end
local props = {}
setmetatable(props, {
__index = function(_, k)
return updateFns[k]()
end
})
self:loadLayout(layout.path, props)
end
local object = {
setValuesByXMLData = function(self, data, renderContext)
lastXMLReferences = {}
base.setValuesByXMLData(self, data, renderContext)
local _OBJECTS = basalt.getObjects()
for _, childNode in pairs(data.children) do
local tagName = childNode.name
if (tagName ~= "animation") then
local layout = renderContext.env[tagName]
local objectKey = tagName:gsub("^%l", string.upper)
if (layout ~= nil) then
insertChildLayout(self, layout, childNode, renderContext)
elseif (_OBJECTS[objectKey] ~= nil) then
local addFn = self["add" .. objectKey]
addXMLObjectType(childNode, addFn, self, renderContext)
end
end
end
addXMLObjectType(data["animation"], self.addAnimation, self, renderContext)
return self
end,
loadLayout = function(self, path, props)
if(fs.exists(path))then
local renderContext = {}
renderContext.env = _ENV
renderContext.env.props = props
local f = fs.open(path, "r")
local data = XMLParser:ParseXmlText(f.readAll())
f.close()
lastXMLReferences = {}
maybeExecuteScript(data, renderContext)
self:setValuesByXMLData(data, renderContext)
end
return self
end,
getXMLElements = function(self)
return lastXMLReferences
end,
}
return object
end,
Textfield = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
if(data["lines"]~=nil)then
local l = data["lines"]["line"]
if(l.attributes~=nil)then l = {l} end
for _,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.attributes~=nil)then entry = {entry} end
local tab = {}
for a,b in pairs(entry)do
local keywordList = b["keyword"]
if(b["keyword"].attributes~=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"].attributes~=nil)then tab = {data["rules"]["rule"]} end
for k,v in pairs(tab)do
if(XMLParser.XmlValue("pattern", v)~=nil)then
self:addRule(XMLParser.XmlValue("pattern", v), colors[XMLParser.XmlValue("fg", v)], colors[XMLParser.XmlValue("bg", v)])
end
end
end
end
return self
end,
}
return object
end,
Thread = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
local script = XMLParser.XmlValue("start", data)~=nil
if(script~=nil)then
local f = load(script, nil, "t", renderContext.env)
self:start(f)
end
return self
end,
}
return object
end,
Timer = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
registerFunctionEvents(self, data, {
"onCall"
}, renderContext)
return self
end,
}
return object
end,
List = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
if(data["item"]~=nil)then
local tab = data["item"]
if(tab.attributes~=nil)then tab = {tab} end
for _,v in pairs(tab)do
if(self:getType()~="Radio")then
self:addItem(XMLParser.XmlValue("text", v), colors[XMLParser.XmlValue("bg", v)], colors[XMLParser.XmlValue("fg", v)])
end
end
end
return self
end,
}
return object
end,
Dropdown = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
return self
end,
}
return object
end,
Radio = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
if(data["item"]~=nil)then
local tab = data["item"]
if(tab.attributes~=nil)then tab = {tab} end
for _,v in pairs(tab)do
self:addItem(XMLParser.XmlValue("text", v), XMLParser.XmlValue("x", v), XMLParser.XmlValue("y", v), colors[XMLParser.XmlValue("bg", v)], colors[XMLParser.XmlValue("fg", v)])
end
end
return self
end,
}
return object
end,
Graph = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
if(data["item"]~=nil)then
local tab = data["item"]
if(tab.attributes~=nil)then tab = {tab} end
for _,_ in pairs(tab)do
self:addDataPoint(XMLParser.XmlValue("value"))
end
end
return self
end,
}
return object
end,
Treeview = function(base, basalt)
local object = {
setValuesByXMLData = function(self, data, renderContext)
base.setValuesByXMLData(self, data, renderContext)
local function addNode(node, data)
if(data["node"]~=nil)then
local tab = data["node"]
if(tab.attributes~=nil)then tab = {tab} end
for _,v in pairs(tab)do
local n = node:addNode(XMLParser.XmlValue("text", v), colors[XMLParser.XmlValue("bg", v)], colors[XMLParser.XmlValue("fg", v)])
addNode(n, v)
end
end
end
if(data["node"]~=nil)then
local tab = data["node"]
if(tab.attributes~=nil)then tab = {tab} end
for _,v in pairs(tab)do
local n = self:addNode(XMLParser.XmlValue("text", v), colors[XMLParser.XmlValue("bg", v)], colors[XMLParser.XmlValue("fg", v)])
addNode(n, v)
end
end
return self
end,
}
return object
end,
}