PageRenderTime 156ms CodeModel.GetById 40ms app.highlight 45ms RepoModel.GetById 66ms app.codeStats 0ms

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

http://github.com/Asphyxia/Tukui
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.