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 }