quad/src/class.lua
세젤귀 고양이들 e1ef8a5dda fixed event handler
2022-02-18 22:54:15 +09:00

366 lines
10 KiB
Lua

local module = {};
-- local exportItemMeta = {
-- __index = function (self,key)
-- local methods = self.__methods;
-- local events = self.__events;
-- return (methods and methods[key]) or (events and events[key]) or (self.getters[key](self.__this));
-- end;
-- __newindex = function (self,key,value)
-- self.__setters[key](self.__this,value);
-- end;
-- };
function module.init(shared)
local new = {};
local InstanceNew = Instance.new;
local event = shared.event; ---@module "src.event"
local bind = event.bind;
local store = shared.store; ---@module "src.store"
local addObject = store.addObject;
local storeNew = store.new;
local mount = shared.mount; ---@module "src.mount"
local getHolder = mount.getHolder;
local mountf = mount.mount;
local advancedTween = shared.tween; ---@module "src.libs.AdvancedTween"
local round = shared.round; ---@module "src.libs.round"
local function setProperty(item,index,value,ClassName)
ClassName = ClassName or item.ClassName;
local isImage = (ClassName == "ImageLabel" or ClassName == "ImageButton");
if index == "roundSize" and isImage then
if not round then
warn "module 'round' needs to be loaded for set images round size but it is not found on 'src.libs'. you should adding that to src.libs directory"
end
round.setRound(item,value);
elseif index == "uiRoundSize" then
local uiCorner = InstanceNew("UICorner",item);
uiCorner.CornerRadius = UDim.new(0,value);
else
item[index] = value; -- set property
end
end
-- make object that from instance, class and more
local pack = table.pack;
function new.make(ClassName,...) -- render object
-- make thing
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 = {},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
childs[i] = ((iprop == 1) and v or v:Clone());
elseif bind(i) then
binds[i] = v;
else
parsed[i] = v;
end
end
end
end
item = func(parsed);
local holder = getHolder(item);
for _,v in ipairs(childs) do
mountf(item,v,holder);
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);
print(("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 props = pack(...);
for iprop = props.n,1,-1 do
local prop = props[iprop];
if prop then
for index,value in pairs(prop) do
local valueType = typeof(value);
local indexType = typeof(index);
-- child
if indexType == "string" and valueType == "table" and value.t == "reg" then -- register (bind to store event)
-- store binding
local with = value.wfunc;
local tstore = value.store;
local rawKey = value.key;
local set = tstore[rawKey];
if set ~= nil or (rawKey:match(",") and with) then
if with then
set = with(tstore,set,value.key,item);
end
setProperty(item,index,set,ClassName);
else
local dset = value.dvalue;
if dset then
setProperty(item,index,dset,ClassName);
end
end
local tween = value.tvalue;
local from = value.fvalue;
-- adding event function
local function regFn(_,newValue,key)
if from then
newValue = from[newValue];
end
if with then
newValue = with(tstore,newValue,key,item);
end
if tween then
if not advancedTween then
return warn "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
advancedTween.RunTween(item,tween,{[index] = newValue});
else
setProperty(item,index,newValue,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
-- prop set
setProperty(item,index,value,ClassName);
elseif indexType == "number" and valueType ~= "boolean" then -- object
-- child object
mountf(item,((iprop == 1) and value or value:Clone()),holder);
end
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,{
__call = function (self,prop,...)
local propType = type(prop);
if propType == "string" then
local lastName = prop;
return function (nprop,...)
nprop = nprop or {};
nprop.Name = lastName;
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
-- function new.export(newFn,setters,getters,methods,events) -- make quad importable class
-- return function ()
-- return setmetatable({
-- __this = newFn();
-- __setters = setters;
-- __getters = getters;
-- __methods = methods;
-- __events = events;
-- },exportItemMeta);
-- end;
-- end
-- set module calling function
setmetatable(new,{
__call = function (self,...)
return new.import(...);
end;
});
-- make class
function new.extend()
local this = {};
--- make new object
function this.new(prop)
-- make metatable
prop = storeNew(prop);
local parent = prop and (prop.Parent or prop.parent);
local self = {__prop = prop; __parent = parent};
local init = this.init;
if init then
init(self,prop);
end
setmetatable(self,this);
-- render that
local object = self:render(prop);
rawset(self,"__object",object);
rawset(self,"__holder",object);
if parent then
rawset(self,"__parent",parent);
mountf(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);
end
return self;
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 = rawget(self,"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,...)
return (self.new or self.New or self.__new)(...);
end;
-- indexer (getter)
function this:__index(k)
if k == "destroy" then
return self.Destroy;
end
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
end
this.getter = {};
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;