- Added the Collection Element and moved parts of the List logic to collection - Added a State Management System - Added a better system to change the position/size of elements - Removed the state plugin
526 lines
18 KiB
Lua
526 lines
18 KiB
Lua
local deepCopy = require("libraries/utils").deepCopy
|
|
local expect = require("libraries/expect")
|
|
local errorManager = require("errorManager")
|
|
|
|
--- PropertySystem is a class that allows Elements to have properties that can be observed and updated.
|
|
--- It also allows for properties to have custom getters and setters. This is the base system for all Elements.
|
|
--- @class PropertySystem
|
|
--- @field _properties table A table containing all property configurations
|
|
--- @field _values table A table containing all property values
|
|
--- @field _observers table A table containing all property observers
|
|
--- @field set function A function to set a property value
|
|
--- @field get function A function to get a property value
|
|
local PropertySystem = {}
|
|
PropertySystem.__index = PropertySystem
|
|
|
|
PropertySystem._properties = {}
|
|
local blueprintTemplates = {}
|
|
|
|
PropertySystem._setterHooks = {}
|
|
|
|
--- Adds a setter hook to the PropertySystem. Setter hooks are functions that are called before a property is set.
|
|
--- @shortDescription Adds a setter hook to the PropertySystem
|
|
--- @param hook function The hook function to add
|
|
function PropertySystem.addSetterHook(hook)
|
|
table.insert(PropertySystem._setterHooks, hook)
|
|
end
|
|
|
|
local function applyHooks(element, propertyName, value, config)
|
|
for _, hook in ipairs(PropertySystem._setterHooks) do
|
|
local newValue = hook(element, propertyName, value, config)
|
|
if newValue ~= nil then
|
|
value = newValue
|
|
end
|
|
end
|
|
return value
|
|
end
|
|
|
|
--- Defines a property for an element class
|
|
--- @shortDescription Defines a property for an element class
|
|
--- @param class table The element class to define the property for
|
|
--- @param name string The name of the property
|
|
--- @param config table The configuration of the property
|
|
function PropertySystem.defineProperty(class, name, config)
|
|
if not rawget(class, '_properties') then
|
|
class._properties = {}
|
|
end
|
|
|
|
class._properties[name] = {
|
|
type = config.type,
|
|
default = config.default,
|
|
canTriggerRender = config.canTriggerRender,
|
|
getter = config.getter,
|
|
setter = config.setter,
|
|
allowNil = config.allowNil,
|
|
}
|
|
|
|
local capitalizedName = name:sub(1,1):upper() .. name:sub(2)
|
|
|
|
class["get" .. capitalizedName] = function(self, ...)
|
|
expect(1, self, "element")
|
|
local value = self._values[name]
|
|
if type(value) == "function" and config.type ~= "function" then
|
|
value = value(self)
|
|
end
|
|
return config.getter and config.getter(self, value, ...) or value
|
|
end
|
|
|
|
class["set" .. capitalizedName] = function(self, value, ...)
|
|
expect(1, self, "element")
|
|
value = applyHooks(self, name, value, config)
|
|
|
|
if type(value) ~= "function" then
|
|
if config.type == "table" then
|
|
if value == nil then
|
|
if not config.allowNil then
|
|
expect(2, value, config.type)
|
|
end
|
|
end
|
|
else
|
|
expect(2, value, config.type)
|
|
end
|
|
end
|
|
|
|
if config.setter then
|
|
value = config.setter(self, value, ...)
|
|
end
|
|
|
|
self:_updateProperty(name, value)
|
|
return self
|
|
end
|
|
|
|
class["get" .. capitalizedName .. "State"] = function(self, state, ...)
|
|
expect(1, self, "element")
|
|
return self.getPropertyState(name, state, ...)
|
|
end
|
|
|
|
class["set" .. capitalizedName .. "State"] = function(self, state, value, ...)
|
|
expect(1, self, "element")
|
|
self.setPropertyState(name, state, value, ...)
|
|
return self
|
|
end
|
|
end
|
|
|
|
--- Combines multiple properties into a single getter and setter
|
|
--- @shortDescription Combines multiple properties
|
|
--- @param class table The element class to combine the properties for
|
|
--- @param name string The name of the combined property
|
|
--- @vararg string The names of the properties to combine
|
|
function PropertySystem.combineProperties(class, name, ...)
|
|
local properties = {...}
|
|
for k,v in pairs(properties)do
|
|
if not class._properties[v] then errorManager.error("Property not found: "..v) end
|
|
end
|
|
local capitalizedName = name:sub(1,1):upper() .. name:sub(2)
|
|
|
|
class["get" .. capitalizedName] = function(self)
|
|
expect(1, self, "element")
|
|
local value = {}
|
|
for _,v in pairs(properties)do
|
|
table.insert(value, self.get(v))
|
|
end
|
|
return table.unpack(value)
|
|
end
|
|
|
|
class["set" .. capitalizedName] = function(self, ...)
|
|
expect(1, self, "element")
|
|
local values = {...}
|
|
for i,v in pairs(properties)do
|
|
self.set(v, values[i])
|
|
end
|
|
return self
|
|
end
|
|
end
|
|
|
|
--- Creates a blueprint of an element class with all its properties
|
|
--- @shortDescription Creates a blueprint of an element class
|
|
--- @param elementClass table The element class to create a blueprint from
|
|
--- @return table blueprint A table containing all property definitions
|
|
function PropertySystem.blueprint(elementClass, properties, basalt, parent)
|
|
if not blueprintTemplates[elementClass] then
|
|
local template = {
|
|
basalt = basalt,
|
|
__isBlueprint = true,
|
|
_values = properties or {},
|
|
_events = {},
|
|
render = function() end,
|
|
dispatchEvent = function() end,
|
|
init = function() end,
|
|
}
|
|
|
|
template.loaded = function(self, callback)
|
|
self.loadedCallback = callback
|
|
return template
|
|
end
|
|
|
|
template.create = function(self)
|
|
local element = elementClass.new()
|
|
element:init({}, self.basalt)
|
|
for name, value in pairs(self._values) do
|
|
element._values[name] = value
|
|
end
|
|
for name, callbacks in pairs(self._events) do
|
|
for _, callback in ipairs(callbacks) do
|
|
element[name](element, callback)
|
|
end
|
|
end
|
|
if(parent~=nil)then
|
|
parent:addChild(element)
|
|
end
|
|
element:updateRender()
|
|
self.loadedCallback(element)
|
|
element:postInit()
|
|
return element
|
|
end
|
|
|
|
local currentClass = elementClass
|
|
while currentClass do
|
|
if rawget(currentClass, '_properties') then
|
|
for name, config in pairs(currentClass._properties) do
|
|
if type(config.default) == "table" then
|
|
template._values[name] = deepCopy(config.default)
|
|
else
|
|
template._values[name] = config.default
|
|
end
|
|
end
|
|
end
|
|
currentClass = getmetatable(currentClass) and rawget(getmetatable(currentClass), '__index')
|
|
end
|
|
|
|
blueprintTemplates[elementClass] = template
|
|
end
|
|
|
|
local blueprint = {
|
|
_values = {},
|
|
_events = {},
|
|
loadedCallback = function() end,
|
|
}
|
|
|
|
blueprint.get = function(name)
|
|
local value = blueprint._values[name]
|
|
local config = elementClass._properties[name]
|
|
if type(value) == "function" and config.type ~= "function" then
|
|
value = value(blueprint)
|
|
end
|
|
return value
|
|
end
|
|
blueprint.set = function(name, value)
|
|
blueprint._values[name] = value
|
|
return blueprint
|
|
end
|
|
|
|
setmetatable(blueprint, {
|
|
__index = function(self, k)
|
|
if k:match("^on%u") then
|
|
return function(_, callback)
|
|
self._events[k] = self._events[k] or {}
|
|
table.insert(self._events[k], callback)
|
|
return self
|
|
end
|
|
end
|
|
if k:match("^get%u") then
|
|
local propName = k:sub(4,4):lower() .. k:sub(5)
|
|
return function()
|
|
return self._values[propName]
|
|
end
|
|
end
|
|
if k:match("^set%u") then
|
|
local propName = k:sub(4,4):lower() .. k:sub(5)
|
|
return function(_, value)
|
|
self._values[propName] = value
|
|
return self
|
|
end
|
|
end
|
|
return blueprintTemplates[elementClass][k]
|
|
end
|
|
})
|
|
|
|
return blueprint
|
|
end
|
|
|
|
--- Creates an element from a blueprint
|
|
--- @shortDescription Creates an element from a blueprint
|
|
--- @param elementClass table The element class to create from the blueprint
|
|
--- @param blueprint table The blueprint to create the element from
|
|
--- @return table element The created element
|
|
function PropertySystem.createFromBlueprint(elementClass, blueprint, basalt)
|
|
local element = elementClass.new({}, basalt)
|
|
for name, value in pairs(blueprint._values) do
|
|
if type(value) == "table" then
|
|
element._values[name] = deepCopy(value)
|
|
else
|
|
element._values[name] = value
|
|
end
|
|
end
|
|
|
|
return element
|
|
end
|
|
|
|
--- Initializes the PropertySystem IS USED INTERNALLY
|
|
--- @shortDescription Initializes the PropertySystem
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:__init()
|
|
self._values = {}
|
|
self._observers = {}
|
|
self._states = {}
|
|
|
|
self.set = function(name, value, ...)
|
|
local oldValue = self._values[name]
|
|
local config = self._properties[name]
|
|
if(config~=nil)then
|
|
if(config.setter) then
|
|
value = config.setter(self, value, ...)
|
|
end
|
|
if config.canTriggerRender then
|
|
self:updateRender()
|
|
end
|
|
self._values[name] = applyHooks(self, name, value, config)
|
|
if oldValue ~= value and self._observers[name] then
|
|
for _, callback in ipairs(self._observers[name]) do
|
|
callback(self, value, oldValue)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
self.get = function(name, ...)
|
|
local value = self._values[name]
|
|
local config = self._properties[name]
|
|
if(config==nil)then errorManager.error("Property not found: "..name) return end
|
|
if type(value) == "function" and config.type ~= "function" then
|
|
value = value(self)
|
|
end
|
|
return config.getter and config.getter(self, value, ...) or value
|
|
end
|
|
|
|
self.setPropertyState = function(name, state, value, ...)
|
|
local config = self._properties[name]
|
|
if(config~=nil)then
|
|
if(config.setter) then
|
|
value = config.setter(self, value, ...)
|
|
end
|
|
|
|
value = applyHooks(self, name, value, config)
|
|
|
|
if not self._states[state] then
|
|
self._states[state] = {}
|
|
end
|
|
|
|
self._states[state][name] = value
|
|
|
|
local currentState = self._values.currentState
|
|
if currentState == state then
|
|
if config.canTriggerRender then
|
|
self:updateRender()
|
|
end
|
|
if self._observers[name] then
|
|
for _, callback in ipairs(self._observers[name]) do
|
|
callback(self, value, nil)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
self.getPropertyState = function(name, state, ...)
|
|
local stateValue = self._states and self._states[state] and self._states[state][name]
|
|
local value = stateValue ~= nil and stateValue or self._values[name]
|
|
|
|
local config = self._properties[name]
|
|
if(config==nil)then errorManager.error("Property not found: "..name) return end
|
|
if type(value) == "function" and config.type ~= "function" then
|
|
value = value(self)
|
|
end
|
|
return config.getter and config.getter(self, value, ...) or value
|
|
end
|
|
|
|
self.getResolved = function(name, ...)
|
|
local currentState = self:getCurrentState()
|
|
local value
|
|
|
|
if currentState and self._states and self._states[currentState] and self._states[currentState][name] ~= nil then
|
|
value = self._states[currentState][name]
|
|
else
|
|
value = self._values[name]
|
|
end
|
|
|
|
local config = self._properties[name]
|
|
if(config==nil)then errorManager.error("Property not found: "..name) return end
|
|
if type(value) == "function" and config.type ~= "function" then
|
|
value = value(self)
|
|
end
|
|
return config.getter and config.getter(self, value, ...) or value
|
|
end
|
|
|
|
local properties = {}
|
|
local currentClass = getmetatable(self).__index
|
|
|
|
while currentClass do
|
|
if rawget(currentClass, '_properties') then
|
|
for name, config in pairs(currentClass._properties) do
|
|
if not properties[name] then
|
|
properties[name] = config
|
|
end
|
|
end
|
|
end
|
|
currentClass = getmetatable(currentClass) and rawget(getmetatable(currentClass), '__index')
|
|
end
|
|
|
|
self._properties = properties
|
|
|
|
local originalMT = getmetatable(self)
|
|
local originalIndex = originalMT.__index
|
|
setmetatable(self, {
|
|
__index = function(t, k)
|
|
local config = self._properties[k]
|
|
if config then
|
|
local value = self._values[k]
|
|
if type(value) == "function" and config.type ~= "function" then
|
|
value = value(self)
|
|
end
|
|
return value
|
|
end
|
|
if type(originalIndex) == "function" then
|
|
return originalIndex(t, k)
|
|
else
|
|
return originalIndex[k]
|
|
end
|
|
end,
|
|
__newindex = function(t, k, v)
|
|
local config = self._properties[k]
|
|
if config then
|
|
if config.setter then
|
|
v = config.setter(self, v)
|
|
end
|
|
v = applyHooks(self, k, v, config)
|
|
self:_updateProperty(k, v)
|
|
else
|
|
rawset(t, k, v)
|
|
end
|
|
end,
|
|
__tostring = function(self)
|
|
return string.format("Object: %s (id: %s)", self._values.type, self.id)
|
|
end
|
|
})
|
|
|
|
for name, config in pairs(properties) do
|
|
if self._values[name] == nil then
|
|
if type(config.default) == "table" then
|
|
self._values[name] = deepCopy(config.default)
|
|
else
|
|
self._values[name] = config.default
|
|
end
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- Update call for a property IS USED INTERNALLY
|
|
--- @shortDescription Update call for a property
|
|
--- @param name string The name of the property
|
|
--- @param value any The value of the property
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:_updateProperty(name, value)
|
|
local oldValue = self._values[name]
|
|
if type(oldValue) == "function" then
|
|
oldValue = oldValue(self)
|
|
end
|
|
|
|
self._values[name] = value
|
|
local newValue = type(value) == "function" and value(self) or value
|
|
|
|
if oldValue ~= newValue then
|
|
if self._properties[name].canTriggerRender then
|
|
self:updateRender()
|
|
end
|
|
if self._observers[name] then
|
|
for _, callback in ipairs(self._observers[name]) do
|
|
callback(self, newValue, oldValue)
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Observers a property
|
|
--- @shortDescription Observers a property
|
|
--- @param name string The name of the property
|
|
--- @param callback function The callback function to call when the property changes
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:observe(name, callback)
|
|
self._observers[name] = self._observers[name] or {}
|
|
table.insert(self._observers[name], callback)
|
|
return self
|
|
end
|
|
|
|
--- Removes an observer from a property
|
|
--- @shortDescription Removes an observer from a property
|
|
--- @param name string The name of the property
|
|
--- @param callback function The callback function to remove
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:removeObserver(name, callback)
|
|
if self._observers[name] then
|
|
for i, cb in ipairs(self._observers[name]) do
|
|
if cb == callback then
|
|
table.remove(self._observers[name], i)
|
|
if #self._observers[name] == 0 then
|
|
self._observers[name] = nil
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Removes all observers from a property
|
|
--- @shortDescription Removes all observers from a property
|
|
--- @param name? string The name of the property
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:removeAllObservers(name)
|
|
if name then
|
|
self._observers[name] = nil
|
|
else
|
|
self._observers = {}
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Adds a property to the PropertySystem on instance level
|
|
--- @shortDescription Adds a property to the PropertySystem on instance level
|
|
--- @param name string The name of the property
|
|
--- @param config table The configuration of the property
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:instanceProperty(name, config)
|
|
PropertySystem.defineProperty(self, name, config)
|
|
self._values[name] = config.default
|
|
return self
|
|
end
|
|
|
|
--- Removes a property from the PropertySystem on instance level
|
|
--- @shortDescription Removes a property from the PropertySystem
|
|
--- @param name string The name of the property
|
|
--- @return table self The PropertySystem
|
|
function PropertySystem:removeProperty(name)
|
|
self._values[name] = nil
|
|
self._properties[name] = nil
|
|
self._observers[name] = nil
|
|
|
|
local capitalizedName = name:sub(1,1):upper() .. name:sub(2)
|
|
self["get" .. capitalizedName] = nil
|
|
self["set" .. capitalizedName] = nil
|
|
self["get" .. capitalizedName .. "State"] = nil
|
|
self["set" .. capitalizedName .. "State"] = nil
|
|
return self
|
|
end
|
|
|
|
--- Gets a property configuration
|
|
--- @shortDescription Gets a property configuration
|
|
--- @param name string The name of the property
|
|
--- @return table config The configuration of the property
|
|
function PropertySystem:getPropertyConfig(name)
|
|
return self._properties[name]
|
|
end
|
|
|
|
return PropertySystem |