/Tukui/modules/unitframes/plugins/oUF_MovableFrames/movable.lua

http://github.com/Asphyxia/Tukui · Lua · 560 lines · 449 code · 89 blank · 22 comment · 85 complexity · bfe7addd20599b8cd7665333bce58e6b MD5 · raw file

  1. local T, C, L = unpack(select(2, ...)) -- Import: T - functions, constants, variables; C - config; L - locales
  2. if C.unitframes.enable ~= true then return end
  3. local _NAME, _NS = ...
  4. local oUF = _NS.oUF or oUF
  5. assert(oUF, "oUF_MovableFrames was unable to locate oUF install.")
  6. local _DB
  7. local _DBNAME = GetAddOnMetadata(_NAME, 'X-SavedVariables')
  8. local _LOCK
  9. local _TITLE = GetAddOnMetadata(_NAME, 'title')
  10. -- I could use the title field in the TOC, but people tend to put color and
  11. -- other shit there, so we'll just use the folder name:
  12. local slashGlobal = _NAME:gsub('%s+', '_'):gsub('[^%a%d_]+', ''):upper()
  13. slashGlobal = slashGlobal .. '_OMF'
  14. local print_fmt = string.format('|cff33ff99%s:|r', _TITLE)
  15. local print = function(...)
  16. return print(print_fmt, ...)
  17. end
  18. local backdropPool = {}
  19. local getPoint = function(obj, anchor)
  20. if(not anchor) then
  21. local UIx, UIy = UIParent:GetCenter()
  22. local Ox, Oy = obj:GetCenter()
  23. -- Frame doesn't really have a positon yet.
  24. if(not Ox) then return end
  25. local OS = obj:GetScale()
  26. Ox, Oy = Ox * OS, Oy * OS
  27. local UIWidth, UIHeight = UIParent:GetRight(), UIParent:GetTop()
  28. local LEFT = UIWidth / 3
  29. local RIGHT = UIWidth * 2 / 3
  30. local point, x, y
  31. if(Ox >= RIGHT) then
  32. point = 'RIGHT'
  33. x = obj:GetRight() - UIWidth
  34. elseif(Ox <= LEFT) then
  35. point = 'LEFT'
  36. x = obj:GetLeft()
  37. else
  38. x = Ox - UIx
  39. end
  40. local BOTTOM = UIHeight / 3
  41. local TOP = UIHeight * 2 / 3
  42. if(Oy >= TOP) then
  43. point = 'TOP' .. (point or '')
  44. y = obj:GetTop() - UIHeight
  45. elseif(Oy <= BOTTOM) then
  46. point = 'BOTTOM' .. (point or '')
  47. y = obj:GetBottom()
  48. else
  49. if(not point) then point = 'CENTER' end
  50. y = Oy - UIy
  51. end
  52. return string.format(
  53. '%s\031%s\031%d\031%d\031\%.3f',
  54. point, 'UIParent', x, y, OS
  55. )
  56. else
  57. local point, parent, _, x, y = anchor:GetPoint()
  58. return string.format(
  59. '%s\031%s\031%d\031%d\031\%.3f',
  60. point, 'UIParent', x, y, obj:GetScale()
  61. )
  62. end
  63. end
  64. local getObjectInformation = function(obj)
  65. -- This won't be set if we're dealing with oUF <1.3.22. Due to this we're just
  66. -- setting it to Unknown. It will only break if the user has multiple layouts
  67. -- spawning the same unit or change between layouts.
  68. local style = obj.style or 'Unknown'
  69. local identifier = obj:GetName() or obj.unit
  70. -- Are we dealing with header units?
  71. local isHeader
  72. local parent = obj:GetParent()
  73. if(parent) then
  74. if(parent:GetAttribute'initialConfigFunction' and parent.style) then
  75. isHeader = parent
  76. identifier = parent:GetName()
  77. elseif(parent:GetAttribute'oUF-onlyProcessChildren') then
  78. isHeader = parent:GetParent()
  79. identifier = isHeader:GetName()
  80. end
  81. end
  82. return style, identifier, isHeader
  83. end
  84. local restoreDefaultPosition = function(style, identifier)
  85. -- We've not saved any default position for this style.
  86. if(not _DB.__INITIAL or not _DB.__INITIAL[style] or not _DB.__INITIAL[style][identifier]) then return end
  87. local obj, isHeader
  88. for _, frame in next, oUF.objects do
  89. local fStyle, fIdentifier, fIsHeader = getObjectInformation(frame)
  90. if(fStyle == style and fIdentifier == identifier) then
  91. obj = frame
  92. isHeader = fIsHeader
  93. break
  94. end
  95. end
  96. if(obj) then
  97. local target = isHeader or obj
  98. target:ClearAllPoints()
  99. local point, parentName, x, y, scale = string.split('\031', _DB.__INITIAL[style][identifier])
  100. if(not scale) then scale = 1 end
  101. target:_SetScale(scale)
  102. target:_SetPoint(point, parentName, point, x, y)
  103. local backdrop = backdropPool[target]
  104. if(backdrop) then
  105. backdrop:ClearAllPoints()
  106. backdrop:SetAllPoints(target)
  107. end
  108. -- We don't need this anymore
  109. _DB.__INITIAL[style][identifier] = nil
  110. if(not next(_DB.__INITIAL[style])) then
  111. _DB[style] = nil
  112. end
  113. end
  114. end
  115. local function restorePosition(obj)
  116. if(InCombatLockdown()) then return end
  117. local style, identifier, isHeader = getObjectInformation(obj)
  118. -- We've not saved any custom position for this style.
  119. if(not _DB[style] or not _DB[style][identifier]) then return end
  120. local target = isHeader or obj
  121. if(not target._SetPoint) then
  122. target._SetPoint = target.SetPoint
  123. target.SetPoint = restorePosition
  124. target._SetScale = target.SetScale
  125. target.SetScale = restorePosition
  126. end
  127. target:ClearAllPoints()
  128. -- damn it Blizzard, _how_ did you manage to get the input of this function
  129. -- reversed. Any sane person would implement this as: split(str, dlm, lim);
  130. local point, parentName, x, y, scale = string.split('\031', _DB[style][identifier])
  131. if(not scale) then
  132. scale = 1
  133. end
  134. if(scale) then
  135. target:_SetScale(scale)
  136. else
  137. scale = target:GetScale()
  138. end
  139. target:_SetPoint(point, parentName, point, x / scale, y / scale)
  140. end
  141. local restoreCustomPosition = function(style, ident)
  142. for _, obj in next, oUF.objects do
  143. local objStyle, objIdent = getObjectInformation(obj)
  144. if(objStyle == style and objIdent == ident) then
  145. return restorePosition(obj)
  146. end
  147. end
  148. end
  149. local saveDefaultPosition = function(obj)
  150. local style, identifier, isHeader = getObjectInformation(obj)
  151. if(not _DB.__INITIAL) then
  152. _DB.__INITIAL = {}
  153. end
  154. if(not _DB.__INITIAL[style]) then
  155. _DB.__INITIAL[style] = {}
  156. end
  157. if(not _DB.__INITIAL[style][identifier]) then
  158. local point
  159. if(isHeader) then
  160. point = getPoint(isHeader)
  161. else
  162. point = getPoint(obj)
  163. end
  164. _DB.__INITIAL[style][identifier] = point
  165. end
  166. end
  167. local savePosition = function(obj, anchor)
  168. local style, identifier, isHeader = getObjectInformation(obj)
  169. if(not _DB[style]) then _DB[style] = {} end
  170. _DB[style][identifier] = getPoint(isHeader or obj, anchor)
  171. end
  172. local saveCustomPosition = function(style, ident, point, x, y, scale)
  173. -- Shouldn't really be the case, but you never know!
  174. if(not _DB[style]) then _DB[style] = {} end
  175. _DB[style][ident] = string.format(
  176. '%s\031%s\031%d\031%d\031\%.3f',
  177. point, 'UIParent', x, y, scale
  178. )
  179. end
  180. -- Attempt to figure out a more sane name to dispaly.
  181. local smartName
  182. do
  183. local nameCache = {}
  184. local validNames = {
  185. 'player',
  186. 'target',
  187. 'focus',
  188. 'raid',
  189. 'pet',
  190. 'party',
  191. 'maintank',
  192. 'mainassist',
  193. 'arena',
  194. }
  195. local rewrite = {
  196. mt = 'maintank',
  197. mtt = 'maintanktarget',
  198. ma = 'mainassist',
  199. mat = 'mainassisttarget',
  200. }
  201. local validName = function(smartName)
  202. -- Not really a valid name, but we'll accept it for simplicities sake.
  203. if(tonumber(smartName)) then
  204. return smartName
  205. end
  206. if(type(smartName) == 'string') then
  207. -- strip away trailing s from pets, but don't touch boss/focus.
  208. smartName = smartName:gsub('([^us])s$', '%1')
  209. if(rewrite[smartName]) then
  210. return rewrite[smartName]
  211. end
  212. for _, v in next, validNames do
  213. if(v == smartName) then
  214. return smartName
  215. end
  216. end
  217. if(
  218. smartName:match'^party%d?$' or
  219. smartName:match'^arena%d?$' or
  220. smartName:match'^boss%d?$' or
  221. smartName:match'^partypet%d?$' or
  222. smartName:match'^raid%d?%d?$' or
  223. smartName:match'%w+target$' or
  224. smartName:match'%w+pet$'
  225. ) then
  226. return smartName
  227. end
  228. end
  229. end
  230. local function guessName(...)
  231. local name = validName(select(1, ...))
  232. local n = select('#', ...)
  233. if(n > 1) then
  234. for i=2, n do
  235. local inp = validName(select(i, ...))
  236. if(inp) then
  237. name = (name or '') .. inp
  238. end
  239. end
  240. end
  241. return name
  242. end
  243. local smartString = function(name)
  244. if(nameCache[name]) then
  245. return nameCache[name]
  246. end
  247. -- Here comes the substitute train!
  248. local n = name
  249. :gsub('ToT', 'targettarget')
  250. :gsub('(%l)(%u)', '%1_%2')
  251. :gsub('([%l%u])(%d)', '%1_%2_')
  252. :gsub('Main_', 'Main')
  253. :lower()
  254. n = guessName(string.split('_', n))
  255. if(n) then
  256. nameCache[name] = n
  257. return n
  258. end
  259. return name
  260. end
  261. smartName = function(obj, header)
  262. if(type(obj) == 'string') then
  263. return smartString(obj)
  264. elseif(header) then
  265. return smartString(header:GetName())
  266. else
  267. local name = obj:GetName()
  268. if(name) then
  269. return smartString(name)
  270. end
  271. return obj.unit or '<unknown>'
  272. end
  273. end
  274. end
  275. do
  276. local frame = CreateFrame"Frame"
  277. frame:SetScript("OnEvent", function(self, event)
  278. return self[event](self)
  279. end)
  280. function frame:VARIABLES_LOADED()
  281. -- I honestly don't trust the load order of SVs.
  282. if (TukuiDataPerChar == nil) then TukuiDataPerChar = {} end
  283. _DB = TukuiDataPerChar.ufpos or {}
  284. TukuiDataPerChar.ufpos = _DB
  285. -- Got to catch them all!
  286. for _, obj in next, oUF.objects do
  287. restorePosition(obj)
  288. end
  289. oUF:RegisterInitCallback(restorePosition)
  290. self:UnregisterEvent"VARIABLES_LOADED"
  291. end
  292. frame:RegisterEvent"VARIABLES_LOADED"
  293. function frame:PLAYER_REGEN_DISABLED()
  294. if(_LOCK) then
  295. print("Anchors hidden due to combat.")
  296. for k, bdrop in next, backdropPool do
  297. bdrop:Hide()
  298. end
  299. _LOCK = nil
  300. end
  301. end
  302. frame:RegisterEvent"PLAYER_REGEN_DISABLED"
  303. end
  304. local getBackdrop
  305. do
  306. local OnShow = function(self)
  307. return self.name:SetText(smartName(self.obj, self.header))
  308. end
  309. local OnHide = function(self)
  310. if(self.dirtyMinHeight) then
  311. self:SetAttribute('minHeight', nil)
  312. end
  313. if(self.dirtyMinWidth) then
  314. self:SetAttribute('minWidth', nil)
  315. end
  316. end
  317. local OnDragStart = function(self)
  318. saveDefaultPosition(self.obj)
  319. self:StartMoving()
  320. local frame = self.header or self.obj
  321. frame:ClearAllPoints();
  322. frame:SetAllPoints(self);
  323. end
  324. local OnDragStop = function(self)
  325. self:StopMovingOrSizing()
  326. savePosition(self.obj, self)
  327. -- Restore the initial anchoring, so the anchor follows the frame when we
  328. -- edit positions through the UI.
  329. restorePosition(self.obj)
  330. self:ClearAllPoints()
  331. self:SetAllPoints(self.header or self.obj)
  332. end
  333. local OnMouseDown = function(self)
  334. local anchor = self:GetParent()
  335. saveDefaultPosition(anchor.obj)
  336. anchor:StartSizing('BOTTOMRIGHT')
  337. local frame = anchor.header or anchor.obj
  338. frame:ClearAllPoints()
  339. frame:SetAllPoints(anchor)
  340. self:SetButtonState("PUSHED", true)
  341. end
  342. local OnMouseUp = function(self)
  343. local anchor = self:GetParent()
  344. self:SetButtonState("NORMAL", false)
  345. anchor:StopMovingOrSizing()
  346. savePosition(anchor.obj, anchor)
  347. end
  348. local OnSizeChanged = function(self, width, height)
  349. local baseWidth, baseHeight = self.baseWidth, self.baseHeight
  350. local scale = width / baseWidth
  351. -- This is damn tiny!
  352. if(scale <= .3) then
  353. scale = .3
  354. end
  355. self:SetSize(scale * baseWidth, scale * baseHeight)
  356. local target = self. target;
  357. (target._SetScale or target.SetScale) (target, scale)
  358. end
  359. getBackdrop = function(obj, isHeader)
  360. local target = isHeader or obj
  361. if(not target:GetCenter()) then return end
  362. if(backdropPool[target]) then return backdropPool[target] end
  363. local backdrop = CreateFrame"Frame"
  364. backdrop:SetParent(UIParent)
  365. backdrop:Hide()
  366. backdrop:SetTemplate("Default")
  367. backdrop:SetFrameStrata("MEDIUM")
  368. backdrop:SetFrameLevel(20)
  369. backdrop:SetAllPoints(target)
  370. backdrop:EnableMouse(true)
  371. backdrop:SetMovable(true)
  372. backdrop:SetResizable(true)
  373. backdrop:RegisterForDrag"LeftButton"
  374. local name = backdrop:CreateFontString(nil, "OVERLAY", "GameFontNormal")
  375. name:SetPoint"CENTER"
  376. name:SetJustifyH"CENTER"
  377. name:SetFont(C.media.pixelfont, C["datatext"].fontsize+1, "MONOCHROMEOUTLINE")
  378. name:SetTextColor(1, 1, 1)
  379. local scale = CreateFrame('Button', nil, backdrop)
  380. scale:SetPoint'BOTTOMRIGHT'
  381. scale:SetSize(16, 16)
  382. scale:SetNormalTexture[[Interface\ChatFrame\UI-ChatIM-SizeGrabber-Up]]
  383. scale:SetHighlightTexture[[Interface\ChatFrame\UI-ChatIM-SizeGrabber-Highlight]]
  384. scale:SetPushedTexture[[Interface\ChatFrame\UI-ChatIM-SizeGrabber-Down]]
  385. scale:SetScript('OnMouseDown', OnMouseDown)
  386. scale:SetScript('OnMouseUp', OnMouseUp)
  387. backdrop.name = name
  388. backdrop.obj = obj
  389. backdrop.header = isHeader
  390. backdrop.target = target
  391. backdrop:SetBackdropBorderColor(1, 0, 0)
  392. backdrop.baseWidth, backdrop.baseHeight = obj:GetSize()
  393. -- We have to define a minHeight on the header if it doesn't have one. The
  394. -- reason for this is that the header frame will have an height of 0.1 when
  395. -- it doesn't have any frames visible.
  396. if(
  397. isHeader and
  398. (
  399. not isHeader:GetAttribute'minHeight' and math.floor(isHeader:GetHeight()) == 0 or
  400. not isHeader:GetAttribute'minWidth' and math.floor(isHeader:GetWidth()) == 0
  401. )
  402. ) then
  403. isHeader:SetHeight(obj:GetHeight())
  404. isHeader:SetWidth(obj:GetWidth())
  405. if(not isHeader:GetAttribute'minHeight') then
  406. isHeader.dirtyMinHeight = true
  407. isHeader:SetAttribute('minHeight', obj:GetHeight())
  408. end
  409. if(not isHeader:GetAttribute'minWidth') then
  410. isHeader.dirtyMinWidth = true
  411. isHeader:SetAttribute('minWidth', obj:GetWidth())
  412. end
  413. elseif(isHeader) then
  414. backdrop.baseWidth, backdrop.baseHeight = isHeader:GetSize()
  415. end
  416. backdrop:SetScript("OnShow", OnShow)
  417. backdrop:SetScript('OnHide', OnHide)
  418. backdrop:SetScript("OnDragStart", OnDragStart)
  419. backdrop:SetScript("OnDragStop", OnDragStop)
  420. backdrop:SetScript('OnSizeChanged', OnSizeChanged)
  421. backdropPool[target] = backdrop
  422. return backdrop
  423. end
  424. end
  425. -- reset data
  426. local function RESETUF()
  427. if C["unitframes"].positionbychar == true then
  428. TukuiUFpos = {}
  429. else
  430. TukuiDataPerChar.ufpos = {}
  431. end
  432. ReloadUI()
  433. end
  434. SLASH_RESETUF1 = "/resetuf"
  435. SlashCmdList["RESETUF"] = RESETUF
  436. T.MoveUnitFrames = function(inp)
  437. if(InCombatLockdown()) then
  438. return print"Frames cannot be moved while in combat. Bailing out."
  439. end
  440. if(not _LOCK) then
  441. for k, obj in next, oUF.objects do
  442. local style, identifier, isHeader = getObjectInformation(obj)
  443. local backdrop = getBackdrop(obj, isHeader)
  444. if(backdrop) then backdrop:Show() end
  445. end
  446. _LOCK = true
  447. else
  448. for k, bdrop in next, backdropPool do
  449. bdrop:Hide()
  450. end
  451. _LOCK = nil
  452. end
  453. end
  454. -- It's not in your best interest to disconnect me. Someone could get hurt.