--========================================================-- -- Scorpio Core System -- -- -- -- Author : kurapica125@outlook.com -- -- Create Date : 2020/07/13 -- -- Update Date : 2021/06/24 -- --========================================================-- --========================================================-- Scorpio "Scorpio.UI.Core" "1.1.0" --========================================================-- import "System.Reactive" --- Clear the property value of the target object local NIL = Namespace.SaveNamespace("Scorpio.UI.NIL", prototype { __tostring = function() return "nil" end }) --- Clear the property value of the settings(so other settings may be used for the property) local CLEAR = Namespace.SaveNamespace("Scorpio.UI.CLEAR", prototype { __tostring = function() return "clear" end }) local isObjectType = Class.IsObjectType local isTypeValidDisabled = System.Platform.TYPE_VALIDATION_DISABLED local isUIObject = UI.IsUIObject local isUIObjectType = UI.IsUIObjectType local isSubType = Class.IsSubType local clone = Toolset.clone local yield = coroutine.yield local tinsert = table.insert local tremove = table.remove local strlower = strlower local tsort = table.sort local isObservable = function(val) return type(val) == "table" and isObjectType(val, IObservable) end local gettable = function(self, key) local val = self[key] if not val or val == NIL or val == CLEAR then val = {} self[key] = val end return val end local parentPath = {} local CHILD_SETTING = 0 -- For children local INSTANT_STYLE_UI_CLASS = {} ---------------------------------------------- -- Helper - Property -- ---------------------------------------------- local _Property = {} local _RecycleHolder = CreateFrame("Frame") _RecycleHolder:Hide() local _PropertyChildName = setmetatable({}, META_WEAKKEY) local _PropertyChildMap = setmetatable({}, { __index = function(self, prop) local val = setmetatable({}, META_WEAKALL) rawset(self, prop, val) return val end }) local _PropertyChildRecycle = setmetatable({}, { __index = function(self, type) if isSubType(type, AnimationGroup) or isSubType(type, ControlPoint) or isSubType(type, Animation) then -- No recycle for the animation type, since they can't change their parent -- No recycle to the mask texture, it's very special since its parent should be the texture's parent rawset(self, type, false) return false else local recycle = Recycle(type, "__" .. Namespace.GetNamespaceName(type):gsub("%.", "_") .. "%d", _RecycleHolder) rawset(self, type, recycle) return recycle end end, __call = function(self, obj) if obj.Disposed then return true end local cls = getmetatable(obj) local recycle = cls and rawget(self, cls) if recycle then obj:SetParent(_RecycleHolder) if obj.ClearAllPoints then obj:ClearAllPoints() end recycle(obj) return true end end }) -- The objservable map local _ObsProp = setmetatable({}, META_WEAKKEY) local function dispatchPropertySetting(cls, prop, setting, oldsetting, root) local settings = _Property[cls] if not settings then return end -- So it doesn't finished the definition if root then oldsetting = settings[prop] elseif settings[prop] and oldsetting ~= settings[prop] then return end settings[prop] = setting for scls in Class.GetSubTypes(cls) do dispatchPropertySetting(scls, prop, setting, oldsetting) end end Runtime.OnTypeDefined = Runtime.OnTypeDefined + function(ptype, cls) if ptype == Class and IsUIObjectType(cls) then if not _Property[cls] then Trace("[Scorpio.UI]Init Property List for %q", tostring(cls)) local super = Class.GetSuperClass(cls) if super and _Property[super] then _Property[cls] = clone(_Property[super]) else _Property[cls] = {} end end local _Prop = System.Property -- Scan the class's property for name, feature in Class.GetFeatures(cls) do if _Prop.Validate(feature) and not _Prop.IsStatic(feature) and _Prop.IsWritable(feature) and not _Prop.IsIndexer(feature) and not __Observable__.IsObservableProperty(feature) then Trace("[Scorpio.UI]Define Property %s for %s", name, tostring(cls)) local ptype = _Prop.GetType(feature) UI.Property { name = name, type = ptype, require = cls, set = function(self, val) self[name] = val end, get = _Prop.IsReadable(feature) and function(self) return self[name] end or nil, default = _Prop.GetDefault(feature), nilable = not _Prop.IsValueRequired(feature), childtype = ptype and IsUIObjectType(ptype) and ptype or nil, } end end end end local applyStylesOnFrame, setCustomStyle local function applyProperty(self, prop, value) --Trace("[Scorpio.UI]Apply Property:%s - %s", prop.name, tostring(value)) -- Check the observable map local map = _ObsProp[self] if map and map[prop] then map[prop]:Unsubscribe() map[prop]:Resubscribe() end if value == nil then if map then map[prop] = nil end if prop.clear then return prop.clear(self) end if prop.default ~= nil then return prop.set(self, clone(prop.default, true)) end if prop.nilable then return prop.set(self, nil) end elseif prop.set then local pset = prop.set if isObservable(value) then if not map then map = {} _ObsProp[self] = map end if not map[prop] then if prop.clear then local clear = prop.clear map[prop] = Observer(function(val) if val ~= nil then return pset(self, val) else return clear(self) end end) elseif prop.nilable then map[prop] = Observer(function(val) return pset(self, val) end) elseif prop.default ~= nil then local dft = prop.default map[prop] = Observer(function(val) if val ~= nil then return pset(self, val) else return pset(self, dft) end end) else map[prop] = Observer(function(val) if val ~= nil then return pset(self,val) end end ) end end value:Subscribe(map[prop]) else -- Check for the child type if value == true and prop.childtype then return end if map then map[prop] = nil end pset(self, clone(value, true)) end elseif prop.childtype and isObservable(value) then if not map then map = {} _ObsProp[self] = map end if not map[prop] then map[prop] = Observer(function(val) if type(val) == "table" and getmetatable(val) == nil then local child, new = prop.get(self) if child then local ok, err = pcall(setCustomStyle, child, nil, val, 1, new) if not ok then Error("[Scorpio.UI]Set custom style to child %q of %s failed - %s", prop.name, self:GetName(true), err) end else Error("[Scorpio.UI]Auto-gen property child %q for %s failed", prop.name, self:GetName(true)) end else prop.clear(self) end end) end value:Subscribe(map[prop]) end end local function getUIPrototype(self) local cls = getmetatable(self) if isUIObjectType(cls) then return cls, true end if self.GetObjectType then return UI[self:GetObjectType()], false end end ---------------------------------------------- -- Helper - Style -- ---------------------------------------------- local _StyleMethods = {} local _StyleOwner local _StyleAccessor local _StyleQueue = Queue() local _ClearQueue = Queue() local _ClassQueue = Queue() local _Recycle = Recycle() local _DefaultStyle = {} local _CustomStyle = setmetatable({}, META_WEAKKEY) local _ClassFrames = {} local _CurrentStyleTarget -- The current style target could be used in other systems(ex. Reactive) -- Combine the sharable settings local function prepareSettings(settings, target, final, cache, paths) -- Simple Check if #settings == 0 and not final then return settings end local isClassSkin = getmetatable(target) == nil local props = _Property[isClassSkin and target[#target] or getUIPrototype(target)] if not props then return final or settings end -- No more operations local needRecycle = not cache cache = cache or _Recycle() if needRecycle and final then for k in pairs(final) do cache[strlower(k)] = k end end final = final or {} local classCnt if isClassSkin then paths = paths or {} classCnt = #target end -- Check the share features provided with N-th index local shares = _Recycle() for k, v in pairs(settings) do local tk = type(k) if tk == "number" then tinsert(shares, k) elseif tk == "string" then local lk = strlower(k) local ek = cache[lk] local prop = props[lk] if isClassSkin then local element paths[classCnt] = k for i = 1, #target do element = __Template__.GetElementType(target[i], unpack(paths, i)) if element then break end end paths[classCnt] = nil if element then tinsert(target, element) tinsert(paths, k) if not ek then cache[lk] = k if type(v) == "table" and getmetatable(v) == nil then final[k] = prepareSettings(v, target, nil, nil, paths) else -- Error but will be checked later final[k] = v end elseif type(final[ek]) == "table" and getmetatable(final[ek]) == nil and type(v) == "table" and getmetatable(v) == nil then -- Combine the share settings final[ek] = prepareSettings(v, target, final[ek], nil, paths) end tremove(target) tremove(paths) elseif prop and prop.childtype then if not ek then cache[lk] = k if type(v) == "table" and getmetatable(v) == nil then final[k]= prepareSettings(v, { prop.childtype } ) else -- Error or observable object will be checked later final[k]= v end elseif type(final[ek]) == "table" and getmetatable(final[ek]) == nil and type(v) == "table" and getmetatable(v) == nil then -- Combine the share settings final[ek] = prepareSettings(v, { prop.childtype }, final[ek]) end elseif not ek then -- No property check here, the error would be raised by the caller cache[lk] = k final[k] = v end else local element = UIObject.GetChild(target, k) or prop and prop.childtype and { prop.childtype } if element then if not ek then cache[lk] = k if type(v) == "table" and getmetatable(v) == nil then final[k]= prepareSettings(v, element) else -- Error or observable object will be checked later final[k]= v end elseif type(final[ek]) == "table" and getmetatable(final[ek]) == nil and type(v) == "table" and getmetatable(v) == nil then -- Combine the share settings final[ek] = prepareSettings(v, element, final[ek]) end elseif not ek then -- No property check here, the error would be raised by the caller cache[lk] = k final[k] = v end end end end if #shares > 0 then tsort(shares) for i = #shares, 1, -1 do -- Could have its own share skins prepareSettings(settings[shares[i]], target, final, cache, paths) end end _Recycle(wipe(shares)) if needRecycle then _Recycle(wipe(cache)) end return final end local function collectPropertyChild(frame) if _ClearQueue[frame] then return end if _ClearQueue.Count == 0 then FireSystemEvent("SCORPIO_UI_COLLECT_PROPERTY_CHILD") end _ClearQueue[frame] = true _ClearQueue:Enqueue(frame) end local function applyStyle(frame) if _StyleQueue[frame] then return end if _StyleQueue.Count == 0 then FireSystemEvent("SCORPIO_UI_APPLY_STYLE") end _StyleQueue[frame] = true _StyleQueue:Enqueue(frame) end local function queueClassFrames(class) if not _ClassQueue[class] then if _ClassQueue.Count == 0 then FireSystemEvent("SCORPIO_UI_UPDATE_CLASS_SKIN") end _ClassQueue[class] = true _ClassQueue:Enqueue(class) end for scls in Class.GetSubTypes(class) do queueClassFrames(scls) end end local function emptyDefaultStyle(settings) if settings and settings ~= NIL and settings ~= CLEAR then for k, v in pairs(settings) do if k == CHILD_SETTING then for child, csetting in pairs(v) do emptyDefaultStyle(csetting) end else settings[k] = CLEAR end end end return settings end function setCustomStyle(target, pname, value, stack, nodirectapply) local custom = gettable(_CustomStyle, target) local haschanges = false local directapply = not _StyleQueue[target] and not nodirectapply local props = _Property[getUIPrototype(target)] if not props then error("The target has no property definitions", stack + 1) end if pname then local prop = props[pname] local cval if prop.childtype then cval = true -- So the system can track the child property directapply = false if value == nil or value == CLEAR or value == NIL then cval = value or CLEAR elseif type(value) == "table" and getmetatable(value) == nil then local child, new= prop.get(target) if child then setCustomStyle(child, nil, value, stack + 1, new) else error(strformat("The target has no child element from %q", pname), stack + 1) end elseif isObservable(value) then -- So the property child could be generatd dynamically cval = value directapply = true else error(strformat("The %q is a child poperty, its setting should be a table", pname), stack + 1) end else if value == nil or value == NIL or value == CLEAR then directapply = false cval = value or CLEAR elseif isObservable(value) then cval = value else if prop.validate then local ret, msg = prop.validate(prop.type, value) if msg then error(Struct.GetErrorMessage(msg, prop.name), stack + 1) end value = ret end cval = value end end if custom[pname] ~= cval then custom[pname] = cval haschanges = true if directapply then return applyProperty(target, prop, cval) end end else if type(value) ~= "table" then error("The style settings must be property key value pair or a table contains the key-value pairs", stack + 1) end value = prepareSettings(value, target) -- Copy and remove the share settings directapply = directapply and _Recycle() for pn, pv in pairs(value) do if type(pn) ~= "string" then error("The style property name must be string", stack + 1) end local child = UIObject.GetChild(target, pn) if child then setCustomStyle(child, nil, pv, stack + 1) else local cval local ln = strlower(pn) local prop = props[ln] local isclear = false if not prop then error(strformat("The %q isn't a valid property for the target", pn), stack + 1) end if prop.childtype then cval = true if pv == CLEAR or pv == NIL then cval = pv isclear = true elseif type(pv) == "table" and getmetatable(pv) == nil then local new child, new = prop.get(target) if child then setCustomStyle(child, nil, pv, stack + 1, new) else error(strformat("The target has no child element from %q", pn), stack + 1) end else error(strformat("The %q is a child poperty, its setting should be a table", pname), stack + 1) end else if pv == NIL or pv == CLEAR then cval = pv isclear = true elseif isObservable(pv) then cval = pv else if prop.validate then local ret, msg = prop.validate(prop.type, pv) if msg then error(Struct.GetErrorMessage(msg, prop.name), stack + 1) end pv = ret end cval = pv end end if custom[ln] ~= cval then custom[ln] = cval haschanges = true if directapply then if isclear then _Recycle(wipe(directapply)) directapply = nil else directapply[ln] = cval end end end end end if directapply then -- Instant apply custon settings, no need queue it _CurrentStyleTarget = target local ok, err = pcall(applyStylesOnFrame, target, directapply) if not ok then Error("[Scorpio.UI]Apply Style Failed: %s", tostring(err)) end _CurrentStyleTarget = nil end end return haschanges and applyStyle(target) end local function registerFrame(cls, frame) local map = _ClassFrames[cls] if not map then map = setmetatable({}, META_WEAKKEY) _ClassFrames[cls] = map end map[frame] = true applyStyle(frame) end local function unregisterFrame(frame) _ClassFrames[getmetatable(frame)][frame] = nil end ---------------------------------------------- -- Helper - Skin -- ---------------------------------------------- local _Skins = {} local _ActiveSkin = {} local function copyBaseSkinSettings(container, base) for k, v in pairs(base) do if k == CHILD_SETTING then for name, setting in pairs(v) do copyBaseSkinSettings(gettable(gettable(container, k), name), setting) end else container[k] = v end end end local function saveSkinSettings(classes, paths, container, settings) if type(settings) ~= "table" then throw("The skin settings for " .. class .. "must be table") end local pathIdx = #classes local class = classes[pathIdx] local props = _Property[class] settings = prepareSettings(settings, classes) -- Copy and remove the share settings -- Check inherit for name, value in pairs(settings) do if type(name) ~= "string" then throw("The skin settings only accpet string values as key") end if strlower(name) == "inherit" then settings[name] = nil if type(value) ~= "string" then throw("The inherit only accpet skin name as value") end local base = _Skins[strlower(value)] if not base then throw(strformat("The skin named %q doesn't existed", value)) elseif not base[class] then throw(strformat("The skin named %q doesn't provide skin for %s", value, tostring(class))) end copyBaseSkinSettings(container, base[class]) break end end for name, value in pairs(settings) do local element paths[pathIdx] = name for i = 1, pathIdx do element = __Template__.GetElementType(classes[i], unpack(paths, i)) if element then break end end paths[pathIdx] = nil if element then tinsert(classes, element) tinsert(paths, name) saveSkinSettings(classes, paths, gettable(gettable(container, CHILD_SETTING), name), value) tremove(classes) tremove(paths) elseif props then name = strlower(name) local prop = props[name] if not prop then throw(strformat("The %q isn't a valid property for %s", name, tostring(class))) end if prop.childtype then container[name] = true -- So we can easily track the child property settings if value == NIL or value == CLEAR or isObservable(value) then if container[CHILD_SETTING] then container[CHILD_SETTING][name] = nil end container[name] = value elseif type(value) == "table" and getmetatable(value) == nil then saveSkinSettings({ prop.childtype }, {}, gettable(gettable(container, CHILD_SETTING), name), value) else throw(strformat("The %q is a child generated from property, need table as settings", name)) end else if value == NIL or value == CLEAR then container[name] = value elseif isObservable(value) then container[name] = value else if prop.validate then local ret, msg = prop.validate(prop.type, value) if msg then throw(Struct.GetErrorMessage(msg, prop.name)) end value = ret end container[name] = value end end else throw("The " .. class .. " has no property definitions") end end end local function copyToDefault(settings, default) for name, value in pairs(settings) do if name == CHILD_SETTING then local childsettings = gettable(default, CHILD_SETTING) for element, setting in pairs(value) do copyToDefault(setting, gettable(childsettings, element)) end else default[name] = value end end end local function activeSkin(name, class, skin, force) if force and _ActiveSkin[class] and _ActiveSkin[class] ~= name then return end if not force and _ActiveSkin[class] == name then return end _ActiveSkin[class] = name local default = emptyDefaultStyle(_DefaultStyle[class]) or {} _DefaultStyle[class] = default copyToDefault(skin, default) queueClassFrames(class) end ---------------------------------------------- -- UIObject -- ---------------------------------------------- __Abstract__() __Sealed__() class "UIObject"(function(_ENV) ---------------------------------------------- -- Helpers -- ---------------------------------------------- local _NameMap = setmetatable({}, META_WEAKKEY) local _ChildMap = setmetatable({}, META_WEAKKEY) local _SetParent = getRealMethodCache("SetParent") local _GetParent = getRealMethodCache("GetParent") local _GetNew = getRealMetaMethodCache("__new") local validate = Struct.ValidateValue local isClass = Class.Validate ---------------------------------------------- -- event -- ---------------------------------------------- -- Fired when parent is changed event "OnParentChanged" ---------------------------------------------- -- Static Methods -- ---------------------------------------------- --- Gets the ui object with the full name __Static__() __Arguments__{ NEString } function FromName(name) local obj for str in name:gmatch("[^%.]+") do if not obj then obj = validate(UI, _G[str], true) else local children = _ChildMap[obj[0]] obj = children and children[str] or UIObject.GetPropertyChild(obj, str) end if not obj then return end end return obj end ---------------------------------------------- -- Methods -- ---------------------------------------------- --- Gets the ui object's name or full name __Final__() function GetName(self, full) local name = _NameMap[self[0]] if name then if full then local globalUI = _G[name] if globalUI and (globalUI == self or IsSameUI(self, globalUI)) then return name else local parent = self:GetParent() local pname = parent and parent:GetName(true) name = _PropertyChildName[self] or name if pname then return pname .. "." .. name end end else return name end else local getName = self.GetName if getName ~= GetName then return getName(self) end end end __Final__() function GetDebugName(self) return self:GetName(true) end --- Gets the parent of ui object __Final__() function GetParent(self) local parent = (_GetParent[getmetatable(self)] or self.GetParent)(self) return parent and GetProxyUI(parent) end --- Sets the ui object's name __Final__() __Arguments__{ NEString } function SetName(self, name) local oname = _NameMap[self[0]] if oname == name then return end local globalUI = _G[oname] if globalUI and (globalUI == self or IsSameUI(self, globalUI)) then error("Usage: UI:SetName(name) - UI with global name can't change its name", 2) end local parent = self:GetParent() if parent then local pui = parent[0] local children = _ChildMap[pui] if children and children[name] then error("Usage: UI:SetName(name) - the name is used by another child", 2) end if not children then children = {} _ChildMap[pui] = children end children[oname] = nil children[name] = self end _NameMap[self[0]] = name end --- Sets the ui object's parent __Final__() function SetParent(self, parent) if parent and not isUIObject(parent) then error("Usage : UI:SetParent([parent]) : the parent is not valid.", 2) end local oparent = self:GetParent() if oparent == parent or IsSameUI(oparent, parent) then return end local name = _NameMap[self[0]] local setParent = _SetParent[getmetatable(self)] if not setParent then error("Usage : UI:SetParent([parent]) : the ui element can't change its parent.", 2) end if oparent then local pui = oparent[0] if _ChildMap[pui] and _ChildMap[pui][name] == self then _ChildMap[pui][name] = nil end end if parent == nil then return pcall(setParent, self, nil) end local pui = parent[0] local children = _ChildMap[pui] if children and children[name] and children[name] ~= self then error("Usage : UI:SetParent([parent]) : parent has another child with the same name.", 2) end setParent(self, GetRawUI(parent)) if not children then children = {} _ChildMap[pui] = children end children[name] = self OnParentChanged(self, parent, oparent) end --- Gets the children of the frame __Iterator__() function GetChilds(self) local children = _ChildMap[self[0]] if children then for name, child in pairs(children) do yield(name, child) end end end --- Gets the child with the given name function GetChild(self, name) if name == 0 then return end local children = _ChildMap[self[0]] return children and children[name] end ---------------------------------------------- -- Dispose -- ---------------------------------------------- function Dispose(self) local ui = self[0] local name = _NameMap[ui] unregisterFrame(self) self:SetParent(nil) -- Dispose the children local children = _ChildMap[ui] if children then for _, obj in pairs(children) do obj:Dispose() end end -- Clear it from the _G local globalUI = _G[name] if globalUI and (globalUI == self or IsSameUI(self, globalUI)) then _G[name] = nil end -- Clear register datas _NameMap[ui] = nil _ChildMap[ui] = nil end ---------------------------------------------- -- Constructor -- ---------------------------------------------- __Final__() __Arguments__{ NEString, UI/UIParent, Any * 0 } function __exist(cls, name, parent, ...) local children = _ChildMap[parent[0]] local object = children and children[name] if object then if getmetatable(object) == cls then return object else throw(("Usage : %s(name, parent, ...) - the parent already has a child named '%s'."):format(Namespace.GetNamespaceName(cls), name)) end end end __Final__() __Arguments__{ UI } function __exist(cls, ui) local proxy = UI.GetProxyUI(ui) if proxy and isClass(getmetatable(proxy)) then return proxy end end __Final__() __Arguments__{ NEString, UI/UIParent, Any * 0 } function __new(cls, name, parent, ...) local self = _GetNew[cls](cls, name, parent, ...) parent = parent[0] local children = _ChildMap[parent] if not children then children = {} _ChildMap[parent] = children end children[name] = self _NameMap[self[0]] = name registerFrame(cls, self) return self end __Final__() __Arguments__{ UI } function __new(cls, ui) local self = { [0] = ui[0] } UI.RegisterProxyUI(self) UI.RegisterRawUI(ui) return self end ---------------------------------------------- -- Meta-Method -- ---------------------------------------------- __index = GetChild end) ---------------------------------------------- -- __Bubbling__ -- ---------------------------------------------- __Sealed__() class "__Bubbling__" (function(_ENV) extend "IApplyAttribute" local getChild = UIObject.GetChild local function getTarget(owner, name) for s in name:gmatch("[^%.]+") do owner = owner and getChild(owner, s) end return owner end ----------------------------------------------------------- -- property -- ----------------------------------------------------------- property "AttributeTarget" { set = false, default = AttributeTargets.Event } ----------------------------------------------------------- -- method -- ----------------------------------------------------------- --- apply changes on the target -- @param target the target -- @param targettype the target type -- @param manager the definition manager of the target -- @param owner the target's owner -- @param name the target's name in the owner -- @param stack the stack level function ApplyAttribute(self, target, targettype, manager, owner, name, stack) local map = self[1] Event.SetEventChangeHandler(target, function(delegate, owner, eventname) if not delegate.PopupeBinded then delegate.PopupeBinded = true for name, events in pairs(map) do local child = type(name) == "number" and owner or getTarget(owner, name) if not child then error(("The child named %q doesn't existed in object of %s"):format(name, tostring(getmetatable(owner)))) end for event in events:gmatch("%w+") do child[event] = child[event] + function(self, ...) return delegate(owner, ...) end end end end end, stack + 1) end ----------------------------------------------------------- -- constructor -- ----------------------------------------------------------- __Arguments__{ struct { [NEString] = NEString } } function __new(_, map) return { map }, true end end) ---------------------------------------------- -- Template -- ---------------------------------------------- __Sealed__() class "__Template__" (function (_ENV) extend "IInitAttribute" local _Template = {} local CHILDREN_MAP = 1 local isUIObjectType = UI.IsUIObjectType local getSuperCTOR = Class.GetSuperMetaMethod local yield = coroutine.yield local getSuperClass = Class.GetSuperClass local tinsert = table.insert local tremove = table.remove local strformat = string.format local tconcat = table.concat local function getElementType(cls, ...) local pathCnt = select("#", ...) local scls = cls repeat local childtree = _Template[scls] local i = 1 local name = select(i, ...) while childtree and i < pathCnt do local child = childtree[name] if child then -- Check if it's created by the child local type = getElementType(child, select(i + 1, ...)) if type then return type, false end end childtree = childtree[CHILDREN_MAP] and childtree[CHILDREN_MAP][name] i = i + 1 name = select(i, ...) end local type = childtree and i == pathCnt and childtree[name] if type then return type, scls == cls end scls = getSuperClass(scls) until not scls end local function parseChildTree(cls, path, elements, supercls, stack) local childtree = {} local container = _Template local pathCnt = #path local pathIdx = pathCnt + 1 -- Save the tree if pathCnt == 0 then _Template[cls] = childtree else container = _Template[cls] for i = 1, #path - 1 do container = container[CHILDREN_MAP][path[i]] end container[CHILDREN_MAP] = container[CHILDREN_MAP] or {} container[CHILDREN_MAP][path[#path]]= childtree end -- Save settings to the tree local subtree for k, v in pairs(elements) do if type(k) == "string" and isUIObjectType(v) then path[pathIdx] = k -- Check if already created by children or super class if getElementType(cls, unpack(path)) or (supercls and getElementType(supercls, unpack(path))) then error(strformat("The the child element named %q is already defined", k), stack + 1) end path[pathIdx] = nil childtree[k] = v elseif type(k) == "number" and type(v) == "table" and getmetatable(v) == nil and not subtree then subtree = v else error("The __Template__'s element type must be an ui object type", stack + 1) end end if subtree then childtree[CHILDREN_MAP] = {} for name, subelements in pairs(subtree) do if type(name) == "string" and type(subelements) == "table" and getmetatable(subelements) == nil then path[pathIdx] = name if getElementType(cls, unpack(path)) or (supercls and getElementType(supercls, unpack(path))) then parseChildTree(cls, path, subelements, supercls, stack + 1) else error(strformat("The child with the path %q doesn't existed", tconcat(path, ".")), stack + 1) end else error("The __Template__'s element's children settings must be a table", stack + 1) end end path[pathIdx] = nil end end local function generateChildren(self, childtree) local temp for k, v in pairs(childtree) do if k ~= CHILDREN_MAP then if INSTANT_STYLE_UI_CLASS[v] then temp = temp or _Recycle() temp[k] = v else v(k, self) end end end if temp then for k, v in pairs(temp) do v(k, self) end _Recycle(wipe(temp)) end if childtree[CHILDREN_MAP] then for name, tree in pairs(childtree[CHILDREN_MAP]) do generateChildren(self:GetChild(name), tree) end end end ----------------------------------------------------------- -- static method -- ----------------------------------------------------------- __Arguments__{ - UIObject, NEString * 1 } __Static__() GetElementType = getElementType ----------------------------------------------------------- -- method -- ----------------------------------------------------------- --- modify the target's definition -- @param target the target -- @param targettype the target type -- @param definition the target's definition -- @param owner the target's owner -- @param name the target's name in the owner -- @param stack the stack level -- @return definition the new definition function InitDefinition(self, target, targettype, definition, owner, name, stack) if targettype == AttributeTargets.Method then if name ~= "__ctor" then error("The __Template__ can only be used on the constructor, not nomral method", stack + 1) end if type(self[1]) ~= "table" then error("The __Template__ lack the element settings", stack + 1) end parseChildTree(owner, {}, self[1], nil, stack + 1) return function(self, ...) local sctor = getSuperCTOR(owner, "__ctor") if sctor then sctor(self, ...) end generateChildren(self, _Template[owner]) return definition(self, ...) end elseif targettype == AttributeTargets.Class then if type(definition) == "table" then local new = { self[0] } local elements = {} for k, v in pairs(definition) do if type(k) == "string" and isUIObjectType(v) then elements[k] = v elseif type(k) == "number" and type(v) == "table" and elements[CHILDREN_MAP] == nil then elements[CHILDREN_MAP] = v else new[k] = v end end parseChildTree(target, {}, elements, self[0], stack + 1) new.__ctor = function(self, ...) local sctor = getSuperCTOR(target, "__ctor") if sctor then sctor(self, ...) end generateChildren(self, _Template[target]) end return new else error("The __Template__ require the class use table of element type settings as definition", stack + 1) end end end ----------------------------------------------------------- -- property -- ----------------------------------------------------------- property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Class } ----------------------------------------------------------- -- constructor -- ----------------------------------------------------------- __Arguments__{ - UIObject/nil } function __new(_, cls) return { [0] = cls }, true end __Arguments__{ RawTable } function __new(_, setting) return { setting }, true end end) ---------------------------------------------- -- Style Property -- ---------------------------------------------- __Sealed__() struct "Scorpio.UI.Property" { name = { type = NEString, require = true }, type = { type = AnyType }, require = { type = ClassType + struct { ClassType }, require = true }, set = { type = Function }, get = { type = Function }, clear = { type = Function }, default = { type = Any }, nilable = { type = Boolean }, childtype = { type = - UIObject }, depends = { type = struct { NEString } }, -- Processed after other properties override = { type = struct { NEString } }, -- override other properties __valid = function(self) if not self.childtype and not self.set then return "%s.set is required" end end, __init = function(self) local setting = { name = self.name, type = self.type, set = self.set, get = self.get, clear = self.clear, default = clone(self.default, true), nilable = self.nilable, childtype = self.childtype, } if self.childtype then -- The child property type should be handled specially local name = self.name local childtype = self.childtype local set = self.set local nilable = self.nilable local clear = self.clear local childname = strlower(self.name) setting.get = function(self, try) local child = _PropertyChildMap[setting][self] if child or try then return child, false end local recycle = _PropertyChildRecycle[childtype] if recycle then child = recycle() if child then child:SetParent(self) end else child = childtype(childname, self) end if child then if set then set(self, child) end _PropertyChildMap[setting][self]= child _PropertyChildName[child] = childname end return child, true end setting.clear = function(self) local child = _PropertyChildMap[setting][self] if not child then return end collectPropertyChild(child) if _PropertyChildRecycle[childtype] then _PropertyChildMap[setting][self] = nil end if clear then clear(self, child) elseif nilable and set then set(self, nil) end end end if self.type then if isTypeValidDisabled then if Struct.Validate(self.type) and not Struct.IsImmutable(self.type) then setting.validate = Struct.ValidateValue end else if Enum.Validate(self.type) then setting.validate = Enum.ValidateValue elseif Struct.Validate(self.type) then setting.validate = Struct.ValidateValue elseif Class.Validate(self.type) then setting.validate = Class.ValidateValue elseif Interface.Validate(self.type) then setting.validate = Interface.ValidateValue end end if setting.default ~= nil and setting.validate then setting.default = setting.validate(setting.type, setting.default) end end if self.depends then setting.depends = {} for i, v in ipairs(self.depends) do setting.depends[i] = strlower(v) end end if self.override then setting.override = {} for i, v in ipairs(self.override) do setting.override[i] = strlower(v) end end local name = strlower(self.name) if isUIObjectType(self.require) then dispatchPropertySetting(self.require, name, setting, nil, true) else for _, cls in ipairs(self.require) do dispatchPropertySetting(cls, name, setting, nil, true) end end end, } ---------------------------------------------- -- Style Accessor -- ---------------------------------------------- -- Style[UnitFrame].HealthBar.Color = { r = 1, g = 0, b = 0 } -- Style[frame].Alpha = 0.5 -- Style[UnitFrame].HealthBar = { color = { r = 1, g = 0, b = 1 }, alpha = 0.8 } local Style = Namespace.SaveNamespace("Scorpio.UI.Style", prototype { __tostring = Namespace.GetNamespaceName, __index = function(self, key) if type(key) == "string" then return _StyleMethods[key] end if isUIObject(key) then _StyleOwner = key return _StyleAccessor end end, __newindex = function(self, key, value) if type(key) == "string" and type(value) == "function" then if _StyleMethods[key] then error(("The method named %s already existed in Scorpio.UI.Style"):format(key), 2) end if Attribute.HaveRegisteredAttributes() then Attribute.SaveAttributes(value, AttributeTargets.Function, 2) local ret = Attribute.InitDefinition(value, AttributeTargets.Function, value, self, key, 2) if ret ~= value then Attribute.ToggleTarget(value, ret) value = ret end Attribute.ApplyAttributes (value, AttributeTargets.Function, nil, self, key, 2) Attribute.AttachAttributes(value, AttributeTargets.Function, self, key, 2) end _StyleMethods[key] = value return end if isUIObject(key) then setCustomStyle(key, nil, value, 2) return end error("The Scorpio.UI.Style access is denied", 2) end }) _StyleAccessor = prototype { __metatable = Style, __index = function(self, key) local target = _StyleOwner if target and type(key) == "string" then local star = UIObject.GetChild(target, key) if star then _StyleOwner = star return _StyleAccessor else _StyleOwner = nil local cls = getUIPrototype(target) local prop = cls and _Property[cls] and _Property[cls][strlower(key)] if prop then if prop.childtype then _StyleOwner = prop.get(target) if _StyleOwner then return _StyleAccessor end elseif prop.get then return prop.get(target) else error("The " .. key .. " property has no get method") end end end end _StyleOwner = nil error("The sub key must be child name or property name", 2) end, __newindex = function(self, key, value) local target = _StyleOwner _StyleOwner = nil if target and type(key) == "string" then local star = UIObject.GetChild(target, key) if star then setCustomStyle(star, nil, value, 2) return else key = strlower(key) local cls = getUIPrototype(target) local prop = cls and _Property[cls] and _Property[cls][key] if prop then setCustomStyle(target, key, value, 2) return end end end error("The sub key must be child name or property name", 2) end } __Iterator__() __Arguments__{ UI } function Style.GetCustomStyles(frame) local custom = _CustomStyle[frame] if custom then local props = _Property[getmetatable(frame)] for name, value in pairs(custom) do yield(props[name].name, (clone(value))) end end end __Arguments__{ - UIObject, NEString * 0 } __Iterator__() function Style.GetDefaultStyles(class, ...) local default = _DefaultStyle[class] if default then if select("#", ...) > 0 then class = __Template__.GetElementType(class, ...) if not class then return end for i = 1, select("#", ...) do local name = select(i, ...) default = default[CHILD_SETTING] default = default and default[name] if not default then return end end end local props = _Property[class] for name, value in pairs(default) do if name ~= 0 then yield(props[name].name, (clone(value))) end end end end ---------------------------------------------- -- Skin System -- ---------------------------------------------- local SkinSettings = struct { [ - UIObject ] = Table } __Arguments__{ NEString, SkinSettings/nil }:Throwable() function Style.RegisterSkin(name, settings) name = strlower(name) if _Skins[name] then return false end local skins = {} _Skins[name] = skins if settings then for class, setting in pairs(settings) do local skin = {} skins[class] = skin saveSkinSettings({class}, {}, skin, setting) end end return true end __Arguments__{ NEString, SkinSettings }:Throwable() function Style.UpdateSkin(name, settings) name = strlower(name) local skins = _Skins[name] if not skins then throw("Usage: Style.UpdateSkin(name, settings) - the name doesn't existed") end for class, setting in pairs(settings) do local skin = emptyDefaultStyle(skins[class]) or {} skins[class] = skin saveSkinSettings({class}, {}, skin, setting) activeSkin(name, class, skin, true) end end __Arguments__{ NEString, - UIObject/nil }:Throwable() function Style.ActiveSkin(name, class) name = strlower(name) local skins = _Skins[name] if not skins then throw("Usage: Style.ActiveSkin(name[, uitype]) - the name doesn't existed") end if class then local skin = skins[class] if not skin then throw("Usage: Style.ActiveSkin(name[, uitype]) - the skin doesn't have settings for " .. class) end return activeSkin(name, class, skin) else for cls, skin in pairs(skins) do activeSkin(name, cls, skin) end end end __Arguments__{ - UIObject } function Style.GetActiveSkin(class) return _ActiveSkin[class] end __Arguments__{ - UIObject } __Iterator__() function Style.GetSkins(class) for name, skins in pairs(_Skins) do if skins[class] then yield(name) end end end __Arguments__{ -UIObject } __Iterator__() function Style.GetProperties(class) local props = _Property[class] if props then for name, prop in pairs(props) do yield(name, prop.childtype or prop.type) end end end __Arguments__{ - UIObject, String } function Style.GetProperty(class, name) local props = _Property[class] local prop = props and props[strlower(name)] if prop then return prop.childtype or prop.type end end function Style.GetCurrentTarget() return _CurrentStyleTarget end Style.RegisterSkin("Default") export { Scorpio.UI.Property } ---------------------------------------------- -- Skin System Services -- ---------------------------------------------- local function genPriority(props, name, styles, priority, lvl) lvl = (lvl or 0) + 1 local maxlvl = lvl for _, dep in ipairs(props[name].depends) do local val = styles[dep] if val ~= nil and val ~= NIL and val ~= CLEAR then priority[dep] = max(priority[dep] or 0, lvl) if props[dep].depends then maxlvl = max(maxlvl, genPriority(props, dep, styles, priority, lvl)) end end end return maxlvl end function applyStylesOnFrame(frame, styles) local props = _Property[getUIPrototype(frame)] if not props then return _Recycle(wipe(styles)) end local priority = _Recycle() local maxlvl = 0 -- Generate the priority to apply the styles based on the depends for name, value in pairs(styles) do if value == NIL or value == CLEAR then applyProperty(frame, props[name], nil) styles[name] = nil elseif props[name].depends then maxlvl = max(maxlvl, genPriority(props, name, styles, priority)) end end -- Process with priority for i = maxlvl, 1, -1 do for name, lv in pairs(priority) do if lv == i and styles[name] ~= nil then applyProperty(frame, props[name], styles[name]) styles[name] = nil end end end -- Process the rest for name, value in pairs(styles) do applyProperty(frame, props[name], value) end _Recycle(wipe(priority)) _Recycle(wipe(styles)) end local function clearStylesOnFrame(frame, styles) local props = _Property[getUIPrototype(frame)] if not props then return _Recycle(wipe(styles)) end local priority = _Recycle() local maxlvl = 0 -- Generate the priority to apply the styles based on the depends for name, value in pairs(styles) do if value == NIL or value == CLEAR then applyProperty(frame, props[name], nil) styles[name] = nil elseif props[name].depends then maxlvl = max(maxlvl, genPriority(props, name, styles, priority)) end end -- Process with priority for i = maxlvl, 1, -1 do for name, lv in pairs(priority) do if lv == i and styles[name] ~= nil then applyProperty(frame, props[name], nil) end end end -- Process property with no priority for name, value in pairs(styles) do if not priority[name] then applyProperty(frame, props[name], nil) end end _Recycle(wipe(priority)) _Recycle(wipe(styles)) end local function buildTempStyle(frame, forClear) local styles = _Recycle() local paths = _Recycle() local children = _Recycle() local tempClass = _Recycle() local props = _Property[getUIPrototype(frame)] if not props then return styles, children end -- No properties can be found -- Prepare the style settings -- Custom -> Root Parent Class -> ... -> Parent Class -> Frame Class -> Super Class local name = _PropertyChildName[frame] or UIObject.GetName(frame) local parent = UIObject.GetParent(frame) while parent and name do local cls = getmetatable(parent) tinsert(paths, 1, name) if cls and isUIObjectType(cls) then wipe(tempClass) repeat tinsert(tempClass, cls) cls = Class.GetSuperClass(cls) until not cls for i = #tempClass, 1, -1 do cls = tempClass[i] local default = _DefaultStyle[cls] local index = 1 while default and paths[index] do default = default[CHILD_SETTING] default = default and default[paths[index]] index = index + 1 end if default then -- The parent -> ... -> child style settings for prop, value in pairs(default) do if prop == CHILD_SETTING then for name in pairs(value) do children[name] = true if styles[name] and styles[name] ~= true and isObservable(styles[name]) then -- So don't create the property child dynamicly styles[name] = true end end else if value ~= CLEAR or styles[prop] == nil then styles[prop] = value -- Check childtype if props[prop].childtype then -- So we need create the property child dynamicly if forClear then children[prop] = true elseif isObservable(value) then children[prop] = nil end end -- Check override if props[prop].override then if value ~= CLEAR then for _, op in ipairs(props[prop].override) do styles[op] = nil -- The overridden property won't be applied end else for _, op in ipairs(props[prop].override) do if styles[op] ~= nil and styles[op] ~= CLEAR then styles[prop]= nil break else styles[op] = nil end end end end end end end end end end name = _PropertyChildName[parent] or UIObject.GetName(parent) parent = UIObject.GetParent(parent) end _Recycle(wipe(paths)) _Recycle(wipe(tempClass)) if _CustomStyle[frame] then for prop, value in pairs(_CustomStyle[frame]) do if value ~= CLEAR or styles[prop] == nil then styles[prop] = value -- Check dynamic property child if props[prop].childtype then if forClear then children[prop] = true elseif isObservable(value) then children[prop] = nil end end -- Check override if props[prop].override then if value ~= CLEAR then for _, op in ipairs(props[prop].override) do styles[op] = nil -- The overridden property won't be applied end else for _, op in ipairs(props[prop].override) do if styles[op] ~= nil and styles[op] ~= CLEAR then styles[prop]= nil break else styles[op] = nil end end end end end end end local cls = getmetatable(frame) if isUIObjectType(cls) then while cls do local default = _DefaultStyle[cls] if default then for prop, value in pairs(default) do if prop == CHILD_SETTING then for name in pairs(value) do if forClear or not isObservable(styles[name]) then children[name] = true end end elseif styles[prop] == nil or styles[prop] == CLEAR then local noOverride = true -- Check property child if forClear and props[prop].childtype then children[prop] = true end -- Check override if props[prop].override then for _, op in ipairs(props[prop].override) do if styles[op] ~= nil and styles[op] ~= CLEAR then noOverride = false break else styles[op] = nil -- Clear the CLEAR value styles end end end if noOverride then styles[prop] = value end end end end cls = Class.GetSuperClass(cls) end end return styles, children end local function clearStyle(frame) local props = _Property[getUIPrototype(frame)] if props and not frame.Disposed then local debugname = frame:GetName(true) or frame:GetObjectType() local clearChilds = _Recycle() Trace("[Scorpio.UI]Clear Style: %s%s", debugname, _PropertyChildName[frame] and (" - " .. _PropertyChildName[frame]) or "") local styles, children = buildTempStyle(frame, true) -- Clear the children for name in pairs(children) do local child = UIObject.GetChild(frame, name) if child then clearStyle(child) elseif props[name] and props[name].childtype then child = props[name].get(frame, true) styles[name] = CLEAR if child then clearChilds[child] = true _ClearQueue[child] = true clearStyle(child) end end end _Recycle(wipe(children)) -- Apply the style settings local ok, err = pcall(clearStylesOnFrame, frame, styles) if not ok then Error("[Scorpio.UI]Clear Style: %s - Failed: %s", debugname, tostring(err)) end for child in pairs(clearChilds) do _ClearQueue[child] = nil end _Recycle(wipe(clearChilds)) end _CustomStyle[frame] = nil if _PropertyChildName[frame] and _PropertyChildRecycle(frame) then _PropertyChildName[frame] = nil end Continue() -- Smoothing the process end function ApplyFrameStyles(self, styles, debugname) -- Apply the style settings _CurrentStyleTarget = self local ok, err = pcall(applyStylesOnFrame, self, styles) if not ok then Error("[Scorpio.UI]Apply Style: %s - Failed: %s", debugname, tostring(err)) end _CurrentStyleTarget = nil end __Service__(true) function ApplyStyleService() while true do local frame = _StyleQueue:Dequeue() while frame do if _StyleQueue[frame] then local props = _Property[getUIPrototype(frame)] if props and not frame.Disposed then local debugname = frame:GetName(true) or frame:GetObjectType() Trace("[Scorpio.UI]Apply Style: %s%s", debugname, _PropertyChildName[frame] and (" - " .. _PropertyChildName[frame]) or "") local styles, children = buildTempStyle(frame) -- Queue the children for name in pairs(children) do local child = UIObject.GetChild(frame, name) if child then applyStyle(child) elseif props[name] and props[name].childtype and styles[name] == true then styles[name] = nil child = props[name].get(frame) if child then applyStyle(child) end end end _Recycle(wipe(children)) _StyleQueue[frame] = nil -- Apply the style settings local isProtected = frame.IsProtected if isProtected and isProtected(frame) then NoCombat(ApplyFrameStyles, frame, styles, debugname) else ApplyFrameStyles(frame, styles, debugname) end Continue() -- Smoothing the process else _StyleQueue[frame] = nil end end frame = _StyleQueue:Dequeue() end NextEvent("SCORPIO_UI_APPLY_STYLE") end end __Service__(true) function CollectPropertyChildService() while true do local frame = _ClearQueue:Dequeue() while frame do if _ClearQueue[frame] then _ClearQueue[frame] = nil clearStyle(frame) end Continue() frame = _ClearQueue:Dequeue() end -- For safe for k, v in pairs(_ClearQueue) do if v == true then _ClearQueue[k] = nil end end NextEvent("SCORPIO_UI_COLLECT_PROPERTY_CHILD") end end __Service__(true) function QueueClassFramesService() while true do local class = _ClassQueue:Dequeue() while class do if _ClassQueue[class] then _ClassQueue[class] = nil -- Allow queue again local count = 1 local frames = _ClassFrames[class] if frames then for frame in pairs(frames) do applyStyle(frame) count = count + 1 if count > 30 then count = 1 Continue() end end end end Continue() class = _ClassQueue:Dequeue() end NextEvent("SCORPIO_UI_UPDATE_CLASS_SKIN") end end -- Apply the style on the frame instantly function UIObject:InstantApplyStyle(skipCheck) _StyleQueue[self] = nil local props = _Property[getUIPrototype(self)] if not props then return end local debugname = self:GetName(true) or self:GetObjectType() Trace("[Scorpio.UI]Instant Apply Style: %s%s", debugname, _PropertyChildName[frame] and (" - " .. _PropertyChildName[frame]) or "") local styles, children = buildTempStyle(self) -- Check the location props, dirty but should be only property has relationship if not skipCheck and type(styles["location"]) == "table" then for _, loc in ipairs(styles["location"]) do if type(loc) == "table" and not UIObject.GetRelativeUI(self, loc.relativeTo) then _StyleQueue[self] = true return end end end -- Queue the children for name in pairs(children) do local child = UIObject.GetChild(self, name) children[name] = false if child then children[name] = child elseif props[name] and props[name].childtype and styles[name] == true then styles[name] = nil child = props[name].get(self) if child then children[name] = child end end end for name, child in pairs(children) do if child then UIObject.InstantApplyStyle(child, true) end end _Recycle(wipe(children)) -- Apply the style settings _CurrentStyleTarget = self local ok, err = pcall(applyStylesOnFrame, self, styles) if not ok then Error("[Scorpio.UI]Apply Style: %s - Failed: %s", debugname, tostring(err)) end _CurrentStyleTarget = nil end -- Get the child property name of the frame if it's generated by the property function UIObject:GetChildPropertyName() return _PropertyChildName[self] end -- Get the child generated from the given property name __Arguments__{ String, Boolean/nil } function UIObject:GetPropertyChild(name, create) self = GetProxyUI(self) local props = _Property[getUIPrototype(self)] local prop = props and props[strlower(name)] return prop and prop.childtype and prop.get(self, not create) end -- Get the relative UI Elements based on the name, nil means parent __Arguments__{ NEString/nil } function UIObject:GetRelativeUI(relativeTo) if relativeTo then local rtar -- $parent.xxx.xxx if relativeTo:find(".", 1, true) then for pattern in relativeTo:gmatch("[^%.]+") do if pattern:lower() == "$parent" then rtar = (rtar or self):GetParent() else local p = rtar or self:GetParent() rtar = UIObject.GetChild(p, pattern) or UIObject.GetPropertyChild(p, pattern) end if not rtar then break end end else local p = self:GetParent() rtar = p and (UIObject.GetChild(p, relativeTo) or UIObject.GetPropertyChild(p, relativeTo)) end return rtar or UIObject.FromName(relativeTo) else return self:GetParent() end end --- Gets the relative ui access name __Arguments__{ UI } function UIObject:GetRelativeUIName(frame) local parent = self:GetParent() -- Use nil represent the parent if UI.IsSameUI(frame, parent) then return nil end -- Use brother's name if UI.IsSameUI(frame:GetParent(), parent) then return frame:GetName() end local fullName = UIObject.GetName(self, true) local tarFullName = UIObject.GetName(frame, true) -- Check the max same prefix local index = 1 if fullName and tarFullName then while fullName:byte(index) == tarFullName:byte(index) do index = index + 1 end if index > #fullName and tarFullName:byte(index) == 46 then -- the frame is a child return tarFullName:sub(index + 1, -1) elseif index > #tarFullName and fullName:byte(index) == 46 then -- means parent return (fullName:sub(index + 1, -1):gsub("[^%.]+", "$parent")) end while index > 1 and fullName:byte(index - 1) ~= 46 do index = index - 1 end return index > 1 and (fullName:sub(index, -1):gsub("[^%.]+", "$parent") .. "." .. tarFullName:sub(index, -1)) or tarFullName else return false end end ---------------------------------------------- -- Tool Attribute -- ---------------------------------------------- --- Define a child property based on the target ui class __Sealed__() class "__ChildProperty__" (function(_ENV) extend "IAttachAttribute" ----------------------------------------------------------- -- property -- ----------------------------------------------------------- property "AttributeTarget" { set = false, default = AttributeTargets.Class } ----------------------------------------------------------- -- method -- ----------------------------------------------------------- --- attach data on the target -- @param target the target -- @param targettype the target type -- @param owner the target's owner -- @param name the target's name in the owner -- @param stack the stack level -- @return data the attribute data to be attached function AttachAttribute(self, target, targettype, owner, name, stack) UI.Property { name = self.Name or Namespace.GetNamespaceName(target, true), require = self.Require, childtype = target, } end ----------------------------------------------------------- -- constructor -- ----------------------------------------------------------- __Arguments__{ - UIObject/Frame, NEString/nil } function __ctor(self, reqframe, name) self.Require = reqframe self.Name = name end end) --- Define a UI class to do the InstantApplyStyle __Sealed__() class "__InstantApplyStyle__" (function(_ENV) extend "IInitAttribute" "IApplyAttribute" local getSuperCTOR = Class.GetSuperMetaMethod ----------------------------------------------------------- -- method -- ----------------------------------------------------------- --- modify the target's definition -- @param target the target -- @param targettype the target type -- @param definition the target's definition -- @param owner the target's owner -- @param name the target's name in the owner -- @param stack the stack level -- @return definition the new definition function InitDefinition(self, target, targettype, definition, owner, name, stack) if targettype == AttributeTargets.Method then if name ~= "__ctor" then error("The __InstantApplyStyle__ can only be used on the constructor, not nomral method", stack + 1) end INSTANT_STYLE_UI_CLASS[owner] = true return function(self, ...) definition(self, ...) return _StyleQueue[self] and self:InstantApplyStyle() end end end ----------------------------------------------------------- --- apply changes on the target -- @param target the target -- @param targettype the target type -- @param manager the definition manager of the target -- @param owner the target's owner -- @param name the target's name in the owner -- @param stack the stack level function ApplyAttribute(self, target, targettype, manager, owner, name, stack) if targettype == AttributeTargets.Class then INSTANT_STYLE_UI_CLASS[target] = true local ctor = Class.GetMetaMethod(target, "__ctor") manager.__ctor = ctor and function(self, ...) ctor(self, ...) return _StyleQueue[self] and self:InstantApplyStyle() end or function(self, ...) local sctor = getSuperCTOR(target, "__ctor") if sctor then sctor(self, ...) end return _StyleQueue[self] and self:InstantApplyStyle() end end end ----------------------------------------------------------- -- property -- ----------------------------------------------------------- property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Class } property "Priority" { type = AttributePriority, default = AttributePriority.Lowest } end)