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) .. ")") self.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