215 lines
6.6 KiB
Markdown
215 lines
6.6 KiB
Markdown
local errorManager = require("errorManager")
|
|
local PropertySystem = require("propertySystem")
|
|
local log = require("log")
|
|
|
|
local protectedNames = {
|
|
colors = true,
|
|
math = true,
|
|
clamp = true,
|
|
round = true
|
|
}
|
|
|
|
local mathEnv = {
|
|
clamp = function(val, min, max)
|
|
return math.min(math.max(val, min), max)
|
|
end,
|
|
round = function(val)
|
|
return math.floor(val + 0.5)
|
|
end
|
|
}
|
|
|
|
local function parseExpression(expr, element, propName)
|
|
expr = expr:gsub("^{(.+)}$", "%1")
|
|
|
|
expr = expr:gsub("([%w_]+)%$([%w_]+)", function(obj, prop)
|
|
if obj == "self" then
|
|
return string.format('__getState("%s")', prop)
|
|
elseif obj == "parent" then
|
|
return string.format('__getParentState("%s")', prop)
|
|
else
|
|
return string.format('__getElementState("%s", "%s")', obj, prop)
|
|
end
|
|
end)
|
|
|
|
expr = expr:gsub("([%w_]+)%.([%w_]+)", function(obj, prop)
|
|
if protectedNames[obj] then
|
|
return obj.."."..prop
|
|
end
|
|
return string.format('__getProperty("%s", "%s")', obj, prop)
|
|
end)
|
|
|
|
local env = setmetatable({
|
|
colors = colors,
|
|
math = math,
|
|
tostring = tostring,
|
|
tonumber = tonumber,
|
|
__getState = function(prop)
|
|
return element:getState(prop)
|
|
end,
|
|
__getParentState = function(prop)
|
|
return element.parent:getState(prop)
|
|
end,
|
|
__getElementState = function(objName, prop)
|
|
local target = element:getBaseFrame():getChild(objName)
|
|
if not target then
|
|
errorManager.header = "Reactive evaluation error"
|
|
errorManager.error("Could not find element: " .. objName)
|
|
return nil
|
|
end
|
|
return target:getState(prop).value
|
|
end,
|
|
__getProperty = function(objName, propName)
|
|
if objName == "self" then
|
|
return element.get(propName)
|
|
elseif objName == "parent" then
|
|
return element.parent.get(propName)
|
|
else
|
|
local target = element:getBaseFrame():getChild(objName)
|
|
if not target then
|
|
errorManager.header = "Reactive evaluation error"
|
|
errorManager.error("Could not find element: " .. objName)
|
|
return nil
|
|
end
|
|
|
|
return target.get(propName)
|
|
end
|
|
end
|
|
}, { __index = mathEnv })
|
|
|
|
if(element._properties[propName].type == "string")then
|
|
expr = "tostring(" .. expr .. ")"
|
|
elseif(element._properties[propName].type == "number")then
|
|
expr = "tonumber(" .. expr .. ")"
|
|
end
|
|
|
|
local func, err = load("return "..expr, "reactive", "t", env)
|
|
if not func then
|
|
errorManager.header = "Reactive evaluation error"
|
|
errorManager.error("Invalid expression: " .. err)
|
|
return function() return nil end
|
|
end
|
|
|
|
return func
|
|
end
|
|
|
|
local function validateReferences(expr, element)
|
|
for ref in expr:gmatch("([%w_]+)%.") do
|
|
if not protectedNames[ref] then
|
|
if ref == "self" then
|
|
elseif ref == "parent" then
|
|
if not element.parent then
|
|
errorManager.header = "Reactive evaluation error"
|
|
errorManager.error("No parent element available")
|
|
return false
|
|
end
|
|
else
|
|
local target = element:getBaseFrame():getChild(ref)
|
|
if not target then
|
|
errorManager.header = "Reactive evaluation error"
|
|
errorManager.error("Referenced element not found: " .. ref)
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local functionCache = setmetatable({}, {__mode = "k"})
|
|
|
|
local observerCache = setmetatable({}, {
|
|
__mode = "k",
|
|
__index = function(t, k)
|
|
t[k] = {}
|
|
return t[k]
|
|
end
|
|
})
|
|
|
|
local function setupObservers(element, expr, propertyName)
|
|
if observerCache[element][propertyName] then
|
|
for _, observer in ipairs(observerCache[element][propertyName]) do
|
|
observer.target:removeObserver(observer.property, observer.callback)
|
|
end
|
|
end
|
|
|
|
local observers = {}
|
|
for ref, prop in expr:gmatch("([%w_]+)%.([%w_]+)") do
|
|
if not protectedNames[ref] then
|
|
local target
|
|
if ref == "self" then
|
|
target = element
|
|
elseif ref == "parent" then
|
|
target = element.parent
|
|
else
|
|
target = element:getBaseFrame():getChild(ref)
|
|
end
|
|
|
|
if target then
|
|
local observer = {
|
|
target = target,
|
|
property = prop,
|
|
callback = function()
|
|
element:updateRender()
|
|
end
|
|
}
|
|
target:observe(prop, observer.callback)
|
|
table.insert(observers, observer)
|
|
end
|
|
end
|
|
end
|
|
|
|
observerCache[element][propertyName] = observers
|
|
end
|
|
|
|
PropertySystem.addSetterHook(function(element, propertyName, value, config)
|
|
if type(value) == "string" and value:match("^{.+}$") then
|
|
local expr = value:gsub("^{(.+)}$", "%1")
|
|
if not validateReferences(expr, element) then
|
|
return config.default
|
|
end
|
|
|
|
setupObservers(element, expr, propertyName)
|
|
|
|
if not functionCache[element] then
|
|
functionCache[element] = {}
|
|
end
|
|
if not functionCache[element][value] then
|
|
local parsedFunc = parseExpression(value, element, propertyName)
|
|
functionCache[element][value] = parsedFunc
|
|
end
|
|
|
|
return function(self)
|
|
local success, result = pcall(functionCache[element][value])
|
|
if not success then
|
|
errorManager.header = "Reactive evaluation error"
|
|
if type(result) == "string" then
|
|
errorManager.error("Error evaluating expression: " .. result)
|
|
else
|
|
errorManager.error("Error evaluating expression")
|
|
end
|
|
return config.default
|
|
end
|
|
return result
|
|
end
|
|
end
|
|
end)
|
|
|
|
local BaseElement = {}
|
|
|
|
BaseElement.hooks = {
|
|
destroy = function(self)
|
|
if observerCache[self] then
|
|
for propName, observers in pairs(observerCache[self]) do
|
|
for _, observer in ipairs(observers) do
|
|
observer.target:removeObserver(observer.property, observer.callback)
|
|
end
|
|
end
|
|
observerCache[self] = nil
|
|
end
|
|
end
|
|
}
|
|
|
|
return {
|
|
BaseElement = BaseElement
|
|
}
|