pfUI:RegisterModule("actionbar", function () local class = UnitClassBase("player") local _, cr, cg, cb = GetUnitColor('player') local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color) local backdrop_highlight = { edgeFile = pfUI.media["img:glow"], edgeSize = 8 } local showgrid = 0 local showgrid_pet = 0 local rawborder, border = GetBorderSize("actionbars") local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() local eventcache = { } -- contains a list of events that shall be processed later -> [event] = true local updatecache = { } -- contains a list of buttons slots that shall be refreshed later -> [slot] = true local buttoncache = { } -- contains a list of all buttons ever created -> [slot] = frame -- ClassicAPI's left/right modifier state, which unlike IsShiftKeyDown is -- corrected for keys released while the game was in the background local function ShiftDown() return IsLeftShiftKeyDown() or IsRightShiftKeyDown() end local function CtrlDown() return IsLeftControlKeyDown() or IsRightControlKeyDown() end local function AltDown() return IsLeftAltKeyDown() or IsRightAltKeyDown() end -- try to assume based on the current mouse positions if a button drag -- should happen even if action-on-key-down is used. By that replace the -- cast events by reverting the active buttons to the old mouse-up state. local drag_await local drag_active local function AssumeButtonDrag() -- skip if keydown press is not enabled if C.bars.keydown ~= "1" then return end -- skip if always shift-drag is not enabled if C.bars.shiftdrag ~= "1" then return end if drag_await and not drag_active and ShiftDown() then drag_active = true -- set all buttons to regular on release clicks for id, button in pairs(buttoncache) do button:RegisterForClicks("LeftButtonUp", "RightButtonUp") end elseif drag_active and not ShiftDown() then drag_active = nil -- set all buttons back to their defaults for id, button in pairs(buttoncache) do button:RegisterForClicks("LeftButtonDown", "RightButtonDown") end end end pfUI.events:RegisterFrameEventAndCallback("MODIFIER_STATE_CHANGED", AssumeButtonDrag) -- hide blizzard bars local function kill(f, killshow) if f.Show and killshow then f.Show = function() return end end if f.UnregisterAllEvents then f:UnregisterAllEvents() end if f.Hide then f:Hide() end end -- also abbreviate mouse buttons local OrigGetBindingText = GetBindingText local function GetBindingText(msg, mod, abbrev) local txt = OrigGetBindingText(msg, mod, abbrev) if abbrev then txt = string.gsub(txt, _G[string.format("%s%s", mod, "BUTTON3")], "MB3") txt = string.gsub(txt, _G[string.format("%s%s", mod, "BUTTON4")], "MB4") txt = string.gsub(txt, _G[string.format("%s%s", mod, "BUTTON5")], "MB5") txt = string.gsub(txt, _G[string.format("%s%s", mod, "MOUSEWHEELDOWN")], "MWD") txt = string.gsub(txt, _G[string.format("%s%s", mod, "MOUSEWHEELUP")], "MWU") end return txt end kill(MainMenuBar) local blizzard_elements = { MultiBarBottomLeft, MultiBarBottomRight, MultiBarLeft, MultiBarRight } for _, f in pairs(blizzard_elements) do kill(f, true) end -- enable all possible actionbar pages SetActionBarToggles(0, 0, 0, 0) _G.SHOW_MULTI_ACTIONBAR_1 = nil _G.SHOW_MULTI_ACTIONBAR_2 = nil _G.SHOW_MULTI_ACTIONBAR_3 = nil _G.SHOW_MULTI_ACTIONBAR_4 = nil -- events that provide special updater functions local special_events = { ["ACTIONBAR_UPDATE_COOLDOWN"] = true, ["ACTIONBAR_UPDATE_USABLE"] = true, ["ACTIONBAR_UPDATE_STATE"] = true, -- fires on every ammo shot / consumable use, but only counts, equipped -- state, cooldowns and usability can change from it ["UNIT_INVENTORY_CHANGED"] = true, } -- events that are shared across all buttons local global_events = { -- slot/button updates ["PLAYER_ENTERING_WORLD"] = true, ["ACTIONBAR_SLOT_CHANGED"] = true, ["UPDATE_BINDINGS"] = true, ["ACTIONBAR_PAGE_CHANGED"] = true, ["UPDATE_BONUS_ACTIONBAR"] = true, ["CRAFT_SHOW"] = true, ["CRAFT_CLOSE"] = true, ["TRADE_SKILL_SHOW"] = true, ["TRADE_SKILL_CLOSE"] = true, ["PLAYER_ENTER_COMBAT"] = true, ["PLAYER_LEAVE_COMBAT"] = true, -- auto repeat action ["START_AUTOREPEAT_SPELL"] = true, ["STOP_AUTOREPEAT_SPELL"] = true, } -- events that are used for the aura/shapeshift bar local aura_events = { ["UPDATE_SHAPESHIFT_FORMS"] = true, ["PLAYER_AURAS_CHANGED"] = true, } -- events that are used for the pet bar local pet_events = { ["PLAYER_CONTROL_LOST"] = true, ["PLAYER_CONTROL_GAINED"] = true, ["PLAYER_FARSIGHT_FOCUS_CHANGED"] = true, ["UNIT_PET"] = true, ["PET_BAR_UPDATE"] = true, ["PET_BAR_UPDATE_COOLDOWN"] = true, } local buttontypes = { -- blizzard keybinds [3] = "MULTIACTIONBAR3BUTTON", [4] = "MULTIACTIONBAR4BUTTON", [5] = "MULTIACTIONBAR2BUTTON", [6] = "MULTIACTIONBAR1BUTTON", [11] = "SHAPESHIFTBUTTON", [12] = "BONUSACTIONBUTTON", -- additional keybinds [2] = "PFPAGING", [7] = "PFSTANCEONE", [8] = "PFSTANCETWO", [9] = "PFSTANCETHREE", [10] = "PFSTANCEFOUR", } local blizzbarmapping = { ["MultiBarRight"] = 3, ["MultiBarLeft"] = 4, ["MultiBarBottomRight"] = 5, ["MultiBarBottomLeft"] = 6, ["ShapeShiftBar"] = 11, ["BonusActionBar"] = 12, } local barnames = { [1] = "Main", [2] = "Paging", [3] = "Right", [4] = "Vertical", [5] = "Left", [6] = "Top", [7] = "StanceBar1", [8] = "StanceBar2", [9] = "StanceBar3", [10] = "StanceBar4", [11] = "Stances", [12] = "Pet", } -- Button animations scale and fade a copy of the icon through keyframes of -- { duration, scale, alpha, smoothing }. Styles with hideicon hide the icon -- while the copy plays. local button_animations = { ["zoomfade"] = { { .67, 2, 0, "NONE" }, }, ["shrinkreturn"] = { hideicon = true, { .13, .36, .36, "OUT" }, { .2, 1, 1, "IN" }, }, ["elasticzoom"] = { hideicon = true, { .1, .47, .47, "OUT" }, { .28, 1.12, 1, "IN_OUT" }, { .28, .97, .97, "IN_OUT" }, { .46, 1, 1, "IN_OUT" }, }, ["wobblezoom"] = { hideicon = true, { .07, .25, .25, "OUT" }, { .17, 1.41, 1, "IN_OUT" }, { .17, .77, .77, "IN_OUT" }, { .17, 1.12, 1, "IN_OUT" }, { .17, .93, .93, "IN_OUT" }, { .17, 1.04, 1, "IN_OUT" }, { .17, .98, .98, "IN_OUT" }, { .59, 1, 1, "IN_OUT" }, }, } local function CreateButtonAnimation(button, style) local group = button.animation.tex:CreateAnimationGroup() group:SetIgnoreFramerateThrottle(true) local scale, alpha = 1, 1 for order, key in ipairs(style) do local duration, nextscale, nextalpha, smoothing = unpack(key) local zoom = group:CreateAnimation("Scale") zoom:SetOrder(order) zoom:SetDuration(duration) zoom:SetSmoothing(smoothing) zoom:SetScale(nextscale / scale, nextscale / scale) local fade = group:CreateAnimation("Alpha") fade:SetOrder(order) fade:SetDuration(duration) fade:SetSmoothing(smoothing) fade:SetChange(nextalpha - alpha) scale, alpha = nextscale, nextalpha end local function Done() button.animation:Hide() button.icon:Show() end group:SetScript("OnFinished", Done) group:SetScript("OnStop", Done) return group end local function PlayButtonAnimation(button) local style = button_animations[C.bars.animation] if not style then return end local animation = button.animation animation.groups[style] = animation.groups[style] or CreateButtonAnimation(button, style) animation.tex:StopAnimating() animation.tex:SetTexture(button.icon:GetTexture()) animation.tex:SetVertexColor(button.icon:GetVertexColor()) animation:Show() if style.hideicon then button.icon:Hide() end animation.groups[style]:Play() end local function GetActiveBar() if CURRENT_ACTIONBAR_PAGE == 1 and GetBonusBarOffset() ~= 0 then return NUM_ACTIONBAR_PAGES + GetBonusBarOffset() else return CURRENT_ACTIONBAR_PAGE end end local function ButtonDrag(self) local self = self or this if _G.LOCK_ACTIONBAR == "1" and not (pfUI_config.bars.shiftdrag == "1" and IsShiftKeyDown()) then return end if self.bar == 12 then PickupPetAction(self.id) else PickupAction(self.id) end end local function ButtonDragStop(self) local self = self or this if MacroFrame_SaveMacro then MacroFrame_SaveMacro() end if self.bar == 12 then PickupPetAction(self.id) else PlaceAction(self.id) end end local mouse local function ButtonAnimate(self) local self = self or this mouse = arg1 and not keystate -- trigger action animation if ( pfUI_config.bars.keydown == "1" and keystate == "down" ) or (pfUI_config.bars.keydown == "0" and keystate == "up" ) or self.bar == 11 or mouse then if C.bars.animmode == "keypress" and ( self:GetAlpha() > .1 or C.bars.animalways == "1" ) then PlayButtonAnimation(self) end end -- handle button highlight if keystate == "down" then self.highlight:Show() elseif not MouseIsOverHitRect(self) then self.highlight:Hide() end end local function ButtonClick(self) local self = self or this local grid = self.bar == 12 and showgrid_pet or showgrid local mouse = arg1 and not keystate local keystate = keystate local slfcast = C.bars.altself == "1" and IsAltKeyDown() and true or self.slfcast slfcast = C.bars.rightself == "1" and arg1 and arg1 == "RightButton" and true or slfcast self.slfcast = nil if ( C.bars.keydown == "1" and keystate == "down" and not drag_active ) or (C.bars.keydown == "0" and keystate == "up" or drag_active ) or self.bar == 11 or mouse then if self.bar == 11 then CastShapeshiftForm(self.id) elseif grid == 1 then if self.bar == 12 then PickupPetAction(self.id) else PickupAction(self.id) end elseif self.bar == 12 then if arg1 == "LeftButton" then if IsPetAttackActive(self.id) then PetStopAttack() else CastPetAction(self.id) end else TogglePetAutocast(self.id) end else if MacroFrame_SaveMacro then MacroFrame_SaveMacro() end UseAction(self.id, nil, slfcast) end end end local function ButtonEnter(self) self = self or this -- indicate that dragging could get enabled, at once if shift is held drag_await = true AssumeButtonDrag() GameTooltip:ClearLines() GameTooltip_SetDefaultAnchor(GameTooltip, self) if self.bar == 11 then GameTooltip:SetShapeshift(self.id) elseif self.bar == 12 then local name, _, _, token = GetPetActionInfo(self.id) if token then GameTooltip:AddLine(_G[name]) GameTooltip:Show() else GameTooltip:SetPetAction(self.id) end elseif self.spellID then GameTooltip:SetSpellByID(self.spellID) else GameTooltip:SetAction(self.id) end self.highlight:Show() end local function ButtonLeave(self) self = self or this -- no longer wait for a drag event drag_await = nil self.highlight:Hide() GameTooltip:Hide() end -- item counts and equipped border, the parts of a slot an inventory change touches local function ButtonInventoryUpdate(self) if self.bar == 11 or self.bar == 12 then return end local sid = self.id -- update consumables if IsConsumableAction(sid) then self.count:SetText(GetActionCount(sid)) elseif IsReagentAction and IsReagentAction(sid) then self.count:SetText(GetReagentCount(sid)) else self.count:SetText(nil) end -- equipped item self.equipped:SetShown(IsEquippedAction(sid) and C.bars.showequipped == "1") end local start, duration, enable, castable, autocast, token local grid, sid, id, bar, active, texture, _ local function ButtonSlotUpdate(self) if not self then return end self = self or this sid = self.id -- 1 to 120 -- reset shared variables castable, autocast, token = nil, nil, nil -- set the own ID for compatibility to some vanilla addons self:SetID(self.id) grid = self.bar == 12 and showgrid_pet or showgrid if self.bar == 11 then -- stance button bar = self.bar id = sid texture, _, active = GetShapeshiftFormInfo(id) elseif self.bar == 12 then -- pet button bar = self.bar id = sid _, _, texture, token, active, castable, autocast = GetPetActionInfo(id) texture = token and _G[texture] or texture else active = IsCurrentAction(sid) or IsAutoRepeatAction(sid) texture = GetActionTexture(sid) bar = GetActiveBar() id = self.bar == 1 and self.slot or sid-((self.bar)-1)*12 end if not self.showempty and self.backdrop and not texture and grid == 0 then self.backdrop:Hide() self.hide = true else self.backdrop:Show() self.hide = nil end -- active border if active then if C.bars.animmode == "statechange" and not self.active:IsShown() then PlayButtonAnimation(self) end self.backdrop:SetBackdropBorderColor(cr,cg,cb,1) self.active:Show() else self.backdrop:SetBackdropBorderColor(er,eg,eb,ea) self.active:Hide() end ButtonInventoryUpdate(self) if self.bar ~= 11 and self.bar ~= 12 then -- update macro text if C.bars["bar"..self.bar] and C.bars["bar"..self.bar].showmacro == "1" then self.macro:SetText(GetActionText(sid)) else self.macro:SetText("") end end -- icon if texture ~= self.texture then self.icon:SetTexture(texture) self.texture = texture end -- handle pet bar quirks if self.bar == 12 then -- desaturate disabled petbar self.icon:SetDesaturated(not GetPetActionsUsable()) -- display autocast if autocast then self.autocast:Show() self.autocast:SetAlpha(self:GetAlpha() * 0.10) else self.autocast:Hide() end self.autocastable:SetShown(castable and C.bars.showcastable == "1") end -- keybinds if not self.hide and self.bar == 1 and self.bar ~= 11 and self.bar ~= 12 then self.keybind:SetText(GetBindingText(GetBindingKey("ACTIONBUTTON"..id), "KEY_", 1)) elseif not self.hide and buttontypes[self.bar] then self.keybind:SetText(GetBindingText(GetBindingKey(buttontypes[self.bar]..id), "KEY_", 1)) else self.keybind:SetText("") end end local sid, usable, oom, _ local function ButtonUsableUpdate(self) self = self or this sid = self.id -- 1 to 120 if self.bar == 11 then _, _, _, usable = GetShapeshiftFormInfo(sid) elseif self.bar == 12 then usable = true else usable, oom = IsUsableAction(sid) end -- update usable [out-of-range = 1, oom = 2, not-usable = 3, default = 0] if self.outofrange and C.bars.glowrange == "1" then if self.vertexstate ~= 1 then self.icon:SetVertexColor(self.rangeColor:GetRGBA()) self.vertexstate = 1 end elseif oom and C.bars.showoom == "1" then if self.vertexstate ~= 2 then self.icon:SetVertexColor(self.oomColor:GetRGBA()) self.vertexstate = 2 end elseif not usable and C.bars.showna == "1" then if self.vertexstate ~= 3 then self.icon:SetVertexColor(self.naColor:GetRGBA()) self.vertexstate = 3 end else if self.vertexstate ~= 0 then self.icon:SetVertexColor(1, 1, 1, 1) self.vertexstate = 0 end end end local function ButtonRangeUpdate(self) self = self or this -- update range display if C.bars.glowrange == "1" and self.bar ~= 11 and self.bar ~= 12 and HasAction(self.id) and ActionHasRange(self.id) and IsActionInRange(self.id) == 0 then if not self.outofrange then self.outofrange = true ButtonUsableUpdate(self) end elseif self.outofrange then self.outofrange = nil ButtonUsableUpdate(self) end end local start, duration, enable local function ButtonCooldownUpdate(button) if not button then return end if button.bar == 11 then start, duration, enable = GetShapeshiftFormCooldown(button.id) elseif button.bar == 12 then start, duration, enable = GetPetActionCooldown(button.id) else start, duration, enable = GetActionCooldown(button.id) end -- Nil-protect: GetActionCooldown can return nil during macro parsing/indexing if start and duration then CooldownFrame_SetTimer(button.cd, start, duration, enable or 1) end end local _, active local function ButtonIsActiveUpdate(button) if not button then return end if button.bar == 11 then _, _, active, _ = GetShapeshiftFormInfo(button.id) elseif button.bar == 12 then _, _, _, _, active, _, _ = GetPetActionInfo(button.id) else active = IsCurrentAction(button.id) or IsAutoRepeatAction(button.id) end -- active border if active then if not button.border_active then button.backdrop:SetBackdropBorderColor(cr,cg,cb,1) button.border_active = true end button.active:Show() else if button.border_active then button.backdrop:SetBackdropBorderColor(er,eg,eb,ea) button.border_active = nil end button.active:Hide() end end local function ButtonFullUpdate(button) if not button then return end ButtonSlotUpdate(button) ButtonRangeUpdate(button) ButtonUsableUpdate(button) ButtonCooldownUpdate(button) ButtonIsActiveUpdate(button) end local function BarsEvent(self) self = self or this -- refresh only specific slots if event == "ACTIONBAR_SLOT_CHANGED" and arg1 and arg1 ~= 0 then updatecache[arg1] = true return end -- run special refresh functions on next update if special_events[event] then eventcache[event] = true return end -- handle aura events if aura_events[event] then for j=1,12 do if self[11] and self[11][j] then updatecache[self[11][j].slot] = true end end return end -- handle pet events if pet_events[event] then for j=1,12 do if self[12] and self[12][j] then updatecache[self[12][j].slot] = true end end return end -- handle global events for id in pairs(buttoncache) do updatecache[id] = true end end local self, button, unlock -- Main update loop with throttle for performance optimization local function BarsUpdate(self) self = self or this -- Throttle for performance if (this.tick_main or 0) > GetTime() then return end this.tick_main = GetTime() + 0.025 if pfUI.unlock then -- update all bars when entering unlock if pfUI.unlock:IsShown() ~= unlock then pfUI.bars:UpdateConfig() unlock = pfUI.unlock:IsShown() end end -- The cached passes below only touch visible buttons. Hidden ones (other -- pages, hidden bars) are flagged stale and get a full update once the -- range pass sees them visible again. -- run cached inventory changes (ammo, reagents, consumables) if eventcache["UNIT_INVENTORY_CHANGED"] then eventcache["UNIT_INVENTORY_CHANGED"] = nil for id, button in pairs(buttoncache) do if button:IsVisible() then ButtonInventoryUpdate(button) ButtonCooldownUpdate(button) ButtonUsableUpdate(button) else button.stale = true end end end -- run cached usable usable actions if eventcache["ACTIONBAR_UPDATE_USABLE"] then eventcache["ACTIONBAR_UPDATE_USABLE"] = nil for id, button in pairs(buttoncache) do if button:IsVisible() then ButtonUsableUpdate(button) else button.stale = true end end end -- run cached cooldown events if eventcache["ACTIONBAR_UPDATE_COOLDOWN"] then eventcache["ACTIONBAR_UPDATE_COOLDOWN"] = nil for id, button in pairs(buttoncache) do if button:IsVisible() then ButtonCooldownUpdate(button) else button.stale = true end end end -- run cached action state events if eventcache["ACTIONBAR_UPDATE_STATE"] then eventcache["ACTIONBAR_UPDATE_STATE"] = nil for id, button in pairs(buttoncache) do if button:IsVisible() then ButtonIsActiveUpdate(button) else button.stale = true end end end for id in pairs(updatecache) do -- run updates based on slot pfUI.bars.ButtonFullUpdate(buttoncache[id]) -- run updates on paging actionbar if required for i=1,12 do if pfUI.bars[1][i].id == id then pfUI.bars.ButtonFullUpdate(pfUI.bars[1][i]) end end -- clear update cache updatecache[id] = nil end if ( this.tick or .2) > GetTime() then return else this.tick = GetTime() + .2 end for id, button in pairs(buttoncache) do if button.stale and button:IsVisible() then -- catch up on updates skipped while hidden button.stale = nil ButtonFullUpdate(button) elseif button:IsShown() then ButtonRangeUpdate(button) end end end -- create the main event and update handler for pfUI actionbars local bars = CreateFrame("Frame", "pfActionBar", UIParent) -- The only unit events in the tables above; both concern the player alone. -- A registration keeps its kind, so these have to go in unit-filtered from -- the start -- RegisterUnitEvent over a plain registration stays plain. local event_units = { ["UNIT_INVENTORY_CHANGED"] = "player", ["UNIT_PET"] = "player", } local function RegisterBarEvent(event) local unit = event_units[event] if unit then bars:RegisterUnitEvent(event, unit) else bars:RegisterEvent(event) end end for event in pairs(special_events) do RegisterBarEvent(event) end for event in pairs(global_events) do RegisterBarEvent(event) end for event in pairs(aura_events) do RegisterBarEvent(event) end for event in pairs(pet_events) do RegisterBarEvent(event) end -- refresh actionbar buttons on event bars:SetScript("OnEvent", BarsEvent) -- update actionbar buttons bars:SetScript("OnUpdate", BarsUpdate) -- enable bar paging via secure functions local function EnablePaging(bar) if not bar.pager then bar.pager = CreateFrame("Frame") bar.pager:RegisterEvent("PLAYER_ENTERING_WORLD") bar.pager:RegisterEvent("UPDATE_BONUS_ACTIONBAR") bar.pager:RegisterEvent("ACTIONBAR_PAGE_CHANGED") bar.pager:SetScript("OnEvent", function() for i=1, 10 do -- reload pageable bars local pageable = C.bars["bar"..i] and C.bars["bar"..i].pageable == "1" and true or nil _G.VIEWABLE_ACTION_BAR_PAGES[i] = pageable end local active = GetActiveBar() for i=1,12 do local id = i + (active-1)*12 bar[i].id = id updatecache[i] = true end end) end end local function SwitchBar(bar) if _G.CURRENT_ACTIONBAR_PAGE ~= bar then _G.CURRENT_ACTIONBAR_PAGE = bar ChangeActionBarPage(bar) end end local inCatForm = nil local prowlActive = nil -- Form IDs per vanilla 1.12 SpellShapeshiftForm.dbc. local CAT_FORM = 1 local SHADOWFORM = 28 local function HasCatForm() return GetShapeshiftFormID() == CAT_FORM and true or nil end local function InShadowform() return GetShapeshiftFormID() == SHADOWFORM and true or nil end local function FullScan() if class ~= "DRUID" then return nil end inCatForm = HasCatForm() prowlActive = inCatForm and IsStealthed() or nil return prowlActive end -- pagemaster / meta page switch do local formpage, shift, ctrl, alt, default = 8, 6, 5, 3, 1 -- while a page modifier is held, MOD-key must run the main bar binding on -- that key so the switched page fires. Overrides do this without touching -- the saved binding set. local pagemods = {} if C.bars.pagemasteralt == "1" then table.insert(pagemods, "ALT") end if C.bars.pagemasterctrl == "1" then table.insert(pagemods, "CTRL") end if C.bars.pagemastershift == "1" then table.insert(pagemods, "SHIFT") end if table.getn(pagemods) > 0 then local pagemaster = CreateFrame("Frame", "pfPageMaster", UIParent) pagemaster:RegisterEvent("PLAYER_ENTERING_WORLD") pagemaster:RegisterEvent("UPDATE_BINDINGS") pagemaster:SetScript("OnEvent", function() ClearOverrideBindings(this) for i = 1, 12 do for _, key in ipairs({ GetBindingKey("ACTIONBUTTON"..i) }) do -- skip keys that already carry a modifier, e.g. "SHIFT-Q" if not key:find("^%u+%-.") then for _, mod in ipairs(pagemods) do SetOverrideBinding(this, false, mod.."-"..key, "ACTIONBUTTON"..i) end end end end end) end -- setup page switch frame. `formpaging` is the shared "auto-page is -- active" flag; druid prowl and priest shadowform each drive it, and -- no character is ever both, so one flag covers both features. local formpaging = nil local pageswitch = CreateFrame("Frame", "pfActionBarPageSwitch", UIParent) pageswitch:RegisterEvent("PLAYER_ENTERING_WORLD") pageswitch:RegisterEvent("UPDATE_SHAPESHIFT_FORM") pageswitch:RegisterEvent("PLAYER_AURAS_CHANGED") pageswitch:RegisterEvent("PLAYER_LOGOUT") local PROWL_IDS = { [5215] = true, [6783] = true, [9913] = true } if class == "DRUID" then pageswitch:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player") end -- tracks prowl and shadowform from the page switch frame's events local function UpdateFormPaging() if class == "PRIEST" then if event == "UPDATE_SHAPESHIFT_FORM" or event == "PLAYER_ENTERING_WORLD" then formpaging = InShadowform() end return end if class ~= "DRUID" then return end if event == "PLAYER_ENTERING_WORLD" then formpaging = FullScan() return end -- Eager prowl detection on the cast itself — zero-latency vs. -- PLAYER_AURAS_CHANGED, which can land a frame or two behind it. -- arg3 = spellID if event == "UNIT_SPELLCAST_SUCCEEDED" then if PROWL_IDS[arg3] then inCatForm = true prowlActive = true formpaging = true end return end if event == "UPDATE_SHAPESHIFT_FORM" then -- Authoritative form-change signal — recompute and short-circuit. inCatForm = HasCatForm() if not inCatForm then prowlActive = nil formpaging = nil end return end -- PLAYER_AURAS_CHANGED: form changes already went through -- UPDATE_SHAPESHIFT_FORM above; this handler only watches prowl on/off. if inCatForm then if IsStealthed() then prowlActive = true formpaging = true else prowlActive = nil formpaging = nil end end end -- sets the page the held modifier and the form ask for. It also runs on -- every page change, so a page switched by other means snaps back. local function UpdatePage() local page = _G.CURRENT_ACTIONBAR_PAGE -- switch actionbar page depending on meta key that is pressed if C.bars.pagemastershift == "1" and ShiftDown() then page = shift elseif C.bars.pagemasterctrl == "1" and CtrlDown() then page = ctrl elseif C.bars.pagemasteralt == "1" and AltDown() then page = alt elseif C.bars.pagemasteralt == "1" or C.bars.pagemasterctrl == "1" or C.bars.pagemastershift == "1" then page = default end -- switch actionbar page while druid stealth / priest shadowform is active if (class == "DRUID" and C.bars.druidstealth == "1") or (class == "PRIEST" and C.bars.priestshadow == "1") then if formpaging and page == default then page = formpage elseif not formpaging and page == formpage then page = default end end SwitchBar(page) end pageswitch:SetScript("OnEvent", function() if event == "PLAYER_LOGOUT" then this:UnregisterAllEvents() this:SetScript("OnEvent", nil) return end UpdateFormPaging() UpdatePage() end) pfUI.events:RegisterFrameEventAndCallback("MODIFIER_STATE_CHANGED", UpdatePage) pfUI.events:RegisterFrameEventAndCallback("ACTIONBAR_PAGE_CHANGED", UpdatePage) end local function CreateActionButton(parent, bar, button) -- load config local size = C.bars["bar"..bar].icon_size local font = pfUI.media[C.bars.font] local font_offset = tonumber(C.bars.font_offset) local macro_size = tonumber(C.bars.macro_size) local macro_color = { GetStringColor(C.bars.macro_color) } local count_size = tonumber(C.bars.count_size) local count_color = { GetStringColor(C.bars.count_color) } local bind_size = tonumber(C.bars.bind_size) local bind_color = { GetStringColor(C.bars.bind_color) } local showempty = C.bars["bar"..bar].showempty local showmacro = C.bars["bar"..bar].showmacro local showkybind = C.bars["bar"..bar].showkeybind local showcount = C.bars["bar"..bar].showcount -- sanitize font sizes if macro_size == 0 then macro_size = 1 end if count_size == 0 then macro_size = 1 end if bind_size == 0 then macro_size = 1 end local button_name = "pfActionBar" .. barnames[bar] .. "Button" .. button local id = (bar-1)*12+button local exists = _G[button_name] and true or nil local f = _G[button_name] or CreateFrame("Button", button_name, parent) -- no button available, create a new one if not exists then -- prepare the button for vanilla if not f.HookScript then f.HookScript = HookScript end f:SetScript("OnClick", ButtonClick) if bar ~= 11 then f:RegisterForDrag("LeftButton", "RightButton") f:SetScript("OnDragStart", ButtonDrag) f:SetScript("OnReceiveDrag", ButtonDragStop) end -- add mouseovers f:SetScript("OnEnter", ButtonEnter) f:SetScript("OnLeave", ButtonLeave) -- add click animation handler f:HookScript("OnClick", ButtonAnimate) f.id = id -- set a static slot f.slot = id -- cooldown f.cd = CreateCooldown(f, "actionbar", f:GetName() .. "Cooldown") -- icon f.icon = f:CreateTexture(button_name .. "Icon", "BACKGROUND") f.icon:SetTexCoord(.08, .92, .08, .92) f.icon:SetAllPoints() -- animation f.animation = CreateFrame("Frame", button_name .. "Animation", f) f.animation.groups = {} f.animation:SetAllPoints() f.animation:Hide() f.animation.tex = f.animation:CreateTexture(button_name .. "AnimationTexture", "BACKGROUND") f.animation.tex:SetTexCoord(.08, .92, .08, .92) f.animation.tex:SetAllPoints() if bar ~= 11 and bar ~= 12 then -- equipped item f.equipped = f:CreateTexture(nil, "BORDER") f.equipped:SetAllPoints() f.equipped:Hide() elseif bar == 12 then f.autocast = CreateFrame("Model", nil, f) f.autocast:SetAllPoints() f.autocast:SetModel("Interface\\Buttons\\UI-AutoCastButton.mdx") f.autocast:SetSequence(0) f.autocast:SetSequenceTime(0, 0) f.autocast:Hide() f.autocastable = f:CreateTexture(nil, "BORDER") f.autocastable:SetAllPoints() f.autocastable:SetTexture("Interface\\Buttons\\UI-AutoCastableOverlay") f.autocastable:SetTexCoord(.25, .75, .25, .75) end -- macro f.macro = f:CreateFontString(button_name .. "Name", "LOW", "GameFontNormal") -- keybind f.keybind = f:CreateFontString(nil, "LOW", "GameFontNormal") -- itemcount f.count = f:CreateFontString(button_name .. "Count", "LOW", "GameFontNormal") -- highlight f.highlight = CreateFrame("Frame", nil, f) f.highlight:SetBackdrop(backdrop_highlight) f.highlight:SetBackdropBorderColor(1,1,1,.8) f.highlight:SetAllPoints() f.highlight:Hide() -- active f.active = CreateFrame("Frame", nil, f) f.active:SetBackdrop(backdrop_highlight) f.active:SetBackdropBorderColor(1,1,.5,1) f.active:SetAllPoints() f.active:Hide() -- add to buttoncache buttoncache[id] = f end -- set keydown option if C.bars.keydown == "1" then f:RegisterForClicks("LeftButtonDown", "RightButtonDown") else f:RegisterForClicks("LeftButtonUp", "RightButtonUp") end -- pet autocast if bar == 12 then f.autocast:SetScale(C.bars["bar"..bar].icon_size / 25) f.autocast:SetAlpha(.10) end -- macro options f.macro:SetShown(showmacro == "1") SetAllPointsOffset(f.macro, f, font_offset, -font_offset) f.macro:SetFont(font, macro_size, "OUTLINE") f.macro:SetTextColor(unpack(macro_color)) f.macro:SetJustifyH("LEFT") f.macro:SetJustifyV("BOTTOM") -- keybind options f.keybind:SetShown(showkybind == "1") SetAllPointsOffset(f.keybind, f, font_offset, -font_offset) f.keybind:SetFont(font, bind_size, "OUTLINE") f.keybind:SetTextColor(unpack(bind_color)) f.keybind:SetJustifyH("RIGHT") f.keybind:SetJustifyV("TOP") f.keybind:SetNonSpaceWrap(false) -- item count options f.count:SetShown(showcount == "1") SetAllPointsOffset(f.count, f, font_offset, -font_offset) f.count:SetFont(font, count_size, "OUTLINE") f.count:SetTextColor(unpack(count_color)) f.count:SetJustifyH("RIGHT") f.count:SetJustifyV("BOTTOM") -- range glow color f.rangeColor = GetStringColorObject(C.bars.rangecolor) -- out of mana color f.oomColor = GetStringColorObject(C.bars.oomcolor) -- not usable color f.naColor = GetStringColorObject(C.bars.nacolor) -- equipped color if f.equipped then f.equipped:SetColorTexture(GetStringColor(C.bars.eqcolor)) end -- general appearance f.showempty = showempty == "1" and true or nil f:SetSize(size, size) CreateBackdrop(f, border) return f end local function CreateActionBar(i) -- load config local buttonbasename = "pfActionBar" .. barnames[i] .. "Button" local enable = C.bars["bar"..i].enable local size = C.bars["bar"..i].icon_size local spacing = C.bars["bar"..i].spacing local background = C.bars["bar"..i].background local formfactor = C.bars["bar"..i].formfactor local uneven = C.bars["bar"..i].uneven or "Down" local fillmode = C.bars["bar"..i].fillmode or "auto" -- validate uneven against bar shape and reset if incompatible local _, _, fcols2, frows2 = string.find(tostring(formfactor or ""), "(%d+)%s*x%s*(%d+)") fcols2, frows2 = tonumber(fcols2), tonumber(frows2) local f_is_square = fcols2 == 3 and frows2 == 3 local f_is_vertical = fcols2 and fcols2 <= 3 and not f_is_square local f_is_horizontal = frows2 and frows2 <= 3 and not f_is_square if f_is_vertical then if uneven ~= "Up" and uneven ~= "Down" then uneven = "Down" end elseif f_is_horizontal then if uneven ~= "Left" and uneven ~= "Right" then uneven = "Down" end end C.bars["bar"..i].uneven = uneven local autohide = C.bars["bar"..i].autohide local hide_time = C.bars["bar"..i].hide_time local hide_combat = C.bars["bar"..i].hide_combat == "1" and true or nil local buttons = tonumber(C.bars["bar"..i].buttons) or 12 if i == 11 and bars[i] then -- shapeshift buttons buttons = GetNumShapeshiftForms() elseif i == 12 and bars[i] then -- pet buttons buttons = NUM_PET_ACTION_SLOTS elseif i == 12 or i == 11 then -- Fallback to 10 buttons on shapeshift and petbar during initialization. -- This lets us load bars that aren't yet available for your class or level. buttons = 10 end -- don't process empty bars if buttons == 0 then bars[i]:Hide() return end -- the stored layout is invalid, temporary fallback if not fcols2 or not frows2 then formfactor = BarLayoutOptions(buttons)[1] end local font = pfUI.font_unit local font_size = C.global.font_unit_size local realsize = size+border*2 -- create frame local init = not bars[i] bars[i] = bars[i] or CreateFrame("Frame", "pfActionBar" .. barnames[i], UIParent) bars[i]:SetID(i) -- autohide if autohide == "1" then EnableAutohide(bars[i], tonumber(hide_time), hide_combat) else DisableAutohide(bars[i]) end -- apply visible settings if enable == "1" then -- handle pet bar if i == 12 then -- only show when pet actions exists bars[i]:SetShown(PetHasActionBar() or pfUI.unlock and pfUI.unlock:IsShown()) -- show/hide petbar on petbar updates if init then bars[i]:RegisterEvent("PET_BAR_UPDATE") bars[i]:SetScript("OnEvent", function() -- hide obsolete buttons for i=1, NUM_PET_ACTION_SLOTS do if not PetHasActionBar() and bars[12][i] then bars[12][i]:Hide() end end -- refresh layout CreateActionBar(12) end) end -- handle shapeshift bar elseif i == 11 then -- only show when shapeshifts exist bars[i]:SetShown(GetNumShapeshiftForms() > 0) -- update shapeshift bar when amount of spells changes if init then bars[i]:RegisterEvent("PLAYER_ENTERING_WORLD") bars[i]:RegisterEvent("UPDATE_BONUS_ACTIONBAR") bars[i]:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") bars[i]:SetScript("OnEvent", function() local count = GetNumShapeshiftForms() if count ~= this.lastCount then this.lastCount = count -- hide obsolete buttons for i=1, NUM_SHAPESHIFT_SLOTS do if i > GetNumShapeshiftForms() and bars[11][i] then bars[11][i]:Hide() end end -- create new buttons and refresh layout CreateActionBar(11) end end) end -- regular bars else bars[i]:Show() end elseif enable == "0" then bars[i]:UnregisterAllEvents() bars[i]:Hide() end -- create action buttons local maxbuttons = (i == 11 or i == 12) and 10 or 12 for j=1,maxbuttons do bars[i][j] = CreateActionButton(bars[i], i, j) bars[i][j].bar = i BarButtonAnchor(bars[i][j], buttonbasename, j, buttons, formfactor, size, border, spacing, uneven, fillmode) bars[i][j]:ClearAllPoints() bars[i][j]:SetPoint(unpack(bars[i][j]._anchor)) bars[i][j]:Show() if i > 10 then bars[i][j].id = j end -- refresh button updatecache[bars[i][j].slot] = true end for j=buttons+1,12 do if bars[i][j] then bars[i][j]:Hide() end end -- enable paging for the first actionbar if i == 1 then EnablePaging(bars[i]) end -- adjust actionbar size BarLayoutSize(bars[i], buttons, formfactor, size, border, spacing, uneven, fillmode) bars[i]:SetSize(bars[i]._size[1], bars[i]._size[2]) bars[i]:ClearAllPoints() if i == 1 then -- main bars[i]:SetPoint("BOTTOM", 0, 2*border) elseif i == 3 then -- left bars[i]:SetPoint("BOTTOMLEFT", bars[1], "BOTTOMRIGHT", 3*border, 0) elseif i == 4 then -- vertical bars[i]:SetPoint("RIGHT", -2*border, 0) elseif i == 5 then -- right bars[i]:SetPoint("BOTTOMRIGHT", bars[1], "BOTTOMLEFT", -3*border, 0) elseif i == 6 then -- top bars[i]:SetPoint("BOTTOM", bars[1], "TOP", 0, -spacing) elseif i == 11 then -- stances bars[i]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border) elseif i == 12 then -- pet bars[i]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border) -- make stance bar dodge by default bars[i]:SetScript("OnShow", function() if pfUI.unlock and pfUI.unlock:IsShown() then return end if bars[11] and bars[11]:IsShown() then bars[11]:ClearAllPoints() bars[11]:SetPoint("BOTTOM", bars[12], "TOP", 0, 3*border) UpdateMovable(bars[11], true) end end) -- restore old stance bar position bars[i]:SetScript("OnHide", function() if pfUI.unlock and pfUI.unlock:IsShown() then return end if bars[11] and bars[11]:IsShown() then bars[11]:ClearAllPoints() bars[11]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border) UpdateMovable(bars[11], true) end end) else -- others bars[i]:SetPoint("TOP", 0, -i*50) end UpdateMovable(bars[i]) -- apply backdrop settings if background == "1" then CreateBackdrop(bars[i], border) CreateBackdropShadow(bars[i]) bars[i].backdrop:Show() elseif bars[i].backdrop then bars[i].backdrop:Hide() end -- share backdrop of main and top actionbar if bars[6] and bars[1] then bars[6].OnMove = bars[6].OnMove or function() bars[1].mergedBackdrop = bars[1].mergedBackdrop or CreateFrame("Frame", nil, UIParent) bars[1].mergedBackdrop:SetPoint("TOPLEFT", bars[6], "TOPLEFT", 0, 0) bars[1].mergedBackdrop:SetPoint("BOTTOMRIGHT", bars[1], "BOTTOMRIGHT", 0, 0) CreateBackdrop(bars[1].mergedBackdrop) local _, anchor, _ = bars[6]:GetPoint() if anchor == bars[1] and C.bars.bar1.enable == "1" and C.bars.bar1.enable == "1" and C.bars.bar6.enable == "1" and C.bars.bar1.background == "1" and C.bars.bar6.background == "1" and C.bars.bar1.autohide == "0" and C.bars.bar6.autohide == "0" and C.bars.bar1.icon_size == C.bars.bar6.icon_size and C.bars.bar1.spacing == C.bars.bar6.spacing and C.bars.bar1.formfactor == C.bars.bar6.formfactor and C.bars.bar1.buttons == C.bars.bar6.buttons then bars[1].mergedBackdrop:Show() if C.bars.bar1.background == "1" and bars[1].backdrop then bars[1].backdrop:Hide() end if C.bars.bar6.background == "1" and bars[6].backdrop then bars[6].backdrop:Hide() end else bars[1].mergedBackdrop:Hide() if C.bars.bar1.background == "1" and bars[1].backdrop then bars[1].backdrop:Show() end if C.bars.bar6.background == "1" and bars[6].backdrop then bars[6].backdrop:Show() end end end bars[6].OnMove() end end -- create actionbars pfUI.bars = bars pfUI.bars.update = updatecache pfUI.bars.buttons = buttoncache pfUI.bars.ButtonFullUpdate = ButtonFullUpdate pfUI.bars.ButtonEnter = ButtonEnter pfUI.bars.ButtonLeave = ButtonLeave pfUI.bars.UpdateGrid = function(self, state, typ) if not typ then showgrid = state for id in pairs(buttoncache) do updatecache[id] = true end elseif typ == "PET" then showgrid_pet = state for slot=133,142 do updatecache[slot] = true end end end pfUI.bars.UpdateConfig = function(self) for i=1,12 do CreateActionBar(i) end end pfUI.bars:UpdateConfig() -- Localize custom keybinds for additional actionbars (see Bindings.xml) local names = { ["PAGING"] = T["Paging Actionbar"], ["STANCEONE"] = T["Stance Bar 1"], ["STANCETWO"] = T["Stance Bar 2"], ["STANCETHREE"] = T["Stance Bar 3"], ["STANCEFOUR"] = T["Stance Bar 4"], } for name, loc in pairs(names) do _G["BINDING_HEADER_PFBAR"..name] = T["Action Bar"] .. " " .. loc for i=1,12 do _G["BINDING_NAME_PF" .. name .. i] = loc .. " " .. T["Button"] .. " " .. i end end -- Map Keybinds to button clicks function _G.pfActionButton(slot, slfcast, opt) if ChatFrameEditBox:IsShown() then return end local bar, button = 1, slot -- determine the proper bar and button if opt and blizzbarmapping[opt] then bar = blizzbarmapping[opt] elseif slot > 12 then bar, button = ceil(slot/12), mod(slot, 12) button = button == 0 and 12 or button end local frame = bars[bar][button] if frame then frame.slfcast = slfcast frame:Click() end end -- Set keybinds to all actionbuttons -- In order to be able to reuse already defined keybinds, we need to remap -- existing button functions to pfUI. We need to get rid of the blizzard calls -- to avoid having them call texture changes and errors due to missing buttons _G.ActionButtonDown = pfActionButton _G.ActionButtonUp = pfActionButton _G.BonusActionButtonDown = function(slot) pfActionButton(slot, nil, "BonusActionBar") end _G.BonusActionButtonUp = function(slot) pfActionButton(slot, nil, "BonusActionBar") end _G.MultiActionButtonDown = function(bar, slot, slf) pfActionButton(slot, slf, bar) end _G.MultiActionButtonUp = function(bar, slot, slf) pfActionButton(slot, slf, bar) end _G.ShapeshiftBar_ChangeForm = function(slot) pfActionButton(slot, nil, "ShapeShiftBar") end -- handle drag-drop grid pfUI.events:RegisterFrameEventAndCallback("ACTIONBAR_SHOWGRID", function() pfUI.bars:UpdateGrid(1) end) pfUI.events:RegisterFrameEventAndCallback("ACTIONBAR_HIDEGRID", function() pfUI.bars:UpdateGrid(0) end) pfUI.events:RegisterFrameEventAndCallback("PET_BAR_SHOWGRID", function() pfUI.bars:UpdateGrid(1, "PET") end) pfUI.events:RegisterFrameEventAndCallback("PET_BAR_HIDEGRID", function() pfUI.bars:UpdateGrid(0, "PET") end) -- reagent counter if C.bars.reagents == "1" then local reagent_slots = { } -- action slot -> first reagent itemID local reagent_counts = { } -- itemID -> count in player's inventory -- For a given action slot, resolve the spell's first reagent itemID -- via GetActionInfo + C_Spell.GetSpellReagents. Macro actions and -- bag-item actions are skipped (their reagent resolution would need a -- macro-body parse / item-effect lookup that we don't bother with). local function UpdateSlot(slot) local newID = nil if HasAction(slot) then local kind, spellID = GetActionInfo(slot) if kind == "spell" and spellID then local reagents = C_Spell.GetSpellReagents(spellID) if reagents and reagents[1] then newID = reagents[1].itemID end end end if reagent_slots[slot] ~= newID then reagent_slots[slot] = newID if newID then reagent_counts[newID] = reagent_counts[newID] or C_Item.GetItemCount(newID) end updatecache[slot] = true end end -- Recount every tracked reagent and flag its buttons for a refresh. local RecountReagents = function() for itemID in pairs(reagent_counts) do reagent_counts[itemID] = C_Item.GetItemCount(itemID) end for slot in pairs(reagent_slots) do updatecache[slot] = true end end local reagentcounter = CreateFrame("Frame", "pfReagentCounter", UIParent) reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD") reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED") reagentcounter:RegisterEvent("BAG_UPDATE_DELAYED") reagentcounter:SetScript("OnEvent", function() if event == "ACTIONBAR_SLOT_CHANGED" then -- arg1 is the changed slot; 0 (or nil) means "all slots" if arg1 and arg1 > 0 then UpdateSlot(arg1) else for slot = 1, 120 do UpdateSlot(slot) end end elseif event == "BAG_UPDATE_DELAYED" then -- inventory changed: refresh the counts we already track RecountReagents() else -- PLAYER_ENTERING_WORLD: seed the full reagent map (UpdateSlot seeds -- each new reagent's count; a BAG_UPDATE_DELAYED follows during login) for slot = 1, 120 do UpdateSlot(slot) end end end) function IsReagentAction(slot) return reagent_slots[slot] and true or nil end function GetReagentCount(slot) return reagent_counts[reagent_slots[slot]] end end end)