From 366e93e49c4faba8eb9c7a1e592e591d005f13eb Mon Sep 17 00:00:00 2001 From: James Whitehead II Date: Mon, 15 Dec 2008 16:45:12 +0000 Subject: [PATCH] Initial import from git repository --- Libs/CallbackHandler-1.0.lua | 239 ++++++++++++++++++ Libs/LibDataBroker-1.1.lua | 90 +++++++ Libs/LibJostle-3.0.lua | 558 ++++++++++++++++++++++++++++++++++++++++++ Libs/LibStub.lua | 30 +++ NinjaPanel.lua | 534 ++++++++++++++++++++++++++++++++++++++++ NinjaPanel.toc | 12 + 6 files changed, 1463 insertions(+) create mode 100644 Libs/CallbackHandler-1.0.lua create mode 100755 Libs/LibDataBroker-1.1.lua create mode 100644 Libs/LibJostle-3.0.lua create mode 100644 Libs/LibStub.lua create mode 100644 NinjaPanel.lua create mode 100644 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 -- 1.7.9.5