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