203 lines
6.3 KiB
Lua
203 lines
6.3 KiB
Lua
local XMLParser = require("xmlParser")
|
|
|
|
local Reactive = {}
|
|
|
|
Reactive.currentEffect = nil
|
|
|
|
Reactive.observable = function(initialValue)
|
|
local value = initialValue
|
|
local observerEffects = {}
|
|
local get = function()
|
|
if (Reactive.currentEffect ~= nil) then
|
|
table.insert(observerEffects, Reactive.currentEffect)
|
|
table.insert(Reactive.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
|
|
|
|
Reactive.untracked = function(getter)
|
|
local parentEffect = Reactive.currentEffect
|
|
Reactive.currentEffect = nil
|
|
local value = getter()
|
|
Reactive.currentEffect = parentEffect
|
|
return value
|
|
end
|
|
|
|
Reactive.effect = function(effectFn)
|
|
local effect = {dependencies = {}}
|
|
local execute = function()
|
|
Reactive.clearEffectDependencies(effect)
|
|
local parentEffect = Reactive.currentEffect
|
|
Reactive.currentEffect = effect
|
|
effectFn()
|
|
Reactive.currentEffect = parentEffect
|
|
end
|
|
effect.execute = execute
|
|
effect.execute()
|
|
end
|
|
|
|
Reactive.derived = function(computeFn)
|
|
local getValue, setValue = Reactive.observable();
|
|
Reactive.effect(function()
|
|
setValue(computeFn())
|
|
end)
|
|
return getValue;
|
|
end
|
|
|
|
Reactive.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
|
|
|
|
local Layout = {
|
|
fromXML = function(text)
|
|
local nodes = XMLParser.parseText(text)
|
|
local script = nil
|
|
for index, node in ipairs(nodes) do
|
|
if (node.tag == "script") then
|
|
script = node.value
|
|
table.remove(nodes, index)
|
|
break
|
|
end
|
|
end
|
|
return {
|
|
nodes = nodes,
|
|
script = script
|
|
}
|
|
end
|
|
}
|
|
|
|
local executeScript = function(script, env)
|
|
return load(script, nil, "t", env)()
|
|
end
|
|
|
|
local registerFunctionEvent = function(object, event, script, env)
|
|
event(object, function(...)
|
|
local success, msg = pcall(load(script, nil, "t", env))
|
|
if not success then
|
|
error("XML Error: "..msg)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return {
|
|
basalt = function(basalt)
|
|
local createObjectsFromXMLNode = function(node, env)
|
|
local layout = env[node.tag]
|
|
if (layout ~= nil) then
|
|
local props = {}
|
|
for prop, expression in pairs(node.attributes) do
|
|
props[prop] = load("return " .. expression, nil, "t", env)
|
|
end
|
|
return basalt.createObjectsFromLayout(layout, props)
|
|
end
|
|
|
|
local objectName = node.tag:gsub("^%l", string.upper)
|
|
local object = basalt:createObject(objectName, node.attributes["id"])
|
|
for attribute, expression in pairs(node.attributes) do
|
|
if (attribute:sub(1, 2) == "on") then
|
|
registerFunctionEvent(object, object[attribute], expression .. "()", env)
|
|
else
|
|
local update = function()
|
|
local value = load("return " .. expression, nil, "t", env)()
|
|
object:setProperty(attribute, value)
|
|
end
|
|
basalt.effect(update)
|
|
end
|
|
end
|
|
for _, child in ipairs(node.children) do
|
|
local childObjects = basalt.createObjectsFromXMLNode(child, env)
|
|
for _, childObject in ipairs(childObjects) do
|
|
object:addChild(childObject)
|
|
end
|
|
end
|
|
return {object}
|
|
end
|
|
|
|
local object = {
|
|
observable = Reactive.observable,
|
|
untracked = Reactive.untracked,
|
|
effect = Reactive.effect,
|
|
derived = Reactive.derived,
|
|
|
|
layout = function(path)
|
|
if (not fs.exists(path)) then
|
|
error("Can't open file " .. path)
|
|
end
|
|
local f = fs.open(path, "r")
|
|
local text = f.readAll()
|
|
f.close()
|
|
return Layout.fromXML(text)
|
|
end,
|
|
|
|
createObjectsFromLayout = function(layout, props)
|
|
local env = _ENV
|
|
env.props = {}
|
|
local updateFns = {}
|
|
for prop, getFn in pairs(props) do
|
|
updateFns[prop] = basalt.derived(function()
|
|
return getFn()
|
|
end)
|
|
end
|
|
setmetatable(env.props, {
|
|
__index = function(_, k)
|
|
return updateFns[k]()
|
|
end
|
|
})
|
|
if (layout.script ~= nil) then
|
|
executeScript(layout.script, env)
|
|
end
|
|
local objects = {}
|
|
for _, node in ipairs(layout.nodes) do
|
|
local _objects = createObjectsFromXMLNode(node, env)
|
|
for _, object in ipairs(_objects) do
|
|
table.insert(objects, object)
|
|
end
|
|
end
|
|
return objects
|
|
end
|
|
}
|
|
return object
|
|
end,
|
|
|
|
Container = function(base, basalt)
|
|
local object = {
|
|
loadLayout = function(self, path, props)
|
|
local wrappedProps = {}
|
|
if (props == nil) then
|
|
props = {}
|
|
end
|
|
for prop, value in pairs(props) do
|
|
wrappedProps[prop] = function()
|
|
return value
|
|
end
|
|
end
|
|
local layout = basalt.layout(path)
|
|
local objects = basalt.createObjectsFromLayout(layout, wrappedProps)
|
|
for _, object in ipairs(objects) do
|
|
self:addChild(object)
|
|
end
|
|
return self
|
|
end
|
|
}
|
|
return object
|
|
end
|
|
} |