553 lines
16 KiB
Lua
553 lines
16 KiB
Lua
local module = {}
|
|
local pack = table.pack
|
|
local match = string.match
|
|
local insert = table.insert
|
|
local gsub = string.gsub
|
|
|
|
---@param shared quad_export
|
|
---@return quad_module_class
|
|
function module.init(shared)
|
|
local warn = shared.warn
|
|
---@class quad_module_class
|
|
local new = {__type = "quad_module_class"}
|
|
local InstanceNew = Instance.new
|
|
local event = shared.Event ---@type quad_module_event
|
|
local bind = event.Bind
|
|
local store = shared.Store ---@type quad_module_store
|
|
local initStoreRegisterBinding = store.__initStoreRegisterBinding
|
|
local addObject = store.AddObject
|
|
local storeNew = store.New
|
|
local mount = shared.Mount ---@type quad_module_mount
|
|
local getHolder = mount.GetHolder
|
|
local mountfunc = mount.MountOne
|
|
local style = shared.Style ---@type quad_module_style
|
|
local styleList = style.Styles
|
|
local parseStyles = style.ParseStyles
|
|
local advancedTween = shared.Tween ---@type quad_module_tween
|
|
local round = shared.Round ---@type quad_module_round
|
|
local signal = shared.Signal ---@type quad_module_signal
|
|
local bindable = signal.Bindable
|
|
|
|
local function InstanceNewWithName(classname,parent,name)
|
|
local item = InstanceNew(classname,parent)
|
|
item.Name = name
|
|
return item
|
|
end
|
|
|
|
local function GetProperty(item,index,ClassName)
|
|
if type(item) == "table" then return item[index] end
|
|
ClassName = ClassName or item.ClassName
|
|
local isImage = (ClassName == "ImageLabel" or ClassName == "ImageButton")
|
|
if (index == "RoundSize") and isImage then
|
|
if not round then
|
|
error("module 'round' needs to be loaded for get image round size but it is not found on 'src.libs'. you should adding that to src.libs directory")
|
|
end
|
|
return round.GetRound(item)
|
|
elseif index == "Corner" then
|
|
local target = (ClassName == "UICorner" and item) or item:FindFirstChildOfClass("UICorner")
|
|
return (target and target.CornerRadius.Offset) or 0
|
|
elseif index == "PaddingAll" then
|
|
local target = (ClassName == "UIPadding" and item) or item:FindFirstChildOfClass("UIPadding")
|
|
return (target and target.PaddingLeft) or UDim.new(0,0)
|
|
elseif index == "PaddingAllOffset" then
|
|
local target = (ClassName == "UIPadding" and item) or item:FindFirstChildOfClass("UIPadding")
|
|
return (target and target.PaddingLeft.Offset) or 0
|
|
elseif index == "Scale" then
|
|
local target = (ClassName == "UIScale" and item) or item:FindFirstChildOfClass("UIScale")
|
|
return (target and target.Scale) or 1
|
|
end
|
|
return item[index]
|
|
end
|
|
new.GetProperty = GetProperty
|
|
|
|
local function SetProperty(item,index,value,ClassName)
|
|
-- if it is not a roblox instance, just call __index
|
|
if type(item) == "table" then
|
|
item[index] = value
|
|
return
|
|
end
|
|
|
|
-- check class name
|
|
ClassName = ClassName or item.ClassName
|
|
-- check is ImageLabel or ImageButton
|
|
local isImage = (ClassName == "ImageLabel" or ClassName == "ImageButton")
|
|
|
|
-- Round Image
|
|
if index == "RoundSize" and isImage then
|
|
if not round then
|
|
error("[QUAD] module 'round' needs to be loaded for set image round size but it is not found on 'src.libs'. you should adding that to src.libs directory")
|
|
end
|
|
round.SetRound(item,value)
|
|
-- UIRound
|
|
elseif index == "Corner" then
|
|
local uiCorner = (ClassName == "UICorner" and item) or item:FindFirstChildOfClass("UICorner") or InstanceNewWithName("UICorner",item,"_quad_round")
|
|
uiCorner.CornerRadius = UDim.new(0,value)
|
|
-- PaddingAll
|
|
elseif index == "PaddingAll" then
|
|
local target = (ClassName == "UIPadding" and item) or item:FindFirstChildOfClass("UIPadding") or InstanceNewWithName("UIPadding",item,"_quad_padding")
|
|
target.PaddingLeft = value
|
|
target.PaddingRight = value
|
|
target.PaddingTop = value
|
|
target.PaddingBottom = value
|
|
-- PaddingAll with Offset
|
|
elseif index == "PaddingAllOffset" then
|
|
local target = (ClassName == "UIPadding" and item) or item:FindFirstChildOfClass("UIPadding") or InstanceNewWithName("UIPadding",item,"_quad_padding")
|
|
local padding = UDim.new(0,value)
|
|
target.PaddingLeft = padding
|
|
target.PaddingRight = padding
|
|
target.PaddingTop = padding
|
|
target.PaddingBottom = padding
|
|
-- Scale
|
|
elseif index == "Scale" then
|
|
local target = (ClassName == "UIScale" and item) or item:FindFirstChildOfClass("UIScale") or InstanceNewWithName("UIScale",item,"_quad_scale")
|
|
target.Scale = value
|
|
else
|
|
-- other, just set property
|
|
item[index] = value
|
|
end
|
|
end
|
|
new.SetProperty = SetProperty
|
|
|
|
local function RawGetProperty(item,index)
|
|
return item[index]
|
|
end
|
|
local function PcallGetProperty(item,index)
|
|
local ok,err = pcall(RawGetProperty,item,index)
|
|
if ok then return err end
|
|
return nil
|
|
end
|
|
new.PcallGetProperty = PcallGetProperty
|
|
|
|
local function Link(linker,item,index,indexType)
|
|
local target = linker.target
|
|
local name = linker.name
|
|
if indexType == "number" then
|
|
-- set
|
|
if rawget(target,name) ~= nil then
|
|
warn(("%s is exist already. Overwrite it"))
|
|
end
|
|
rawset(target,name,item)
|
|
-- target[name] = item
|
|
elseif indexType == "string" then
|
|
local targetSignal = target:GetPropertyChangedSignal(name)
|
|
local fire = PcallGetProperty(targetSignal,"Fire")
|
|
local ok = fire and bind(item,index,function(...)
|
|
fire(targetSignal,...)
|
|
end,"function")
|
|
if not ok then
|
|
warn("Failed to bind linker event")
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ProcessQuadProperty(processedProperty,iprop,holder,item,className,index,value)
|
|
if processedProperty[index] then
|
|
return
|
|
end
|
|
|
|
local valueType = typeof(value)
|
|
local indexType = typeof(index)
|
|
local quadType = valueType == "table" and PcallGetProperty(value,"__type")
|
|
|
|
if quadType == "quad_linker" then
|
|
-- linking
|
|
Link(value,item,index,indexType)
|
|
elseif indexType == "string" and quadType == "quad_register" then
|
|
-- register (bind to store event)
|
|
|
|
processedProperty[index] = true -- ignore next
|
|
|
|
-- store reading
|
|
do
|
|
local setValue = value:CalcWithDefault(item)
|
|
if setValue == nil then
|
|
warn "[Quad] register return value must not be nil, but got nil. did you inited values before?"
|
|
end
|
|
SetProperty(item,index,setValue,className)
|
|
end
|
|
|
|
-- adding handle function (binding)
|
|
local function regFn(_,newValue,key)
|
|
local setValue,tween = value:CalcWithNewValue(item,newValue,key)
|
|
if tween then
|
|
if not advancedTween then
|
|
return warn "[QUAD] module 'AdvancedTween' needs to be loaded for tween properties but it is not found on 'src.libs'. you should adding that to src.libs directory"
|
|
end
|
|
-- local ended, onStepped
|
|
-- if tween.Ended then
|
|
-- ended = function (...)
|
|
-- tween.Ended(item,...)
|
|
-- end
|
|
-- end
|
|
-- if tween.OnStepped then
|
|
-- onStepped = function (...)
|
|
-- tween.OnStepped(item,...)
|
|
-- end
|
|
-- end
|
|
advancedTween.RunTween(item,tween,{[index] = setValue},nil,nil,SetProperty,GetProperty)
|
|
else
|
|
SetProperty(item,index,setValue,className)
|
|
end
|
|
end
|
|
value:Register(regFn)
|
|
|
|
-- this is using hacky of roblox instance
|
|
-- this is will keep reference from week table until
|
|
-- inscance got GCed
|
|
item:GetPropertyChangedSignal("ClassName"):Connect(regFn)
|
|
elseif (valueType == "function" or valueType == "table") and indexType == "string" and bind(item,index,value,valueType) then -- connect event
|
|
-- event binding
|
|
elseif indexType == "string" then
|
|
processedProperty[index] = true -- ignore next
|
|
-- prop set
|
|
SetProperty(item,index,value,className)
|
|
elseif indexType == "number" and valueType == "table" and quadType == "quad_style" then -- style
|
|
-- style parsing
|
|
for _,thisStyle in ipairs(parseStyles(value)) do
|
|
if not processedProperty[thisStyle] then
|
|
processedProperty[thisStyle] = true
|
|
for styleIndex,styleValue in pairs(thisStyle) do
|
|
if type(styleValue) ~= "table" or pcall(styleValue,"__type") ~= "quad_style" then
|
|
ProcessQuadProperty(processedProperty,iprop,holder,item,className,styleIndex,styleValue)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif indexType == "number" and valueType ~= "boolean" then -- object
|
|
-- child object
|
|
if iprop ~= 1 then
|
|
value = value:Clone()
|
|
end
|
|
mountfunc(item,value,holder,true)
|
|
end
|
|
end
|
|
|
|
-- make object that from instance, class and more
|
|
function new.Make(ClassName,...) -- render object
|
|
-- make object from ClassName
|
|
local item
|
|
local classOfClassName = type(ClassName)
|
|
if classOfClassName == "string" then -- if classname is a string value, cdall instance.new for making new roblox instance
|
|
item = InstanceNew(ClassName)
|
|
elseif classOfClassName == "function" then -- if classname is a function value, call it for making new object (OOP object)
|
|
item = ClassName()
|
|
elseif classOfClassName == "table" then -- if classname is a calss what is included new function, call it for making new object (object)
|
|
local func = ClassName.new or ClassName.New or ClassName.__new
|
|
if ClassName.__noSetup then -- if is support initing props
|
|
local childs,props,parsed,binds,links = {},pack(...),{},{},{}
|
|
for iprop = props.n,1,-1 do
|
|
local prop = props[iprop]
|
|
if prop then
|
|
for i,v in pairs(prop) do
|
|
if type(i) == "number" then
|
|
insert(childs[i],(iprop == 1) and v or v:Clone())
|
|
elseif bind(i) then
|
|
binds[i] = v
|
|
elseif PcallGetProperty(v,"__type") == "quad_linker" then
|
|
links[i] = v
|
|
else
|
|
parsed[i] = v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
item = func(parsed)
|
|
local holder = getHolder(item)
|
|
for _,v in ipairs(childs) do
|
|
mountfunc(item,v,holder)
|
|
end
|
|
for i,v in pairs(links) do
|
|
Link(v,item,i,type(i))
|
|
end
|
|
for i,v in pairs(binds) do
|
|
bind(item,i,v)
|
|
end
|
|
return item
|
|
end
|
|
item = func()
|
|
end
|
|
if not item then -- if cannot make new object, ignore this call
|
|
local str = tostring(ClassName)
|
|
warn(("[Quad] fail to make item '%s', did you forget to checking the class '%s' is exist?"):format(str,str))
|
|
end
|
|
|
|
-- set property and adding child and binding functions
|
|
local holder = getHolder(item) -- to __holder or holder or it self (for tabled)
|
|
-- for iprop,prop in ipairs({...}) do
|
|
local propsListArray = pack(...)
|
|
local processedProperty = {}
|
|
for iprop = 1,propsListArray.n do
|
|
local prop = propsListArray[iprop]
|
|
if prop then
|
|
local binding = {}
|
|
-- set property
|
|
for index,value in pairs(prop) do
|
|
if bind(index) then
|
|
binding[index] = value
|
|
elseif type(index) ~= "number" then
|
|
ProcessQuadProperty(processedProperty,iprop,holder,item,ClassName,index,value)
|
|
end
|
|
end
|
|
-- childs
|
|
for index,value in ipairs(prop) do
|
|
ProcessQuadProperty(processedProperty,iprop,holder,item,ClassName,index,value)
|
|
end
|
|
-- bindings
|
|
for index,value in pairs(binding) do
|
|
bind(item,index,value)
|
|
end
|
|
end
|
|
end
|
|
return item
|
|
end
|
|
local make = new.Make
|
|
|
|
-- import quad object
|
|
function new.Import(ClassName,defaultProperties) -- make new quad class object
|
|
if type(ClassName) == "userdata" and ClassName.ClassName == "ModuleScript" then
|
|
ClassName = require(ClassName)
|
|
end
|
|
local this = defaultProperties or {}
|
|
setmetatable(this,{
|
|
__type = "quad_imported_class",
|
|
__call = function (self,prop,...)
|
|
local propType = type(prop)
|
|
if propType == "string" then
|
|
local lastName = prop
|
|
return function (nprop,...)
|
|
nprop = nprop or {}
|
|
if not nprop.Name then
|
|
nprop.Name = gsub(gsub(match(lastName,"[^,]+")," +$",""),"^ +","")
|
|
end
|
|
for styleName,styleObj in pairs(styleList) do
|
|
if match(prop,styleName) then
|
|
insert(nprop,styleObj)
|
|
end
|
|
end
|
|
local item = make(ClassName,nprop,...,this)
|
|
addObject(lastName,item)
|
|
return item
|
|
end
|
|
elseif propType == "nil" then
|
|
return make(ClassName,this)
|
|
end
|
|
return make(ClassName,prop,...,this)
|
|
end
|
|
})
|
|
return this
|
|
end
|
|
|
|
-- set module calling function
|
|
setmetatable(new,{
|
|
__call = function (self,...)
|
|
return new.Import(...)
|
|
end
|
|
})
|
|
|
|
local function Linker(target,name)
|
|
return {
|
|
__type = "quad_linker";
|
|
target = target;
|
|
name = name;
|
|
}
|
|
end
|
|
|
|
-- make class
|
|
function new.Extend()
|
|
local this = {__type = "quad_extend"}
|
|
|
|
--- make new object
|
|
function this.New(prop)
|
|
-- make metatable
|
|
prop = storeNew(prop,nil)
|
|
local parent = prop and (prop.Parent or prop.parent)
|
|
local self = {
|
|
__prop = prop;
|
|
__parent = parent;
|
|
__propertyChangedSignals = {};
|
|
}
|
|
local init = this.Init
|
|
if init then
|
|
init(self,prop)
|
|
end
|
|
setmetatable(self,this)
|
|
initStoreRegisterBinding(prop,this)
|
|
|
|
-- render that
|
|
local object = self:Render(prop)
|
|
rawset(self,"__object",object)
|
|
rawset(self,"__holder",object)
|
|
if parent then
|
|
rawset(self,"__parent",parent)
|
|
mountfunc(self,parent)
|
|
end
|
|
-- prevent gc
|
|
((type(object) == "table" and object.__object) or object)
|
|
:GetPropertyChangedSignal "ClassName":Connect(function()
|
|
return self
|
|
end)
|
|
|
|
--after render
|
|
local afterRender = this.AfterRender
|
|
if afterRender then
|
|
afterRender(self,object,prop)
|
|
end
|
|
return self
|
|
end
|
|
|
|
function this:GetPropertyChangedSignal(propertyName)
|
|
local thisSignal = self.__propertyChangedSignals[propertyName]
|
|
if thisSignal then
|
|
return thisSignal
|
|
end
|
|
thisSignal = bindable.New()
|
|
self.__propertyChangedSignals[propertyName] = thisSignal
|
|
return thisSignal
|
|
end
|
|
|
|
function this:EmitPropertyChangedSignal(propertyName,value)
|
|
local thisSignal = self.__propertyChangedSignals[propertyName]
|
|
if not thisSignal then
|
|
return
|
|
end
|
|
thisSignal:Fire(value)
|
|
end
|
|
|
|
--- re-render object (if some props chaged, call this to render again)
|
|
function this:Update() -- swap object
|
|
local object = rawget(self,"__object") -- get last instance
|
|
local parent = object and object.Parent -- date parent
|
|
if not object then
|
|
parent = rawget(self,"__parent")
|
|
end
|
|
local lastObject = object
|
|
if lastObject then
|
|
lastObject.Parent = nil
|
|
end
|
|
|
|
object = self:Render(self.__prop) -- new instance
|
|
rawset(self,"__holder",object)
|
|
rawset(self,"__object",object)
|
|
object.Parent = getHolder(parent) -- update parent
|
|
|
|
-- restore childs
|
|
local child = rawget(self,"__child")
|
|
if child then
|
|
for _,v in pairs(child) do
|
|
if v then
|
|
v.Parent = object
|
|
end
|
|
end
|
|
end
|
|
|
|
-- prevnet gc
|
|
((type(object) == "table" and object.__object) or object):GetPropertyChangedSignal "ClassName":Connect(function()
|
|
return self
|
|
end)
|
|
if lastObject then -- remove old instance
|
|
self:Destroy(lastObject)
|
|
end
|
|
|
|
--after render
|
|
local afterRender = this.AfterRender
|
|
if afterRender then
|
|
afterRender(self,object)
|
|
end
|
|
end
|
|
|
|
-- define destroy method
|
|
function this:Destroy(object)
|
|
local unload = this.Unload
|
|
object = object or rawget(self,"__object")
|
|
if object then
|
|
if unload then
|
|
unload(self,object)
|
|
else
|
|
local destroy = (object.Destroy or object.destroy)
|
|
if destroy then
|
|
pcall(destroy,object)
|
|
end
|
|
end
|
|
end
|
|
self = nil
|
|
end
|
|
|
|
--- link to new
|
|
function this.__call(self,...)
|
|
if self == this then
|
|
return (self.new or self.New or self.__new)(...)
|
|
end
|
|
|
|
local name = select(1,...)
|
|
if type(name) ~= "string" then
|
|
error(("Name must be string, but got %s"):format(type(name)))
|
|
end
|
|
return Linker(self,name)
|
|
end
|
|
|
|
-- indexer (getter)
|
|
function this:__index(k)
|
|
local getter = this.Getter[k]
|
|
if getter then
|
|
return getter(self,rawget(self,"__object"))
|
|
end
|
|
|
|
-- from this (have more priority)
|
|
local fromThis = this[k]
|
|
if fromThis then
|
|
return fromThis
|
|
end
|
|
|
|
-- from prop, not start with '_' (private value)
|
|
if k:sub(1,1) ~= "_" then
|
|
return self.__prop[k]
|
|
end
|
|
end
|
|
|
|
--- new indexer (setter)
|
|
function this:__newindex(k,v)
|
|
-- from setter
|
|
local setter = this.Setter[k]
|
|
if setter then
|
|
return setter(self,v,rawget(self,"__object"))
|
|
end
|
|
|
|
-- is private (self value)
|
|
if k == "holder" or k:sub(1,1) == "_" then
|
|
return rawset(self,k,v)
|
|
end
|
|
|
|
-- prop update
|
|
self.__prop[k] = v
|
|
if this.UpdateTriggers[k] then
|
|
self:Update()
|
|
end
|
|
|
|
-- call PropertyChangedSignal
|
|
if self.__propertyChangedSignals[k] and self[k] ~= v then
|
|
self:EmitPropertyChangedSignal(k,v)
|
|
end
|
|
end
|
|
|
|
this.Getter = {
|
|
Parent = function(self)
|
|
return rawget(self,"__parent")
|
|
end
|
|
}
|
|
this.Setter = {
|
|
Parent = function(self,parent,object)
|
|
rawset(self,"__parent",parent)
|
|
if object then
|
|
object.Parent = getHolder(parent)
|
|
end
|
|
end
|
|
}
|
|
this.UpdateTriggers = {}
|
|
this.__noSetup = true
|
|
return this
|
|
end
|
|
|
|
return new
|
|
end
|
|
|
|
return module
|