diff --git a/Libs/CallbackHandler-1.0.lua b/Libs/CallbackHandler-1.0.lua
new file mode 100644
index 0000000..5a495f6
--- /dev/null
+++ b/Libs/CallbackHandler-1.0.lua
@@ -0,0 +1,239 @@
+--[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]]
+local MAJOR, MINOR = "CallbackHandler-1.0", 3
+local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not CallbackHandler then return end -- No upgrade needed
+
+local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
+
+local type = type
+local pcall = pcall
+local pairs = pairs
+local assert = assert
+local concat = table.concat
+local loadstring = loadstring
+local next = next
+local select = select
+local type = type
+local xpcall = xpcall
+
+local function errorhandler(err)
+ return geterrorhandler()(err)
+end
+
+local function CreateDispatcher(argCount)
+ local code = [[
+ local next, xpcall, eh = ...
+
+ local method, ARGS
+ local function call() method(ARGS) end
+
+ local function dispatch(handlers, ...)
+ local index
+ index, method = next(handlers)
+ if not method then return end
+ local OLD_ARGS = ARGS
+ ARGS = ...
+ repeat
+ xpcall(call, eh)
+ index, method = next(handlers, index)
+ until not method
+ ARGS = OLD_ARGS
+ end
+
+ return dispatch
+ ]]
+
+ local ARGS, OLD_ARGS = {}, {}
+ for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
+ code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", "))
+ return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
+end
+
+local Dispatchers = setmetatable({}, {__index=function(self, argCount)
+ local dispatcher = CreateDispatcher(argCount)
+ rawset(self, argCount, dispatcher)
+ return dispatcher
+end})
+
+--------------------------------------------------------------------------
+-- CallbackHandler:New
+--
+-- target - target object to embed public APIs in
+-- RegisterName - name of the callback registration API, default "RegisterCallback"
+-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
+-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
+
+function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
+ -- TODO: Remove this after beta has gone out
+ assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
+
+ RegisterName = RegisterName or "RegisterCallback"
+ UnregisterName = UnregisterName or "UnregisterCallback"
+ if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
+ UnregisterAllName = "UnregisterAllCallbacks"
+ end
+
+ -- we declare all objects and exported APIs inside this closure to quickly gain access
+ -- to e.g. function names, the "target" parameter, etc
+
+
+ -- Create the registry object
+ local events = setmetatable({}, meta)
+ local registry = { recurse=0, events=events }
+
+ -- registry:Fire() - fires the given event/message into the registry
+ function registry:Fire(eventname, ...)
+ if not rawget(events, eventname) or not next(events[eventname]) then return end
+ local oldrecurse = registry.recurse
+ registry.recurse = oldrecurse + 1
+
+ Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
+
+ registry.recurse = oldrecurse
+
+ if registry.insertQueue and oldrecurse==0 then
+ -- Something in one of our callbacks wanted to register more callbacks; they got queued
+ for eventname,callbacks in pairs(registry.insertQueue) do
+ local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
+ for self,func in pairs(callbacks) do
+ events[eventname][self] = func
+ -- fire OnUsed callback?
+ if first and registry.OnUsed then
+ registry.OnUsed(registry, target, eventname)
+ first = nil
+ end
+ end
+ end
+ registry.insertQueue = nil
+ end
+ end
+
+ -- Registration of a callback, handles:
+ -- self["method"], leads to self["method"](self, ...)
+ -- self with function ref, leads to functionref(...)
+ -- "addonId" (instead of self) with function ref, leads to functionref(...)
+ -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
+ target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
+ if type(eventname) ~= "string" then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
+ end
+
+ method = method or eventname
+
+ local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
+
+ if type(method) ~= "string" and type(method) ~= "function" then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
+ end
+
+ local regfunc
+
+ if type(method) == "string" then
+ -- self["method"] calling style
+ if type(self) ~= "table" then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
+ elseif self==target then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
+ elseif type(self[method]) ~= "function" then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
+ end
+
+ if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
+ local arg=select(1,...)
+ regfunc = function(...) self[method](self,arg,...) end
+ else
+ regfunc = function(...) self[method](self,...) end
+ end
+ else
+ -- function ref with self=object or self="addonId"
+ if type(self)~="table" and type(self)~="string" then
+ error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2)
+ end
+
+ if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
+ local arg=select(1,...)
+ regfunc = function(...) method(arg,...) end
+ else
+ regfunc = method
+ end
+ end
+
+
+ if events[eventname][self] or registry.recurse<1 then
+ -- if registry.recurse<1 then
+ -- we're overwriting an existing entry, or not currently recursing. just set it.
+ events[eventname][self] = regfunc
+ -- fire OnUsed callback?
+ if registry.OnUsed and first then
+ registry.OnUsed(registry, target, eventname)
+ end
+ else
+ -- we're currently processing a callback in this registry, so delay the registration of this new entry!
+ -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
+ registry.insertQueue = registry.insertQueue or setmetatable({},meta)
+ registry.insertQueue[eventname][self] = regfunc
+ end
+ end
+
+ -- Unregister a callback
+ target[UnregisterName] = function(self, eventname)
+ if not self or self==target then
+ error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
+ end
+ if type(eventname) ~= "string" then
+ error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
+ end
+ if rawget(events, eventname) and events[eventname][self] then
+ events[eventname][self] = nil
+ -- Fire OnUnused callback?
+ if registry.OnUnused and not next(events[eventname]) then
+ registry.OnUnused(registry, target, eventname)
+ end
+ end
+ if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
+ registry.insertQueue[eventname][self] = nil
+ end
+ end
+
+ -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
+ if UnregisterAllName then
+ target[UnregisterAllName] = function(...)
+ if select("#",...)<1 then
+ error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
+ end
+ if select("#",...)==1 and ...==target then
+ error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
+ end
+
+
+ for i=1,select("#",...) do
+ local self = select(i,...)
+ if registry.insertQueue then
+ for eventname, callbacks in pairs(registry.insertQueue) do
+ if callbacks[self] then
+ callbacks[self] = nil
+ end
+ end
+ end
+ for eventname, callbacks in pairs(events) do
+ if callbacks[self] then
+ callbacks[self] = nil
+ -- Fire OnUnused callback?
+ if registry.OnUnused and not next(callbacks) then
+ registry.OnUnused(registry, target, eventname)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return registry
+end
+
+
+-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
+-- try to upgrade old implicit embeds since the system is selfcontained and
+-- relies on closures to work.
+
diff --git a/Libs/LibDataBroker-1.1.lua b/Libs/LibDataBroker-1.1.lua
new file mode 100755
index 0000000..f47c0cd
--- /dev/null
+++ b/Libs/LibDataBroker-1.1.lua
@@ -0,0 +1,90 @@
+
+assert(LibStub, "LibDataBroker-1.1 requires LibStub")
+assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0")
+
+local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4)
+if not lib then return end
+oldminor = oldminor or 0
+
+
+lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib)
+lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {}
+local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks
+
+if oldminor < 2 then
+ lib.domt = {
+ __metatable = "access denied",
+ __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end,
+ }
+end
+
+if oldminor < 3 then
+ lib.domt.__newindex = function(self, key, value)
+ if not attributestorage[self] then attributestorage[self] = {} end
+ if attributestorage[self][key] == value then return end
+ attributestorage[self][key] = value
+ local name = namestorage[self]
+ if not name then return end
+ callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self)
+ end
+end
+
+if oldminor < 2 then
+ function lib:NewDataObject(name, dataobj)
+ if self.proxystorage[name] then return end
+
+ if dataobj then
+ assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table")
+ self.attributestorage[dataobj] = {}
+ for i,v in pairs(dataobj) do
+ self.attributestorage[dataobj][i] = v
+ dataobj[i] = nil
+ end
+ end
+ dataobj = setmetatable(dataobj or {}, self.domt)
+ self.proxystorage[name], self.namestorage[dataobj] = dataobj, name
+ self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj)
+ return dataobj
+ end
+end
+
+if oldminor < 1 then
+ function lib:DataObjectIterator()
+ return pairs(self.proxystorage)
+ end
+
+ function lib:GetDataObjectByName(dataobjectname)
+ return self.proxystorage[dataobjectname]
+ end
+
+ function lib:GetNameByDataObject(dataobject)
+ return self.namestorage[dataobject]
+ end
+end
+
+if oldminor < 4 then
+ local next = pairs(attributestorage)
+ function lib:pairs(dataobject_or_name)
+ local t = type(dataobject_or_name)
+ assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)")
+
+ local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+ assert(attributestorage[dataobj], "Data object not found")
+
+ return next, attributestorage[dataobj], nil
+ end
+
+ local ipairs_iter = ipairs(attributestorage)
+ function lib:ipairs(dataobject_or_name)
+ local t = type(dataobject_or_name)
+ assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)")
+
+ local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+ assert(attributestorage[dataobj], "Data object not found")
+
+ return ipairs_iter, attributestorage[dataobj], 0
+ end
+end
diff --git a/Libs/LibJostle-3.0.lua b/Libs/LibJostle-3.0.lua
new file mode 100644
index 0000000..0dee240
--- /dev/null
+++ b/Libs/LibJostle-3.0.lua
@@ -0,0 +1,558 @@
+--[[
+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
diff --git a/Libs/LibStub.lua b/Libs/LibStub.lua
new file mode 100644
index 0000000..cfc97de
--- /dev/null
+++ b/Libs/LibStub.lua
@@ -0,0 +1,30 @@
+-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local LibStub = _G[LIBSTUB_MAJOR]
+
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+ LibStub = LibStub or {libs = {}, minors = {} }
+ _G[LIBSTUB_MAJOR] = LibStub
+ LibStub.minor = LIBSTUB_MINOR
+
+ function LibStub:NewLibrary(major, minor)
+ assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+ minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
+
+ local oldminor = self.minors[major]
+ if oldminor and oldminor >= minor then return nil end
+ self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+ return self.libs[major], oldminor
+ end
+
+ function LibStub:GetLibrary(major, silent)
+ if not self.libs[major] and not silent then
+ error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
+ end
+ return self.libs[major], self.minors[major]
+ end
+
+ function LibStub:IterateLibraries() return pairs(self.libs) end
+ setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
diff --git a/NinjaPanel.lua b/NinjaPanel.lua
new file mode 100644
index 0000000..c7b6799
--- /dev/null
+++ b/NinjaPanel.lua
@@ -0,0 +1,534 @@
+NinjaPanel = {panels = {}, plugins = {}}
+
+-- Import Data Broker and bail if we can't find it for some reason
+local ldb = LibStub:GetLibrary("LibDataBroker-1.1")
+local jostle = LibStub:GetLibrary("LibJostle-3.0")
+local db
+
+local eventFrame = CreateFrame("Frame", "NinjaPanelEventFrame", UIParent)
+eventFrame:RegisterEvent("ADDON_LOADED")
+eventFrame:SetScript("OnEvent", function(self, event, arg1, ...)
+ if arg1 == "NinjaPanel" and event == "ADDON_LOADED" then
+ -- Update addon options once they've been loaded
+ if not NinjaPanelDB then
+ NinjaPanelDB = {}
+ end
+ db = NinjaPanelDB
+
+ self:UnregisterEvent("ADDON_LOADED")
+ NinjaPanel:SpawnPanel("NinjaPanelTop", "TOP")
+ --NinjaPanel:SpawnPanel("NinjaPanelBottom", "BOTTOM")
+ NinjaPanel:ScanForPlugins()
+ end
+end)
+
+-- Local functions that are defined below
+local SortWeightName
+local Button_OnEnter, Button_OnLeave
+local Button_OnDragStart, Button_OnDragStop, Button_OnUpdateDragging
+local Panel_UpdateLayout
+
+function NinjaPanel:SpawnPanel(name, position)
+ local panel = CreateFrame("Frame", name, eventFrame)
+ panel.bg = panel:CreateTexture(name .. "BG", "BACKGROUND")
+ panel.border = panel:CreateTexture(name .. "Border", "BACKGROUND")
+ panel.name = name
+ panel.position = position
+
+ panel:ClearAllPoints()
+ if position == "TOP" then
+ panel:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, 0)
+ panel:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", 0, 0)
+ panel:SetHeight(16)
+ panel.bg:SetTexture(1, 1, 1, 0.8)
+ panel.bg:SetGradient("VERTICAL", 0.2, 0.2, 0.2, 0, 0, 0)
+ panel.bg:SetPoint("TOPLEFT")
+ panel.bg:SetPoint("TOPRIGHT")
+ panel.bg:SetHeight(15)
+ panel.border:SetTexture(1, 1, 1, 0.8)
+ panel.border:SetGradient("HORIZONTAL", 203 / 255, 161 / 255, 53 / 255, 0, 0, 0)
+ panel.border:SetPoint("TOPLEFT", panel.bg, "BOTTOMLEFT", 0, 0)
+ panel.border:SetPoint("TOPRIGHT", panel.bg, "BOTTOMRIGHT", 0, 0)
+ panel.border:SetHeight(1)
+
+ if jostle then
+ jostle:RegisterTop(panel)
+ end
+ elseif position == "BOTTOM" then
+ panel:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 0, 0)
+ panel:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", 0, 0)
+ panel:SetHeight(16)
+ panel.bg:SetTexture(1, 1, 1, 0.8)
+ panel.bg:SetGradient("VERTICAL", 0, 0, 0, 0.2, 0.2, 0.2)
+ panel.bg:SetPoint("BOTTOMLEFT")
+ panel.bg:SetPoint("BOTTOMRIGHT")
+ panel.bg:SetHeight(15)
+ panel.border:SetTexture(1, 1, 1, 0.8)
+ panel.border:SetGradient("HORIZONTAL", 203 / 255, 161 / 255, 53 / 255, 0, 0, 0)
+ panel.border:SetPoint("BOTTOMLEFT", panel.bg, "TOPLEFT", 0, 0)
+ panel.border:SetPoint("BOTTOMRIGHT", panel.bg, "TOPRIGHT", 0, 0)
+ panel.border:SetHeight(1)
+
+ if jostle then
+ jostle:RegisterBottom(panel)
+ end
+ end
+
+ -- TODO: Add the panel methods here
+ panel.plugins = {}
+ table.insert(self.panels, panel)
+ self.panels[panel.name] = panel
+
+ return panel
+end
+
+function NinjaPanel:HasPlugin(name)
+ return self.plugins[name] and true
+end
+
+function NinjaPanel:SpawnPlugin(name, object, type)
+ db.plugins[name] = db.plugins[name] or {}
+ local opts = setmetatable(db.plugins[name], {
+ __index = {
+ weight = 0,
+ alignRight = false,
+ }
+ })
+
+ local entry = {}
+ self.plugins[name] = entry
+
+ entry.type = type
+ entry.object = object
+ entry.name = name
+ entry.weight = opts.weight
+
+ -- Push all of the launchers to the right-hand side
+ if object.type == "launcher" and rawget(opts, "alignRight") == nil then
+ entry.alignRight = true
+ else
+ entry.alignRight = opts.alignRight
+ end
+
+ local button = CreateFrame("Button", "NinjaPanelButton_" .. name, eventFrame)
+ button.icon = button:CreateTexture(nil, "BACKGROUND")
+ button.text = button:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
+ button.entry = entry
+ button.object = object
+ button:RegisterForClicks("AnyUp")
+ button:SetMovable(true)
+
+ entry.button = button
+
+ ldb.RegisterCallback(self, "LibDataBroker_AttributeChanged_" .. name, "UpdatePlugin")
+end
+
+function NinjaPanel:ScanForPlugins()
+ self.warned = self.warned or {}
+
+ local changed = false
+ for name,dataobj in ldb:DataObjectIterator() do
+ if not self:HasPlugin(name) then
+ if dataobj.type == "data source" or dataobj.text then
+ self:SpawnPlugin(name, dataobj, "data source")
+ changed = true
+ elseif dataobj.type == "launcher" or (dataobj.icon and dataobj.OnClick) then
+ self:SpawnPlugin(name, dataobj, "launcher")
+ changed = true
+ elseif not self.warned[name] then
+ print("Skipping unknown broker object for " .. name .. "(" .. tostring(dataobj.type) .. ")")
+ warned[name] = true
+ end
+ end
+ end
+
+ self:UpdatePanels()
+end
+
+function NinjaPanel:UpdateButtonWidth(button)
+ local iconWidth = button.icon:IsShown() and button.icon:GetWidth() or 0
+ local textWidth = button.text:IsShown() and button.text:GetWidth() or 0
+ button:SetWidth(textWidth + iconWidth + ((textWidth > 0) and 9 or 3))
+end
+
+function NinjaPanel:UpdatePlugin(event, name, key, value, dataobj)
+ -- name: The name of the plugin being updated
+ -- key: The key that was updated in the plugin
+ -- value: The new value of the given key
+ -- dataobj: The actual data object
+
+ -- Bail out early if necessary
+ if not self:HasPlugin(name) then
+ return
+ end
+
+ local entry = self.plugins[name]
+ local button = entry.button
+
+ if key == "text" then
+ button.text:SetFormattedText("%s", value)
+ self:UpdateButtonWidth(button)
+ elseif key == "icon" then
+ button.icon:SetTexture(value)
+ end
+
+ -- Update the icon coordinates if either the icon or the icon coords were
+ if key == "icon" or key == "iconCoords" then
+ -- Since the icon has changed, update texcoord and color
+ if entry.object.iconCoords then
+ button.icon:SetTexCoord(unpack(dataobj.iconCoords))
+ else
+ button.icon:SetTexCoord(0, 1, 0, 1)
+ end
+ end
+
+ -- Update the icon color if either the icon or the color attributes are changed
+ if key == "icon" or key == "iconR" or key == "iconG" or key == "iconB" then
+ if entry.object.iconR then
+ local r = dataobj.iconR or 1
+ local g = dataobj.iconG or 1
+ local b = dataobj.iconB or 1
+ button.icon:SetVertexColor(r, g, b)
+ else
+ button.icon:SetVertexColor(1, 1, 1)
+ end
+ end
+end
+
+function NinjaPanel:UpdatePanels()
+ -- Ensure the options table exists
+ db.panels = db.panels or {}
+
+ -- Iterate over the plugins that have been registered, and claim children
+ local head = self.panels[1]
+ for name,entry in pairs(self.plugins) do
+ if not entry.panel then
+ local opt = db.plugins[name]
+ local panel = opt.panel and self.panels[opt.panel] or head
+ self:AttachPlugin(entry, panel)
+ entry.button:SetParent(panel)
+ end
+ end
+
+ -- Loop through each of the panels, updating the visual display
+ for idx,panel in ipairs(self.panels) do
+ local name = panel.name
+ db.panels[name] = db.panels[name] or {}
+ local opt = db.panels[name]
+ setmetatable(opt, {
+ __index = {
+ height = 15,
+ border_height = 1,
+ gradient = {0.2, 0.2, 0.2, 1.0, 0, 0, 0, 1.0},
+ gradient_dir = "VERTICAL",
+ border_gradient = {203 / 255, 161 / 255, 53 / 255, 1.0, 0, 0, 0, 1.0},
+ border_gradient_dir = "HORIZONTAL",
+ }
+ })
+
+ -- DEFAULT OPTIONS HERE
+ local height = opt.height
+ local border_height = opt.border_height
+ local gradient = opt.gradient
+ local gradient_dir = opt.gradient_dir
+ local border_gradient = opt.border_gradient
+ local border_gradient_dir = opt.border_gradient_dir
+
+ panel:SetHeight(height + border_height)
+ panel.bg:SetHeight(height)
+ panel.border:SetHeight(border_height)
+ panel.bg:SetGradientAlpha(gradient_dir, unpack(gradient))
+ panel.border:SetGradientAlpha(border_gradient_dir, unpack(border_gradient))
+ end
+
+ -- Update the plugins on each panel
+ for idx,panel in ipairs(self.panels) do
+ Panel_UpdateLayout(panel)
+ end
+end
+
+function NinjaPanel:AttachPlugin(plugin, panel)
+ panel.plugins[plugin.name] = plugin
+ plugin.panel = panel
+end
+
+function NinjaPanel:HardAnchorPlugins()
+ for idx,panel in ipairs(self.panels) do
+ local opt = db.panels[panel.name]
+ local yoffset = opt.border_height
+
+ for name,entry in pairs(panel.plugins) do
+ local button = entry.button
+ local left = button:GetLeft()
+ button:ClearAllPoints()
+ button:SetPoint("LEFT", panel, "LEFT", left, 0)
+ end
+ end
+end
+
+ldb.RegisterCallback(NinjaPanel, "LibDataBroker_DataObjectCreated", "ScanForPlugins")
+
+function Panel_UpdateLayout(self)
+ local left, right = {}, {}
+
+ -- Loop through all of the plugins in the given panel
+ for name,entry in pairs(self.plugins) do
+ local panel_opts = db.panels[self.name]
+
+ table.insert(entry.alignRight and right or left, entry)
+
+ local button = entry.button
+ local height = panel_opts.height - (panel_opts.border_height * 2)
+ button:SetHeight(height)
+
+ if entry.object.icon then
+ -- Actually update the layout of the button
+ button.icon:SetHeight(height)
+ button.icon:SetWidth(height)
+ button.icon:SetTexture(entry.object.icon)
+ button.icon:ClearAllPoints()
+ button.icon:SetPoint("LEFT", button, "LEFT", 3, panel_opts.border_height)
+ button.icon:Show()
+
+ -- Run a SetTexCoord on the icon if .iconCoords is set
+ if entry.object.iconCoords then
+ button.icon:SetTexCoord(unpack(entry.object.iconCoords))
+ else
+ button.icon:SetTexCoord(0, 1, 0, 1)
+ end
+
+ if entry.object.iconR or entry.object.iconG or entry.object.iconB then
+ local r = entry.object.iconR or 1.0
+ local g = entry.object.iconG or 1.0
+ local b = entry.object.iconB or 1.0
+ button.icon:SetVertexColor(r, g, b)
+ end
+ else
+ button.icon:Hide()
+ end
+
+ -- Attach the button scripts
+ if entry.object.OnEnter and not entry.object.OnTooltipShow then
+ button:SetScript("OnEnter", entry.object.OnEnter)
+ button:SetScript("OnLeave", entry.object.OnLeave)
+ else
+ button:SetScript("OnEnter", Button_OnEnter)
+ button:SetScript("OnLeave", Button_OnLeave)
+ end
+
+ button:SetScript("OnClick", entry.object.OnClick)
+ button:SetScript("OnDragStart", Button_OnDragStart)
+ button:SetScript("OnDragStop", Button_OnDragStop)
+ button:RegisterForDrag("LeftButton")
+
+ button.text:SetText(entry.object.text or "Waiting...")
+ button.text:SetHeight(height)
+ button.text:ClearAllPoints()
+
+ if button.icon:IsShown() then
+ button.text:SetPoint("LEFT", button.icon, "RIGHT", 5, 0)
+ else
+ button.text:SetPoint("LEFT", button, "LEFT", 3, panel_opts.border_height)
+ end
+
+ if entry.object.type == "launcher" then
+ -- Hide the text
+ button.text:Hide()
+ else
+ button.text:Show()
+ end
+ NinjaPanel:UpdateButtonWidth(button)
+ end
+
+ -- Sort the list of plugins into left/right
+ table.sort(left, SortWeightName)
+ table.sort(right, SortWeightName)
+
+ -- Anchor everything that is left-aligned
+ for idx,entry in ipairs(left) do
+ local button = entry.button
+ button:ClearAllPoints()
+ if idx == 1 then
+ button:SetPoint("LEFT", self, "LEFT", 3, 0)
+ else
+ button:SetPoint("LEFT", left[idx-1].button, "RIGHT", 3, 0)
+ end
+ end
+
+ -- Anchor everything that is right-aligned
+ for idx,entry in ipairs(right) do
+ local button = entry.button
+ button:ClearAllPoints()
+ if idx == 1 then
+ button:SetPoint("RIGHT", self, "RIGHT", -3, 0)
+ else
+ button:SetPoint("RIGHT", right[idx-1].button, "LEFT", -3, 0)
+ end
+ end
+end
+
+--[[-----------------------------------------------------------------------
+-- Locally defined functions
+-----------------------------------------------------------------------]]--
+
+function SortWeightName(a,b)
+ if a.weight and b.weight then
+ return a.weight < b.weight
+ else
+ return a.name < b.name
+ end
+end
+
+function Button_OnEnter(self, ...)
+ GameTooltip:SetOwner(self, "ANCHOR_NONE")
+ GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
+ GameTooltip:ClearLines()
+ if self.object.OnTooltipShow then
+ self.object.OnTooltipShow(GameTooltip)
+ else
+ GameTooltip:SetText(self.entry.name)
+ end
+ GameTooltip:Show()
+end
+
+function Button_OnLeave(self, ...)
+ GameTooltip:Hide()
+end
+
+function Button_OnUpdateDragging(self, elapsed)
+ self:ClearAllPoints()
+ local left, right = GetCursorPosition()
+ left = left / self:GetEffectiveScale()
+ self:SetPoint("LEFT", self:GetParent(), "LEFT", left, 0)
+end
+
+function Button_OnDragStart(self, button, ...)
+ NinjaPanel:HardAnchorPlugins()
+ self:SetToplevel(true)
+ self:SetScript("OnUpdate", Button_OnUpdateDragging)
+ self.origLeft = self:GetLeft()
+end
+
+function Button_OnDragStop(self, button, ...)
+ self:SetScript("OnUpdate", nil)
+ self:StopMovingOrSizing()
+ local p = self:GetParent()
+
+ local left, right = {}, {}
+ for name,entry in pairs(p.plugins) do
+ if entry.button ~= self then
+ table.insert(entry.alignRight and right or left, entry)
+ end
+ end
+
+ table.sort(left, SortWeightName)
+ table.sort(right, SortWeightName)
+
+ local newLeft, newRight = self:GetLeft(), self:GetRight()
+ local alignRight = false
+ local leftPos, rightPos = {}, {}
+
+ -- Store the positions for the right-most plugins first
+ for idx,entry in ipairs(right) do
+ rightPos[entry] = entry.button:GetLeft()
+ end
+
+ -- Store the positions for the left-most plugins
+ for idx,entry in ipairs(left) do
+ leftPos[entry] = entry.button:GetRight()
+ end
+
+ -- If we are moving to the right
+ if self.origLeft <= newLeft then
+ -- Check to see if we're on the right-hand side of the panel
+ for idx, entry in ipairs(right) do
+ if newRight > rightPos[entry] then
+ rightPos[self.entry] = rightPos[entry] + 1
+ alignRight = true
+ break
+ end
+ end
+
+ if not alignRight then
+ for idx=#left, 1, -1 do
+ local entry = left[idx]
+ if newRight > entry.button:GetLeft() and newRight <= entry.button:GetRight() then
+ leftPos[self.entry] = leftPos[entry] + 1
+ alignRight = false
+ break
+ end
+ end
+ end
+ else
+ -- We are moving to the left
+ -- Check to see if we're on the right-hand side of the panel
+ if right[1] and newLeft > right[#right].button:GetLeft() then
+ for idx=#right, 1, -1 do
+ local entry = right[idx]
+ if newLeft < entry.button:GetRight() then
+ rightPos[self.entry] = rightPos[entry] - 1
+ alignRight = true
+ break
+ end
+ end
+ end
+
+ if not alignRight then
+ for idx,entry in ipairs(left) do
+ if newLeft < leftPos[entry] then
+ leftPos[self.entry] = leftPos[entry] - 1
+ alignRight = false
+ break
+ end
+ end
+ end
+ end
+
+ -- If we didn't get a position above
+ if not leftPos[self.entry] and not rightPos[self.entry] then
+ -- Handle the case where we're the first plugin to go to the right
+ local panelRight = p:GetRight()
+ if newRight >= panelRight - 100 then
+ rightPos[self.entry] = panelRight
+ alignRight = true
+ end
+
+ -- Handle the case where we're the first plugin to go to the left
+ if not alignRight then
+ if newLeft <= 100 then
+ leftPos[self.entry] = 0
+ alignRight = false
+ else
+ -- Otherwise, just tag it onto the right of the left
+ leftPos[self.entry] = panelRight
+ alignRight = false
+ end
+ end
+ end
+
+ table.insert(alignRight and right or left, self.entry)
+ self.entry.alignRight = alignRight
+
+ table.sort(left, function(a,b) return leftPos[a] < leftPos[b] end)
+ table.sort(right, function(a,b) return rightPos[a] > rightPos[b] end)
+
+ for idx,entry in ipairs(left) do
+ entry.weight = idx
+ end
+ for idx,entry in ipairs(right) do
+ entry.weight = idx
+ end
+
+ -- Save the new weight information out to the database
+ if not NinjaPanelDB.plugins then NinjaPanelDB.plugins = {} end
+ local opts = NinjaPanelDB.plugins
+
+ for name,entry in pairs(p.plugins) do
+ opts[name].weight = entry.weight
+ opts[name].enabled = entry.enabled
+ opts[name].alignRight = entry.alignRight
+ end
+
+ Panel_UpdateLayout(p)
+end
+
diff --git a/NinjaPanel.toc b/NinjaPanel.toc
new file mode 100644
index 0000000..b43c0c7
--- /dev/null
+++ b/NinjaPanel.toc
@@ -0,0 +1,12 @@
+## Interface: 30000
+## Title: NinjaPanel
+## Author: Cladhaire
+## Version: wowi:revision
+## Notes: A simple no-frills top panel for data broker feeds
+## SavedVariables: NinjaPanelDB
+
+Libs\LibStub.lua
+Libs\CallbackHandler-1.0.lua
+Libs\LibDataBroker-1.1.lua
+Libs\LibJostle-3.0.lua
+NinjaPanel.lua