PageRenderTime 35ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Tukui/modules/unitframes/core/oUF/ouf.lua

http://github.com/Asphyxia/Tukui
Lua | 575 lines | 439 code | 108 blank | 28 comment | 93 complexity | f15fa35b2f4bc76b58c8e41458dd4377 MD5 | raw file
  1. local parent, ns = ...
  2. local global = GetAddOnMetadata(parent, 'X-oUF')
  3. local _VERSION = GetAddOnMetadata(parent, 'version')
  4. local oUF = ns.oUF
  5. local Private = oUF.Private
  6. local argcheck = Private.argcheck
  7. local print = Private.print
  8. local error = Private.error
  9. local OnEvent = Private.OnEvent
  10. local styles, style = {}
  11. local callback, units, objects = {}, {}, {}
  12. local select = select
  13. local UnitExists = UnitExists
  14. local conv = {
  15. ['playerpet'] = 'pet',
  16. ['playertarget'] = 'target',
  17. }
  18. local elements = {}
  19. -- updating of "invalid" units.
  20. local enableTargetUpdate = function(object)
  21. local total = 0
  22. object.onUpdateFrequency = object.onUpdateFrequency or .5
  23. object:SetScript('OnUpdate', function(self, elapsed)
  24. if(not self.unit) then
  25. return
  26. elseif(total > self.onUpdateFrequency) then
  27. self:UpdateAllElements'OnUpdate'
  28. total = 0
  29. end
  30. total = total + elapsed
  31. end)
  32. end
  33. Private.enableTargetUpdate = enableTargetUpdate
  34. local iterateChildren = function(...)
  35. for l = 1, select("#", ...) do
  36. local obj = select(l, ...)
  37. if(type(obj) == 'table' and obj.isChild) then
  38. local unit = SecureButton_GetModifiedUnit(obj)
  39. local subUnit = conv[unit] or unit
  40. units[subUnit] = obj
  41. obj.unit = subUnit
  42. obj.id = subUnit:match'^.-(%d+)'
  43. obj:UpdateAllElements"PLAYER_ENTERING_WORLD"
  44. end
  45. end
  46. end
  47. local OnAttributeChanged = function(self, name, value)
  48. if(name == "unit" and value) then
  49. if(self.unit and (self.unit == value or self.realUnit == value)) then
  50. return
  51. else
  52. if(self.hasChildren) then
  53. iterateChildren(self:GetChildren())
  54. end
  55. if(not self:GetAttribute'oUF-onlyProcessChildren') then
  56. local unit = SecureButton_GetModifiedUnit(self)
  57. unit = conv[unit] or unit
  58. units[unit] = self
  59. self.unit = unit
  60. self.id = unit:match"^.-(%d+)"
  61. self:UpdateAllElements"PLAYER_ENTERING_WORLD"
  62. end
  63. end
  64. end
  65. end
  66. local frame_metatable = {
  67. __index = CreateFrame"Button"
  68. }
  69. Private.frame_metatable = frame_metatable
  70. for k, v in pairs{
  71. EnableElement = function(self, name, unit)
  72. argcheck(name, 2, 'string')
  73. argcheck(unit, 3, 'string', 'nil')
  74. local element = elements[name]
  75. if(not element) then return end
  76. if(element.enable(self, unit or self.unit)) then
  77. table.insert(self.__elements, element.update)
  78. end
  79. end,
  80. DisableElement = function(self, name)
  81. argcheck(name, 2, 'string')
  82. local element = elements[name]
  83. if(not element) then return end
  84. for k, update in next, self.__elements do
  85. if(update == element.update) then
  86. table.remove(self.__elements, k)
  87. -- We need to run a new update cycle incase we knocked ourself out of sync.
  88. -- The main reason we do this is to make sure the full update is completed
  89. -- if an element for some reason removes itself _during_ the update
  90. -- progress.
  91. self:UpdateAllElements('DisableElement', name)
  92. break
  93. end
  94. end
  95. return element.disable(self)
  96. end,
  97. Enable = RegisterUnitWatch,
  98. Disable = function(self)
  99. UnregisterUnitWatch(self)
  100. self:Hide()
  101. end,
  102. UpdateAllElements = function(self, event)
  103. local unit = self.unit
  104. if(not UnitExists(unit)) then return end
  105. if(self.PreUpdate) then
  106. self:PreUpdate(event)
  107. end
  108. for _, func in next, self.__elements do
  109. func(self, event, unit)
  110. end
  111. if(self.PostUpdate) then
  112. self:PostUpdate(event)
  113. end
  114. end,
  115. } do
  116. frame_metatable.__index[k] = v
  117. end
  118. local updateActiveUnit = function(self, event, unit)
  119. -- Calculate units to work with
  120. local realUnit, modUnit = SecureButton_GetUnit(self), SecureButton_GetModifiedUnit(self)
  121. -- _GetUnit() doesn't rewrite playerpet -> pet like _GetModifiedUnit does.
  122. if(realUnit == 'playerpet') then
  123. realUnit = 'pet'
  124. end
  125. if(modUnit == "pet" and realUnit ~= "pet") then
  126. modUnit = "vehicle"
  127. end
  128. -- Drop out if the event unit doesn't match any of the frame units.
  129. if(not UnitExists(modUnit) or unit and unit ~= realUnit and unit ~= modUnit) then return end
  130. if(modUnit ~= realUnit) then
  131. self.realUnit = realUnit
  132. else
  133. self.realUnit = nil
  134. end
  135. -- Change the active unit and run a full update.
  136. if(self.unit ~= modUnit) then
  137. self.unit = modUnit
  138. self:UpdateAllElements('RefreshUnit')
  139. return true
  140. end
  141. end
  142. local OnShow = function(self)
  143. if(not updateActiveUnit(self, 'OnShow')) then
  144. return self:UpdateAllElements'OnShow'
  145. end
  146. end
  147. local initObject = function(unit, style, styleFunc, header, ...)
  148. local num = select('#', ...)
  149. for i=1, num do
  150. local object = select(i, ...)
  151. local objectUnit = object:GetAttribute'oUF-guessUnit' or unit
  152. local suffix = object:GetAttribute'unitsuffix'
  153. object.__elements = {}
  154. object.style = style
  155. object = setmetatable(object, frame_metatable)
  156. -- Expose the frame through oUF.objects.
  157. table.insert(objects, object)
  158. -- We have to force update the frames when PEW fires.
  159. object:RegisterEvent("PLAYER_ENTERING_WORLD", object.UpdateAllElements)
  160. -- Handle the case where someone has modified the unitsuffix attribute in
  161. -- oUF-initialConfigFunction.
  162. if(suffix and not objectUnit:match(suffix)) then
  163. objectUnit = objectUnit .. suffix
  164. end
  165. if(not (suffix == 'target' or objectUnit and objectUnit:match'target')) then
  166. object:RegisterEvent('UNIT_ENTERED_VEHICLE', updateActiveUnit)
  167. object:RegisterEvent('UNIT_EXITED_VEHICLE', updateActiveUnit)
  168. -- We don't need to register UNIT_PET for the player unit. We rigester it
  169. -- mainly because UNIT_EXITED_VEHICLE and UNIT_ENTERED_VEHICLE doesn't always
  170. -- have pet information when they fire for party and raid units.
  171. if(objectUnit ~= 'player') then
  172. object:RegisterEvent('UNIT_PET', updateActiveUnit)
  173. end
  174. end
  175. if(not header) then
  176. -- No header means it's a frame created through :Spawn().
  177. object:SetAttribute("*type1", "target")
  178. object:SetAttribute('*type2', 'menu')
  179. -- No need to enable this for *target frames.
  180. if(not (unit:match'target' or suffix == 'target')) then
  181. object:SetAttribute('toggleForVehicle', true)
  182. end
  183. -- Other boss and target units are handled by :HandleUnit().
  184. if(suffix == 'target') then
  185. enableTargetUpdate(object)
  186. elseif(not (unit:match'%w+target' or unit:match'(boss)%d?$' == 'boss')) then
  187. object:SetScript('OnEvent', Private.OnEvent)
  188. end
  189. else
  190. -- Used to update frames when they change position in a group.
  191. object:RegisterEvent('PARTY_MEMBERS_CHANGED', object.UpdateAllElements)
  192. if(num > 1) then
  193. if(object:GetParent() == header) then
  194. object.hasChildren = true
  195. else
  196. object.isChild = true
  197. end
  198. end
  199. if(suffix == 'target') then
  200. enableTargetUpdate(object)
  201. else
  202. object:SetScript('OnEvent', Private.OnEvent)
  203. end
  204. end
  205. styleFunc(object, objectUnit, not header)
  206. object:SetScript("OnAttributeChanged", OnAttributeChanged)
  207. object:SetScript("OnShow", OnShow)
  208. for element in next, elements do
  209. object:EnableElement(element, objectUnit)
  210. end
  211. for _, func in next, callback do
  212. func(object)
  213. end
  214. -- Make Clique happy
  215. _G.ClickCastFrames = ClickCastFrames or {}
  216. ClickCastFrames[object] = true
  217. end
  218. end
  219. local walkObject = function(object, unit)
  220. local parent = object:GetParent()
  221. local style = parent.style or style
  222. local styleFunc = styles[style]
  223. local header = parent:GetAttribute'oUF-headerType' and parent
  224. -- Check if we should leave the main frame blank.
  225. if(object:GetAttribute'oUF-onlyProcessChildren') then
  226. object.hasChildren = true
  227. object:SetScript('OnAttributeChanged', OnAttributeChanged)
  228. return initObject(unit, style, styleFunc, header, object:GetChildren())
  229. end
  230. return initObject(unit, style, styleFunc, header, object, object:GetChildren())
  231. end
  232. function oUF:RegisterInitCallback(func)
  233. table.insert(callback, func)
  234. end
  235. function oUF:RegisterMetaFunction(name, func)
  236. argcheck(name, 2, 'string')
  237. argcheck(func, 3, 'function', 'table')
  238. if(frame_metatable.__index[name]) then
  239. return
  240. end
  241. frame_metatable.__index[name] = func
  242. end
  243. function oUF:RegisterStyle(name, func)
  244. argcheck(name, 2, 'string')
  245. argcheck(func, 3, 'function', 'table')
  246. if(styles[name]) then return error("Style [%s] already registered.", name) end
  247. if(not style) then style = name end
  248. styles[name] = func
  249. end
  250. function oUF:SetActiveStyle(name)
  251. argcheck(name, 2, 'string')
  252. if(not styles[name]) then return error("Style [%s] does not exist.", name) end
  253. style = name
  254. end
  255. do
  256. local function iter(_, n)
  257. -- don't expose the style functions.
  258. return (next(styles, n))
  259. end
  260. function oUF.IterateStyles()
  261. return iter, nil, nil
  262. end
  263. end
  264. local getCondition
  265. do
  266. local conditions = {
  267. raid40 = '[@raid26,exists] show;',
  268. raid25 = '[@raid11,exists] show;',
  269. raid10 = '[@raid6,exists] show;',
  270. raid = '[group:raid] show;',
  271. party = '[group:party,nogroup:raid] show;',
  272. solo = '[@player,exists,nogroup:party] show;',
  273. }
  274. function getCondition(...)
  275. local cond = ''
  276. for i=1, select('#', ...) do
  277. local short = select(i, ...)
  278. local condition = conditions[short]
  279. if(condition) then
  280. cond = cond .. condition
  281. end
  282. end
  283. return cond .. 'hide'
  284. end
  285. end
  286. local generateName = function(unit, ...)
  287. local name = 'oUF_' .. style:gsub('[^%a%d_]+', '')
  288. local raid, party, groupFilter
  289. for i=1, select('#', ...), 2 do
  290. local att, val = select(i, ...)
  291. if(att == 'showRaid') then
  292. raid = true
  293. elseif(att == 'showParty') then
  294. party = true
  295. elseif(att == 'groupFilter') then
  296. groupFilter = val
  297. end
  298. end
  299. local append
  300. if(raid) then
  301. if(groupFilter) then
  302. if(type(groupFilter) == 'number' and groupFilter > 0) then
  303. append = groupFilter
  304. elseif(groupFilter:match'TANK') then
  305. append = 'MainTank'
  306. elseif(groupFilter:match'ASSIST') then
  307. append = 'MainAssist'
  308. else
  309. local _, count = groupFilter:gsub(',', '')
  310. if(count == 0) then
  311. append = groupFilter
  312. else
  313. append = 'Raid'
  314. end
  315. end
  316. else
  317. append = 'Raid'
  318. end
  319. elseif(party) then
  320. append = 'Party'
  321. elseif(unit) then
  322. append = unit:gsub("^%l", string.upper)
  323. end
  324. if(append) then
  325. name = name .. append
  326. end
  327. -- Change oUF_LilyRaidRaid into oUF_LilyRaid
  328. name = name:gsub('(%u%l+)([%u%l]*)%1', '%1')
  329. local base = name
  330. local i = 2
  331. while(_G[name]) do
  332. name = base .. i
  333. i = i + 1
  334. end
  335. return name
  336. end
  337. do
  338. local styleProxy = function(self, frame, ...)
  339. return walkObject(_G[frame])
  340. end
  341. -- There has to be an easier way to do this.
  342. local initialConfigFunction = [[
  343. local header = self:GetParent()
  344. local frames = table.new()
  345. table.insert(frames, self)
  346. self:GetChildList(frames)
  347. for i=1, #frames do
  348. local frame = frames[i]
  349. local unit
  350. -- There's no need to do anything on frames with onlyProcessChildren
  351. if(not frame:GetAttribute'oUF-onlyProcessChildren') then
  352. RegisterUnitWatch(frame)
  353. -- Attempt to guess what the header is set to spawn.
  354. local groupFilter = header:GetAttribute'groupFilter'
  355. if(type(groupFilter) == 'string' and groupFilter:match('MAIN[AT]')) then
  356. local role = groupFilter:match('MAIN([AT])')
  357. if(role == 'T') then
  358. unit = 'maintank'
  359. else
  360. unit = 'mainassist'
  361. end
  362. elseif(header:GetAttribute'showRaid') then
  363. unit = 'raid'
  364. elseif(header:GetAttribute'showParty') then
  365. unit = 'party'
  366. end
  367. local headerType = header:GetAttribute'oUF-headerType'
  368. local suffix = frame:GetAttribute'unitsuffix'
  369. if(unit and suffix) then
  370. if(headerType == 'pet' and suffix == 'target') then
  371. unit = unit .. headerType .. suffix
  372. else
  373. unit = unit .. suffix
  374. end
  375. elseif(unit and headerType == 'pet') then
  376. unit = unit .. headerType
  377. end
  378. frame:SetAttribute('*type1', 'target')
  379. frame:SetAttribute('*type2', 'menu')
  380. frame:SetAttribute('toggleForVehicle', true)
  381. frame:SetAttribute('oUF-guessUnit', unit)
  382. end
  383. local body = header:GetAttribute'oUF-initialConfigFunction'
  384. if(body) then
  385. frame:Run(body, unit)
  386. end
  387. end
  388. header:CallMethod('styleFunction', self:GetName())
  389. local clique = header:GetFrameRef("clickcast_header")
  390. if(clique) then
  391. clique:SetAttribute("clickcast_button", self)
  392. clique:RunAttribute("clickcast_register")
  393. end
  394. ]]
  395. function oUF:SpawnHeader(overrideName, template, visibility, ...)
  396. if(not style) then return error("Unable to create frame. No styles have been registered.") end
  397. template = (template or 'SecureGroupHeaderTemplate')
  398. local isPetHeader = template:match'PetHeader'
  399. local name = overrideName or generateName(nil, ...)
  400. local header = CreateFrame('Frame', name, UIParent, template)
  401. header:SetAttribute("template", "oUF_ClickCastUnitTemplate")
  402. for i=1, select("#", ...), 2 do
  403. local att, val = select(i, ...)
  404. if(not att) then break end
  405. header:SetAttribute(att, val)
  406. end
  407. header.style = style
  408. header.styleFunction = styleProxy
  409. -- We set it here so layouts can't directly override it.
  410. header:SetAttribute('initialConfigFunction', initialConfigFunction)
  411. header:SetAttribute('oUF-headerType', isPetHeader and 'pet' or 'group')
  412. if(Clique) then
  413. SecureHandlerSetFrameRef(header, 'clickcast_header', Clique.header)
  414. end
  415. if(header:GetAttribute'showParty') then
  416. self:DisableBlizzard'party'
  417. end
  418. if(visibility) then
  419. local type, list = string.split(' ', visibility, 2)
  420. if(list and type == 'custom') then
  421. RegisterAttributeDriver(header, 'state-visibility', list)
  422. else
  423. local condition = getCondition(string.split(',', visibility))
  424. RegisterAttributeDriver(header, 'state-visibility', condition)
  425. end
  426. end
  427. return header
  428. end
  429. end
  430. function oUF:Spawn(unit, overrideName)
  431. argcheck(unit, 2, 'string')
  432. if(not style) then return error("Unable to create frame. No styles have been registered.") end
  433. unit = unit:lower()
  434. local name = overrideName or generateName(unit)
  435. local object = CreateFrame("Button", name, UIParent, "SecureUnitButtonTemplate")
  436. object.unit = unit
  437. object.id = unit:match"^.-(%d+)"
  438. units[unit] = object
  439. walkObject(object, unit)
  440. object:SetAttribute("unit", unit)
  441. RegisterUnitWatch(object)
  442. self:DisableBlizzard(unit)
  443. self:HandleUnit(object)
  444. return object
  445. end
  446. function oUF:AddElement(name, update, enable, disable)
  447. argcheck(name, 2, 'string')
  448. argcheck(update, 3, 'function', 'nil')
  449. argcheck(enable, 4, 'function', 'nil')
  450. argcheck(disable, 5, 'function', 'nil')
  451. if(elements[name]) then return error('Element [%s] is already registered.', name) end
  452. elements[name] = {
  453. update = update;
  454. enable = enable;
  455. disable = disable;
  456. }
  457. end
  458. oUF.version = _VERSION
  459. oUF.units = units
  460. oUF.objects = objects
  461. if(global) then
  462. if(parent ~= 'oUF' and global == 'oUF') then
  463. error("%s is doing it wrong and setting its global to oUF.", parent)
  464. else
  465. _G[global] = oUF
  466. end
  467. end