diff --git a/build.rbxmx b/build.rbxmx index 68ad819..cb10f08 100644 --- a/build.rbxmx +++ b/build.rbxmx @@ -28,19 +28,19 @@ frame{ ]] -require = require(script.require); -local event = require("event"); -local store = require("store"); -local style = require("style"); -local class = require("class"); -local mount = require("mount"); +local require = require(script.require); +local event = require("event"); ---@module "src.event" +local store = require("store"); ---@module "src.store" +--local style = require("style"); +local class = require("class"); ---@module "src.class" +local mount = require("mount"); ---@module "src.mount" function module.init() local this = {items = {}}; this.event = event.init(this); this.store = store.init(this); - this.style = style.init(this); + --this.style = style.init(this); this.class = class.init(this); this.mount = mount.init(this); @@ -55,22 +55,34 @@ return module; ]]> - - bind - - - - class 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 bind = shared.event.bind; local addObject = shared.store.addObject; + local storeNew = shared.store.new; - function new.make(ClassName,prop) + function new.getHolder(item) + return (type(item) == "table") and (item._holder or item.holder or item.__holder) or item; + end + local getHolder = new.getHolder; + + -- make object that from instance, class and more + function new.make(ClassName,...) -- render object -- make thing local item; local classOfClassName = type(ClassName); @@ -80,6 +92,32 @@ function module.init(shared) 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 = {},{...}; + local parsed; + for iprop = #props,1,-1 do + local prop = props[iprop]; + for i,v in ipairs(prop) do + childs[i] = ((iprop == 1) and v or v:Clone()); + prop[i] = nil; + end + if next(prop) then -- there are (key/value)s + if parsed then + for i,v in pairs(prop) do + parsed[i] = v; + end + else + parsed = prop; + end + end + end + item = func(parsed); + local holder = getHolder(item); + for _,v in ipairs(childs) do + v.Parent = holder; + end + return item; + end item = func(); end if not item then -- if cannot make new object, ignore this call @@ -88,24 +126,54 @@ function module.init(shared) end -- set property and adding child and binding functions - for index,value in pairs(prop) do - local valueType = typeof(value); - local indexType = typeof(index); + local holder = getHolder(item); -- to __holder or holder or it self (for tabled) + -- for iprop,prop in ipairs({...}) do + for iprop = select("#",...),1,-1 do + local prop = select(iprop,...); + for index,value in pairs(prop) do + local valueType = typeof(value); + local indexType = typeof(index); - -- child - if indexType ~= "string" then -- object - value.Parent = new; - elseif valueType == "function" and bind(value) then -- connect event - elseif indexType == "string" then - new[index] = value; -- set property + -- child + if indexType == "string" and valueType == "table" and value.t == "reg" then -- register (bind to store event) + -- store binding + local with = value.wfunc; + local set = value.store[value.key]; + if set then + if with then + set = with(set); + end + item[index] = set; + else + local dset = value.dvalue; + if dset then + item[index] = dset; + end + end + value.register(function (newValue,store) + if with then + newValue = with(newValue,item,store); + end + item[index] = newValue; + end); + elseif (valueType == "function" or valueType == "table") and bind(item,index,value,valueType) then -- connect event + -- event binding + elseif indexType == "string" then + -- prop set + item[index] = value; -- set property + elseif indexType == "number" then -- object + -- child object + ((iprop == 1) and value or value:Clone()).Parent = holder; + end end end return item; end local make = new.make; - function new.import(ClassName) -- make new quad class object - local this = {styles = {}}; + -- import quad object + function new.import(ClassName,defaultProperties) -- make new quad class object + local this = defaultProperties or {}; setmetatable(this,{ __call = function (self,prop) if type(prop) == "string" then @@ -117,26 +185,159 @@ function module.init(shared) return item; end; end - return make(ClassName,prop); + 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 self.import(...); + 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); + object.Parent = getHolder(parent); + 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 = rawget(self,"__parent") or (object and object.Parent); -- date parent + 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 + if lastObject then -- remove old instance + self:Destroy(lastObject); + 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 + 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; - + event local module = {}; @@ -144,10 +345,9 @@ return module; function module.init(shared) local new = {}; local unpack = table.unpack; - local coroutineCreate = coroutine.create; - local coroutineResume = coroutine.resume; + local wrap = coroutine.wrap; - local prefix = "event::"; + local prefix = "Event::"; local special = { ["Property::(.+)"] = function (this,func,property) this.GetPropertyChangedSignal(property):Connect(function() @@ -158,7 +358,7 @@ function module.init(shared) func(this); end; ["Created::"] = function (this,func) - coroutineResume(coroutineCreate(func),this); + wrap(func)(this); end; }; @@ -170,10 +370,19 @@ function module.init(shared) }); -- try binding, if key dose match with anything, ignore call - function new.bind(this,key,func) + function new.bind(this,key,func,typefunc) + -- if is advanced binding (self setted) + typefunc = typefunc or type(func); + local self; + if typefunc == "table" then + self = func.self; + func = func.func; + end -- check prefix if key:sub(1,prefixLen) ~= prefix then return; + elseif not func then + return true; end key = key:sub(prefixLen + 1,-1); @@ -190,7 +399,7 @@ function module.init(shared) local event = this[key]; if event then event:Connect(function(...) - func(this,...); + func(self or this,...); end); return true; end @@ -198,7 +407,7 @@ function module.init(shared) -- property changed binding local prefixProperty = prefix .. "Property::"; - function new.property(name) + function new.prop(name) return prefixProperty .. name; end @@ -211,7 +420,7 @@ end return module; - + mount local module = {}; @@ -219,14 +428,33 @@ return module; function module.init(shared) local new = {}; + local mount = {}; + mount.__index = mount; + function mount:unmount() + local this = self.this; + local typeThis = type(this); + if typeThis == "userdata" then + this:Destroy(); + elseif typeThis == "table" then + pcall(function() + this.Parent = nil; + end); + local destroyThis = this.Destroy; + if destroyThis then + pcall(destroyThis); + end + end + end + -- we should add plugin support function new.mount(to,this) this.Parent = to; + return setmetatable({to = to,this = this},mount); end setmetatable(new,{ __call = function (self,...) - self.mount(...); + return new.mount(...); end; }); @@ -236,7 +464,7 @@ end return module; - + require -- remove the roblox's require system, @@ -272,7 +500,7 @@ return function (query) end; - + store - - - style - local module = {}; - -function module.init(shared) - local new = {}; - - function shared.new(property) - - end - - return new; -end - -return module; - - \ No newline at end of file diff --git a/mybutton.lua b/mybutton.lua new file mode 100644 index 0000000..0bec8c0 --- /dev/null +++ b/mybutton.lua @@ -0,0 +1,52 @@ +local quad = require(script.Parent.quad); +local render = quad.init(); +local class = render.class; +local event = render.event; + +local myButton = class.extend(); -- 클래스 만들기 +local imageButton = class.import "ImageButton"; -- 로블 인스턴스 클래스화 +local textLabel = class.import "TextLabel"; +local textButton = class.import "TextButton"; + +-- 인스턴스의 기본값 설정 +imageButton.BackgroundTransparency = 1; +textButton.BorderSizePixel = 0; +textLabel.Size = UDim2.fromScale(1,1); + +-- 기본 값 설정 +function myButton:init(props) + props.style = props.style or "round"; -- flat ... + props.roundSize = props.roundSize or 12; + props.Text = props.Text or "Button Text"; + props.Size = props.Size or UDim2.fromOffset(120,32); +end + +-- 렌더링 +function myButton:render(props) + if props.style == "round" then + return imageButton { + Size = props "Size"; + Image = "http://www.roblox.com/asset/?id=4668069300"; + ScaleType = Enum.ScaleType.Slice; + SliceCenter = Rect.new(Vector2.new(516,516),Vector2.new(516,516)); + SliceScale = props "roundSize":with(function (value) + return 0.0019166666666667 * props.roundSize; + end); + [event "MouseButton1Click"] = { + self = self; + func = props.MouseButton1Click; + }; + textLabel { + Text = props "Text"; + }; + }; + elseif props.style == "flat" then + return textButton { + Size = props "Size"; + BorderSizePixel = 2; + } + else error ("unknown style " .. props.style); + end +end + +myButton.updateTriggers.style = true; diff --git a/src/class.lua b/src/class.lua index 9fe33d9..a73ebcc 100644 --- a/src/class.lua +++ b/src/class.lua @@ -15,7 +15,14 @@ function module.init(shared) local new = {}; local bind = shared.event.bind; local addObject = shared.store.addObject; + local storeNew = shared.store.new; + function new.getHolder(item) + return (type(item) == "table") and (item._holder or item.holder or item.__holder) or item; + end + local getHolder = new.getHolder; + + -- make object that from instance, class and more function new.make(ClassName,...) -- render object -- make thing local item; @@ -29,7 +36,8 @@ function module.init(shared) if ClassName.__noSetup then -- if is support initing props local childs,props = {},{...}; local parsed; - for iprop,prop in ipairs(props) do + for iprop = #props,1,-1 do + local prop = props[iprop]; for i,v in ipairs(prop) do childs[i] = ((iprop == 1) and v or v:Clone()); prop[i] = nil; @@ -37,15 +45,15 @@ function module.init(shared) if next(prop) then -- there are (key/value)s if parsed then for i,v in pairs(prop) do - prop[i] = v; + parsed[i] = v; end else parsed = prop; end end end - item = func(props); - local holder = (type(item) == "table") and (item.__holder or item.holder) or item; + item = func(parsed); + local holder = getHolder(item); for _,v in ipairs(childs) do v.Parent = holder; end @@ -59,17 +67,43 @@ function module.init(shared) end -- set property and adding child and binding functions - local holder = (type(item) == "table") and (item.__holder or item.holder) or item; -- to __holder or holder or it self (for tabled) - for iprop,prop in pairs({...}) do + local holder = getHolder(item); -- to __holder or holder or it self (for tabled) + -- for iprop,prop in ipairs({...}) do + for iprop = select("#",...),1,-1 do + local prop = select(iprop,...); for index,value in pairs(prop) do local valueType = typeof(value); local indexType = typeof(index); -- child - if valueType == "function" and bind(value) then -- connect event + if indexType == "string" and valueType == "table" and value.t == "reg" then -- register (bind to store event) + -- store binding + local with = value.wfunc; + local set = value.store[value.key]; + if set then + if with then + set = with(set); + end + item[index] = set; + else + local dset = value.dvalue; + if dset then + item[index] = dset; + end + end + value.register(function (newValue,store) + if with then + newValue = with(newValue,item,store); + end + item[index] = newValue; + end); + elseif (valueType == "function" or valueType == "table") and bind(item,index,value,valueType) then -- connect event + -- event binding elseif indexType == "string" then + -- prop set item[index] = value; -- set property elseif indexType == "number" then -- object + -- child object ((iprop == 1) and value or value:Clone()).Parent = holder; end end @@ -78,6 +112,7 @@ function module.init(shared) end local make = new.make; + -- import quad object function new.import(ClassName,defaultProperties) -- make new quad class object local this = defaultProperties or {}; setmetatable(this,{ @@ -116,6 +151,127 @@ function module.init(shared) 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); + object.Parent = getHolder(parent); + 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 = rawget(self,"__parent") or (object and object.Parent); -- date parent + 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 + if lastObject then -- remove old instance + self:Destroy(lastObject); + 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 + 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 diff --git a/src/event.lua b/src/event.lua index 4c7c32c..465d445 100644 --- a/src/event.lua +++ b/src/event.lua @@ -3,10 +3,9 @@ local module = {}; function module.init(shared) local new = {}; local unpack = table.unpack; - local coroutineCreate = coroutine.create; - local coroutineResume = coroutine.resume; + local wrap = coroutine.wrap; - local prefix = "event::"; + local prefix = "Event::"; local special = { ["Property::(.+)"] = function (this,func,property) this.GetPropertyChangedSignal(property):Connect(function() @@ -17,7 +16,7 @@ function module.init(shared) func(this); end; ["Created::"] = function (this,func) - coroutineResume(coroutineCreate(func),this); + wrap(func)(this); end; }; @@ -29,10 +28,19 @@ function module.init(shared) }); -- try binding, if key dose match with anything, ignore call - function new.bind(this,key,func) + function new.bind(this,key,func,typefunc) + -- if is advanced binding (self setted) + typefunc = typefunc or type(func); + local self; + if typefunc == "table" then + self = func.self; + func = func.func; + end -- check prefix if key:sub(1,prefixLen) ~= prefix then return; + elseif not func then + return true; -- nil binding end key = key:sub(prefixLen + 1,-1); @@ -49,7 +57,7 @@ function module.init(shared) local event = this[key]; if event then event:Connect(function(...) - func(this,...); + func(self or this,...); end); return true; end diff --git a/src/init.lua b/src/init.lua index 0c97e69..68d5481 100644 --- a/src/init.lua +++ b/src/init.lua @@ -23,7 +23,7 @@ frame{ ]] -require = require(script.require); +local require = require(script.require); local event = require("event"); ---@module "src.event" local store = require("store"); ---@module "src.store" --local style = require("style"); diff --git a/src/store.lua b/src/store.lua index 2dd8e57..6bdecb3 100644 --- a/src/store.lua +++ b/src/store.lua @@ -20,6 +20,7 @@ function module.init(shared) local new = {}; local items = shared.items; + -- id space (array of object) local objSpace = {}; function objSpace:each(func) for i,v in ipairs(self) do @@ -50,21 +51,27 @@ function module.init(shared) function objSpace.__new() return setmetatable({},objSpace); end - function objSpace:__newIndex(key,value) + function objSpace:__newIndex(key,value) -- props setter self:each(function (this) this[key] = value; end); end + objSpace.__mode = "kv"; -- week link for gc objSpace.__index = objSpace; + -- get object array with id (objSpace) function new.getObjects(id) return items[id]; end + + -- get first object with id (not array) function new.getObject(id) local item = items[id]; return item and item[1]; end + --TODO: if item is exist already, ignore this call + -- adding object with id function new.addObject(ids,object) for id in ids:gmatch("[^,]+") do -- split by , -- remove trailing, heading spaces @@ -78,6 +85,7 @@ function module.init(shared) end end + -- bindable store object local store = {}; function store:__index(key) return self.__self[key]; @@ -96,20 +104,31 @@ function module.init(shared) if not register then local event = {} self.__evt[key] = event; - register = function (efunc) - insert(event,efunc); - end; + register = { + register = function (efunc) + insert(event,efunc); + end; + with = function (s,efunc) + return setmetatable({wfunc = efunc},{__index = s}); + end; + default = function (s,value) + return setmetatable({dvalue = value},{__index = s}); + end; + key = key; + store = self; + t = "reg"; + }; self.__reg[key] = register; end if func then - register(func); + register.register(func); end - return {register,t="reg"}; + return register; end - function new.new() + function new.new(self) return setmetatable( - {__self = {},__evt = {},__reg = {}},store + {__self = self or {},__evt = {},__reg = {}},store ); end diff --git a/testing.md b/testing.md index e39b399..873c210 100644 --- a/testing.md +++ b/testing.md @@ -32,4 +32,64 @@ store.getObjects "Child".BackgroundColor = Color3.fromRGB(255,0,0); store.getObjects "Child":forEach(function (this) print(this); end); +``` + +# 클래서 +```lua +-- 새로운 클래스를 확장한다 +local myButton = class.extend(); + +-- 클래스를 초기화합니다, 메타 테이블에 영향을 받지 않습니다 +-- 선택적입니다 +function myButton:init(prop) + prop.Text = prop.Text or "Hello world"; -- 기본 값을 설정합니다 +end + +-- 실제 그려질 때 이다 +function myButton:render(prop) + -- if you make value in self, you must be use + -- prefix '_', if you missing that, it will make + -- overflow on meta method!! + self._someValue = 2; + return textButton { + frame { + [event.created] = function (this) + self._holder = this; -- set holder for child instance + end; + }; + --Text = prop.Text; -- 이렇게 하면 업데이트 될 때 다시 그려짐 + Text = prop "Text":default("Hello world"); -- 이렇게 하면 업데이트 될 때 프로퍼티만 고침 + [event "MouseButton1Click"] = { + self = self; + func = prop[event "MouseButton1Click"]; + }; + [event.created] = function (this) + self._button; + end; + }; +end +myButton.ignore.Text = true; + +function myButton.getter:MouseButton1Click(object) + return (self._button or object).MouseButton1Click; + -- ^ this is same ^ +end + +function myButton.setter:Count(v,object) + self._someValue = v; +end +function myButton.getter:Count(object) + return self._someValue; +end + +-- this is option, you can set destroy functions +function myButton:unload(object) + print("remove myButton",object,self); + object:Destroy(); +end + +-- DONT MAKE THIS! this is will broken class system +-- function myButton.new() +-- end + ``` \ No newline at end of file