--[[ Name: LibJostle-3.0 Revision: $Rev: 43 $ Author(s): ckknight (ckknight@gmail.com) Website: http://ckknight.wowinterface.com/ Documentation: http://wiki.wowace.com/index.php/LibJostle-3.0 SVN: http://svn.wowace.com/root/trunk/JostleLib/LibJostle-3.0 Description: A library to handle rearrangement of blizzard's frames when bars are added to the sides of the screen. License: LGPL v2.1 ]] local MAJOR_VERSION = "LibJostle-3.0" local MINOR_VERSION = tonumber(("$Revision: 43 $"):match("(%d+)")) + 90000 if not LibStub then error(MAJOR_VERSION .. " requires LibStub") end local oldLib = LibStub:GetLibrary(MAJOR_VERSION, true) local Jostle = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) if not Jostle then return end local blizzardFrames = { 'PlayerFrame', 'TargetFrame', 'MinimapCluster', 'PartyMemberFrame1', 'TicketStatusFrame', 'WorldStateAlwaysUpFrame', 'MainMenuBar', 'MultiBarRight', 'CT_PlayerFrame_Drag', 'CT_TargetFrame_Drag', 'Gypsy_PlayerFrameCapsule', 'Gypsy_TargetFrameCapsule', 'TemporaryEnchantFrame', 'DEFAULT_CHAT_FRAME', 'ChatFrame2', 'GroupLootFrame1', 'TutorialFrameParent', 'FramerateLabel', 'QuestTimerFrame', 'DurabilityFrame', 'CastingBarFrame', } local blizzardFramesData = {} local _G = _G Jostle.hooks = oldLib and oldLib.hooks or {} Jostle.topFrames = oldLib and oldLib.topFrames or {} Jostle.bottomFrames = oldLib and oldLib.bottomFrames or {} Jostle.topAdjust = oldLib and oldLib.topAdjust Jostle.bottomAdjust = oldLib and oldLib.bottomAdjust if Jostle.topAdjust == nil then Jostle.topAdjust = true end if Jostle.bottomAdjust == nil then Jostle.bottomAdjust = true end if not Jostle.hooks.WorldMapFrame_Hide then Jostle.hooks.WorldMapFrame_Hide = true hooksecurefunc(WorldMapFrame, "Hide", function() if Jostle.WorldMapFrame_Hide then Jostle:WorldMapFrame_Hide() end end) end if not Jostle.hooks.TicketStatusFrame_OnEvent then Jostle.hooks.TicketStatusFrame_OnEvent = true hooksecurefunc("TicketStatusFrame_OnEvent", function() if Jostle.TicketStatusFrame_OnEvent then Jostle:TicketStatusFrame_OnEvent() end end) end if not Jostle.hooks.FCF_UpdateDockPosition then Jostle.hooks.FCF_UpdateDockPosition = true hooksecurefunc("FCF_UpdateDockPosition", function() if Jostle.FCF_UpdateDockPosition then Jostle:FCF_UpdateDockPosition() end end) end if not Jostle.hooks.FCF_UpdateCombatLogPosition then Jostle.hooks.FCF_UpdateCombatLogPosition = true hooksecurefunc("FCF_UpdateCombatLogPosition", function() if Jostle.FCF_UpdateCombatLogPosition then Jostle:FCF_UpdateCombatLogPosition() end end) end if not Jostle.hooks.UIParent_ManageFramePositions then Jostle.hooks.UIParent_ManageFramePositions = true hooksecurefunc("UIParent_ManageFramePositions", function() if Jostle.UIParent_ManageFramePositions then Jostle:UIParent_ManageFramePositions() end end) end if not Jostle.hooks.PlayerFrame_SequenceFinished then Jostle.hooks.PlayerFrame_SequenceFinished = true hooksecurefunc("PlayerFrame_SequenceFinished", function() if Jostle.PlayerFrame_SequenceFinished then Jostle:PlayerFrame_SequenceFinished() end end) end Jostle.frame = oldLib and oldLib.frame or CreateFrame("Frame") local JostleFrame = Jostle.frame local start = GetTime() local nextTime = 0 local fullyInitted = false JostleFrame:SetScript("OnUpdate", function(this, elapsed) local now = GetTime() if now - start >= 3 then fullyInitted = true for k,v in pairs(blizzardFramesData) do blizzardFramesData[k] = nil end this:SetScript("OnUpdate", function(this, elapsed) if GetTime() >= nextTime then Jostle:Refresh() this:Hide() end end) end end) function JostleFrame:Schedule(time) time = time or 0 nextTime = GetTime() + time self:Show() end JostleFrame:UnregisterAllEvents() JostleFrame:SetScript("OnEvent", function(this, event, ...) return Jostle[event](Jostle, ...) end) JostleFrame:RegisterEvent("PLAYER_AURAS_CHANGED") JostleFrame:RegisterEvent("PLAYER_REGEN_DISABLED") JostleFrame:RegisterEvent("PLAYER_REGEN_ENABLED") JostleFrame:RegisterEvent("PLAYER_CONTROL_GAINED") function Jostle:PLAYER_AURAS_CHANGED() JostleFrame:Schedule() end function Jostle:WorldMapFrame_Hide() JostleFrame:Schedule() end function Jostle:TicketStatusFrame_OnEvent() self:Refresh(TicketStatusFrame, TemporaryEnchantFrame) end function Jostle:FCF_UpdateDockPosition() self:Refresh(DEFAULT_CHAT_FRAME) end function Jostle:FCF_UpdateCombatLogPosition() self:Refresh(ChatFrame2) end function Jostle:UIParent_ManageFramePositions() self:Refresh(GroupLootFrame1, TutorialFrameParent, FramerateLabel, QuestTimerFrame, DurabilityFrame) end function Jostle:PlayerFrame_SequenceFinished() self:Refresh(PlayerFrame) end function Jostle:GetScreenTop() local bottom = GetScreenHeight() for _,frame in ipairs(self.topFrames) do if frame.IsShown and frame:IsShown() and frame.GetBottom and frame:GetBottom() and frame:GetBottom() < bottom then bottom = frame:GetBottom() end end return bottom end function Jostle:GetScreenBottom() local top = 0 for _,frame in ipairs(self.bottomFrames) do if frame.IsShown and frame:IsShown() and frame.GetTop and frame:GetTop() and frame:GetTop() > top then top = frame:GetTop() end end return top end function Jostle:RegisterTop(frame) for k,f in ipairs(self.bottomFrames) do if f == frame then table.remove(self.bottomFrames, k) break end end for _,f in ipairs(self.topFrames) do if f == frame then return end end table.insert(self.topFrames, frame) JostleFrame:Schedule() return true end function Jostle:RegisterBottom(frame) for k,f in ipairs(self.topFrames) do if f == frame then table.remove(self.topFrames, k) break end end for _,f in ipairs(self.bottomFrames) do if f == frame then return end end table.insert(self.bottomFrames, frame) JostleFrame:Schedule() return true end function Jostle:Unregister(frame) for k,f in ipairs(self.topFrames) do if f == frame then table.remove(self.topFrames, k) JostleFrame:Schedule() return true end end for k,f in ipairs(self.bottomFrames) do if f == frame then table.remove(self.bottomFrames, k) JostleFrame:Schedule() return true end end end function Jostle:IsTopAdjusting() return self.topAdjust end function Jostle:EnableTopAdjusting() if not self.topAdjust then self.topAdjust = not self.topAdjust JostleFrame:Schedule() end end function Jostle:DisableTopAdjusting() if self.topAdjust then self.topAdjust = not self.topAdjust JostleFrame:Schedule() end end function Jostle:IsBottomAdjusting() return self.bottomAdjust end function Jostle:EnableBottomAdjusting() if not self.bottomAdjust then self.bottomAdjust = not self.bottomAdjust JostleFrame:Schedule() end end function Jostle:DisableBottomAdjusting() if self.bottomAdjust then self.bottomAdjust = not self.bottomAdjust JostleFrame:Schedule() end end local tmp = {} local queue = {} local inCombat = false function Jostle:ProcessQueue() if not inCombat and HasFullControl() then for k in pairs(queue) do self:Refresh(k) queue[k] = nil end end end function Jostle:PLAYER_CONTROL_GAINED() self:ProcessQueue() end function Jostle:PLAYER_REGEN_ENABLED() inCombat = false self:ProcessQueue() end function Jostle:PLAYER_REGEN_DISABLED() inCombat = true end local function isClose(alpha, bravo) return math.abs(alpha - bravo) < 0.1 end function Jostle:Refresh(...) if not fullyInitted then return end local screenHeight = GetScreenHeight() local topOffset = self:IsTopAdjusting() and self:GetScreenTop() or screenHeight local bottomOffset = self:IsBottomAdjusting() and self:GetScreenBottom() or 0 if topOffset ~= screenHeight or bottomOffset ~= 0 then JostleFrame:Schedule(10) end local frames if select('#', ...) >= 1 then for k in pairs(tmp) do tmp[k] = nil end for i = 1, select('#', ...) do tmp[i] = select(i, ...) end frames = tmp else frames = blizzardFrames end if inCombat or not HasFullControl() and not UnitHasVehicleUI("player") then for _,frame in ipairs(frames) do if type(frame) == "string" then frame = _G[frame] end if frame then queue[frame] = true end end return end local screenHeight = GetScreenHeight() for _,frame in ipairs(frames) do if type(frame) == "string" then frame = _G[frame] end local framescale = frame and frame.GetScale and frame:GetScale() or 1 if frame and not blizzardFramesData[frame] and frame.GetTop and frame:GetCenter() and select(2, frame:GetCenter()) then if select(2, frame:GetCenter()) <= screenHeight / 2 or frame == MultiBarRight then blizzardFramesData[frame] = {y = frame:GetBottom(), top = false} else blizzardFramesData[frame] = {y = frame:GetTop() - screenHeight / framescale, top = true} end if frame == MinimapCluster then blizzardFramesData[frame].lastX = GetScreenWidth() - MinimapCluster:GetWidth() blizzardFramesData[frame].lastY = GetScreenHeight() blizzardFramesData[frame].lastScale = 1 end end end for _,frame in ipairs(frames) do if type(frame) == "string" then frame = _G[frame] end local framescale = frame and frame.GetScale and frame:GetScale() or 1 if ((frame and frame.IsUserPlaced and not frame:IsUserPlaced()) or ((frame == DEFAULT_CHAT_FRAME or frame == ChatFrame2) and SIMPLE_CHAT == "1") or frame == FramerateLabel) and (frame ~= ChatFrame2 or SIMPLE_CHAT == "1") then local frameData = blizzardFramesData[frame] if (select(2, frame:GetPoint(1)) ~= UIParent and select(2, frame:GetPoint(1)) ~= WorldFrame) then -- do nothing elseif frame == PlayerFrame and (CT_PlayerFrame_Drag or Gypsy_PlayerFrameCapsule) then -- do nothing elseif frame == TargetFrame and (CT_TargetFrame_Drag or Gypsy_TargetFrameCapsule) then -- do nothing elseif frame == PartyMemberFrame1 and (CT_MovableParty1_Drag or Gypsy_PartyFrameCapsule) then -- do nothing elseif frame == MainMenuBar and Gypsy_HotBarCapsule then -- do nothing elseif frame == MinimapCluster and select(3, frame:GetPoint(1)) ~= "TOPRIGHT" then -- do nothing elseif frame == DurabilityFrame and DurabilityFrame:IsShown() and (DurabilityFrame:GetLeft() > GetScreenWidth() or DurabilityFrame:GetRight() < 0 or DurabilityFrame:GetBottom() > GetScreenHeight() or DurabilityFrame:GetTop() < 0) then DurabilityFrame:Hide() elseif frame == FramerateLabel and ((frameData.lastX and not isClose(frameData.lastX, frame:GetLeft())) or not isClose(WorldFrame:GetHeight() * WorldFrame:GetScale(), UIParent:GetHeight() * UIParent:GetScale())) then -- do nothing elseif frame == PlayerFrame or frame == MainMenuBar or frame == TemporaryEnchantFrame or frame == CastingBarFrame or frame == TutorialFrameParent or frame == FramerateLabel or frame == QuestTimerFrame or frame == DurabilityFrame or frame == QuestWatchFrame or not (frameData.lastScale and frame.GetScale and frameData.lastScale == frame:GetScale()) or not (frameData.lastX and frameData.lastY and (not isClose(frameData.lastX, frame:GetLeft()) or not isClose(frameData.lastY, frame:GetTop()))) then local anchor local anchorAlt local width, height = GetScreenWidth(), GetScreenHeight() local x if frame:GetRight() and frame:GetLeft() then local anchorFrame = UIParent if frame == MainMenuBar or frame == GroupLootFrame1 or frame == FramerateLabel then x = 0 anchor = "" elseif frame:GetRight() / framescale <= width / 2 then x = frame:GetLeft() / framescale anchor = "LEFT" else x = frame:GetRight() - width / framescale anchor = "RIGHT" end local y = blizzardFramesData[frame].y local offset = 0 if blizzardFramesData[frame].top then anchor = "TOP" .. anchor offset = ( topOffset - height ) / framescale else anchor = "BOTTOM" .. anchor offset = bottomOffset / framescale end if frame == MinimapCluster and not MinimapBorderTop:IsShown() then offset = offset + MinimapBorderTop:GetHeight() * 3/5 elseif frame == TemporaryEnchantFrame and TicketStatusFrame:IsShown() then offset = offset - TicketStatusFrame:GetHeight() * TicketStatusFrame:GetScale() elseif frame == DEFAULT_CHAT_FRAME then y = MainMenuBar:GetHeight() * MainMenuBar:GetScale() + 32 if PetActionBarFrame:IsShown() or ShapeshiftBarFrame:IsShown() then offset = offset + ShapeshiftBarFrame:GetHeight() * ShapeshiftBarFrame:GetScale() end if MultiBarBottomLeft:IsShown() then offset = offset + MultiBarBottomLeft:GetHeight() * MultiBarBottomLeft:GetScale() - 21 end elseif frame == ChatFrame2 then y = MainMenuBar:GetHeight() * MainMenuBar:GetScale() + 32 if MultiBarBottomRight:IsShown() then offset = offset + MultiBarBottomRight:GetHeight() * MultiBarBottomRight:GetScale() - 21 end elseif frame == CastingBarFrame then y = MainMenuBar:GetHeight() * MainMenuBar:GetScale() + 17 if PetActionBarFrame:IsShown() or ShapeshiftBarFrame:IsShown() then offset = offset + ShapeshiftBarFrame:GetHeight() * ShapeshiftBarFrame:GetScale() end if MultiBarBottomLeft:IsShown() or MultiBarBottomRight:IsShown() then offset = offset + MultiBarBottomLeft:GetHeight() * MultiBarBottomLeft:GetScale() end elseif frame == GroupLootFrame1 or frame == TutorialFrameParent or frame == FramerateLabel then if MultiBarBottomLeft:IsShown() or MultiBarBottomRight:IsShown() then offset = offset + MultiBarBottomLeft:GetHeight() * MultiBarBottomLeft:GetScale() end elseif frame == QuestTimerFrame or frame == DurabilityFrame or frame == QuestWatchFrame then anchorFrame = MinimapCluster x = 0 y = 0 offset = 0 if frame ~= QuestTimerFrame and QuestTimerFrame:IsShown() then y = y - QuestTimerFrame:GetHeight() * QuestTimerFrame:GetScale() end if frame == QuestWatchFrame and DurabilityFrame:IsShown() then y = y - DurabilityFrame:GetHeight() * DurabilityFrame:GetScale() end if frame == DurabilityFrame then x = -20 end anchor = "TOPRIGHT" anchorAlt = "BOTTOMRIGHT" if MultiBarRight:IsShown() then x = x - MultiBarRight:GetWidth() * MultiBarRight:GetScale() if MultiBarLeft:IsShown() then x = x - MultiBarLeft:GetWidth() * MultiBarLeft:GetScale() end end end if frame == FramerateLabel then anchorFrame = WorldFrame end frame:ClearAllPoints() frame:SetPoint(anchor, anchorFrame, anchorAlt or anchor, x, y + offset) blizzardFramesData[frame].lastX = frame:GetLeft() blizzardFramesData[frame].lastY = frame:GetTop() blizzardFramesData[frame].lastScale = framescale end end end end end local function compat() local Jostle20 = {} function Jostle20:RegisterTop(...) Jostle:RegisterTop(...) end function Jostle20:RegisterBottom(...) Jostle:RegisterBottom(...) end function Jostle20:GetScreenTop(...) Jostle:GetScreenTop(...) end function Jostle20:GetScreenBottom(...) Jostle:GetScreenBottom(...) end function Jostle20:Unregister(...) Jostle:Unregister(...) end function Jostle20:IsTopAdjusting(...) Jostle:IsTopAdjusting(...) end function Jostle20:EnableTopAdjusting(...) Jostle:EnableTopAdjusting(...) end function Jostle20:DisableTopAdjusting(...) Jostle:DisableTopAdjusting(...) end function Jostle20:IsBottomAdjusting(...) Jostle:IsBottomAdjusting(...) end function Jostle20:EnableBottomAdjusting(...) Jostle:EnableBottomAdjusting(...) end function Jostle20:DisableBottomAdjusting(...) Jostle:DisableBottomAdjusting(...) end function Jostle20:Refresh(...) Jostle:Refresh(...) end local function activate(self, oldLib) Jostle20 = self if oldLib and oldLib.topFrames and oldLib.bottomFrames then for i,v in ipairs(oldLib.topFrames) do Jostle:RegisterTop(v) end for i,v in ipairs(oldLib.bottomFrames) do Jostle:RegisterBottom(v) end end end local function external(self, instance, major) if major == "AceEvent-2.0" then instance:embed(self) self:UnregisterAllEvents() self:CancelAllScheduledEvents() end end AceLibrary:Register(Jostle20, "Jostle-2.0", MINOR_VERSION*1000, activate, external) Jostle20 = AceLibrary("Jostle-2.0") end if AceLibrary then compat() elseif Rock then function Jostle:OnLibraryLoad(major, version) if major == "AceLibrary" then compat() end end end