Quantcast

Initial import from git repository

James Whitehead II [12-15-08 - 16:45]
Initial import from git repository
Filename
Libs/CallbackHandler-1.0.lua
Libs/LibDataBroker-1.1.lua
Libs/LibJostle-3.0.lua
Libs/LibStub.lua
NinjaPanel.lua
NinjaPanel.toc
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