--[[ Copyright (c) 2010-2020, Hendrik "nevcairiel" Leppkes All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the developer nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]] local MAJOR_VERSION = "LibActionButton-1.0-ElvUI" local MINOR_VERSION = 22 -- the real minor version is 79 if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end local lib, oldversion = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) if not lib then return end -- Lua functions local type, error, tostring, tonumber, assert, select = type, error, tostring, tonumber, assert, select local setmetatable, wipe, unpack, pairs, next = setmetatable, wipe, unpack, pairs, next local str_match, format, tinsert, tremove = string.match, format, tinsert, tremove local WoWClassic = select(4, GetBuildInfo()) < 20000 local KeyBound = LibStub("LibKeyBound-1.0", true) local CBH = LibStub("CallbackHandler-1.0") local LBG = LibStub("LibButtonGlow-1.0", true) local Masque = LibStub("Masque", true) lib.eventFrame = lib.eventFrame or CreateFrame("Frame") lib.eventFrame:UnregisterAllEvents() lib.buttonRegistry = lib.buttonRegistry or {} lib.activeButtons = lib.activeButtons or {} lib.actionButtons = lib.actionButtons or {} lib.nonActionButtons = lib.nonActionButtons or {} lib.ChargeCooldowns = lib.ChargeCooldowns or {} lib.NumChargeCooldowns = lib.NumChargeCooldowns or 0 lib.ACTION_HIGHLIGHT_MARKS = lib.ACTION_HIGHLIGHT_MARKS or setmetatable({}, { __index = ACTION_HIGHLIGHT_MARKS }) lib.callbacks = lib.callbacks or CBH:New(lib) local Generic = CreateFrame("CheckButton") local Generic_MT = {__index = Generic} local Action = setmetatable({}, {__index = Generic}) local Action_MT = {__index = Action} local PetAction = setmetatable({}, {__index = Generic}) local PetAction_MT = {__index = PetAction} local Spell = setmetatable({}, {__index = Generic}) local Spell_MT = {__index = Spell} local Item = setmetatable({}, {__index = Generic}) local Item_MT = {__index = Item} local Macro = setmetatable({}, {__index = Generic}) local Macro_MT = {__index = Macro} local Toy = setmetatable({}, {__index = Generic}) local Toy_MT = {__index = Toy} local Custom = setmetatable({}, {__index = Generic}) local Custom_MT = {__index = Custom} local type_meta_map = { empty = Generic_MT, action = Action_MT, --pet = PetAction_MT, spell = Spell_MT, item = Item_MT, macro = Macro_MT, toy = Toy_MT, custom = Custom_MT } local ButtonRegistry, ActiveButtons, ActionButtons, NonActionButtons = lib.buttonRegistry, lib.activeButtons, lib.actionButtons, lib.nonActionButtons local Update, UpdateButtonState, UpdateUsable, UpdateCount, UpdateCooldown, UpdateTooltip, UpdateNewAction, UpdateSpellHighlight, ClearNewActionHighlight local StartFlash, StopFlash, UpdateFlash, UpdateHotkeys, UpdateRangeTimer, UpdateOverlayGlow local UpdateFlyout, ShowGrid, HideGrid, UpdateGrid, SetupSecureSnippets, WrapOnClick local ShowOverlayGlow, HideOverlayGlow local EndChargeCooldown local UpdateRange -- Sezz: new method local InitializeEventHandler, OnEvent, ForAllButtons, OnUpdate local function GameTooltip_GetOwnerForbidden() if GameTooltip:IsForbidden() then return nil end return GameTooltip:GetOwner() end local DefaultConfig = { outOfRangeColoring = "button", tooltip = "enabled", showGrid = false, useColoring = true, colors = { range = { 0.8, 0.1, 0.1 }, mana = { 0.5, 0.5, 1.0 }, usable = { 1.0, 1.0, 1.0 }, notUsable = { 0.4, 0.4, 0.4 }, }, hideElements = { macro = false, hotkey = false, equipped = false, }, keyBoundTarget = false, clickOnDown = false, flyoutDirection = "UP", disableCountDownNumbers = false, useDrawBling = true, useDrawSwipeOnCharges = true, handleOverlay = true, } --- Create a new action button. -- @param id Internal id of the button (not used by LibActionButton-1.0, only for tracking inside the calling addon) -- @param name Name of the button frame to be created (not used by LibActionButton-1.0 aside from naming the frame) -- @param header Header that drives these action buttons (if any) function lib:CreateButton(id, name, header, config) if type(name) ~= "string" then error("Usage: CreateButton(id, name. header): Buttons must have a valid name!", 2) end if not header then error("Usage: CreateButton(id, name, header): Buttons without a secure header are not yet supported!", 2) end if not KeyBound then KeyBound = LibStub("LibKeyBound-1.0", true) end local button = setmetatable(CreateFrame("CheckButton", name, header, "SecureActionButtonTemplate, ActionButtonTemplate"), Generic_MT) button:RegisterForDrag("LeftButton", "RightButton") button:RegisterForClicks("AnyUp") button.cooldown:SetFrameStrata(button:GetFrameStrata()) button.cooldown:SetFrameLevel(button:GetFrameLevel() + 1) -- Frame Scripts button:SetScript("OnEnter", Generic.OnEnter) button:SetScript("OnLeave", Generic.OnLeave) button:SetScript("PreClick", Generic.PreClick) button:SetScript("PostClick", Generic.PostClick) button.id = id button.header = header -- Mapping of state -> action button.state_types = {} button.state_actions = {} -- Store the LAB Version that created this button for debugging button.__LAB_Version = MINOR_VERSION -- just in case we're not run by a header, default to state 0 button:SetAttribute("state", 0) SetupSecureSnippets(button) WrapOnClick(button) -- adjust hotkey style for better readability button.HotKey:SetFont(button.HotKey:GetFont(), 13, "OUTLINE") button.HotKey:SetVertexColor(0.75, 0.75, 0.75) button.HotKey:SetPoint("TOPLEFT", button, "TOPLEFT", -2, -4) -- adjust count/stack size button.Count:SetFont(button.Count:GetFont(), 16, "OUTLINE") -- Store the button in the registry, needed for event and OnUpdate handling if not next(ButtonRegistry) then InitializeEventHandler() end ButtonRegistry[button] = true button:UpdateConfig(config) -- run an initial update button:UpdateAction() UpdateHotkeys(button) -- somewhat of a hack for the Flyout buttons to not error. button.action = 0 lib.callbacks:Fire("OnButtonCreated", button) return button end function SetupSecureSnippets(button) button:SetAttribute("_custom", Custom.RunCustom) -- secure UpdateState(self, state) -- update the type and action of the button based on the state button:SetAttribute("UpdateState", [[ local state = ... self:SetAttribute("state", state) local type, action = (self:GetAttribute(format("labtype-%s", state)) or "empty"), self:GetAttribute(format("labaction-%s", state)) self:SetAttribute("type", type) if type ~= "empty" and type ~= "custom" then local action_field = (type == "pet") and "action" or type self:SetAttribute(action_field, action) self:SetAttribute("action_field", action_field) end local onStateChanged = self:GetAttribute("OnStateChanged") if onStateChanged then self:Run(onStateChanged, state, type, action) end ]]) -- this function is invoked by the header when the state changes button:SetAttribute("_childupdate-state", [[ self:RunAttribute("UpdateState", message) self:CallMethod("UpdateAction") ]]) -- secure PickupButton(self, kind, value, ...) -- utility function to place a object on the cursor button:SetAttribute("PickupButton", [[ local kind, value = ... if kind == "empty" then return "clear" elseif kind == "action" or kind == "pet" then local actionType = (kind == "pet") and "petaction" or kind return actionType, value elseif kind == "spell" or kind == "item" or kind == "macro" then return "clear", kind, value else print("LibActionButton-1.0: Unknown type: " .. tostring(kind)) return false end ]]) button:SetAttribute("OnDragStart", [[ if (self:GetAttribute("buttonlock") and not IsModifiedClick("PICKUPACTION")) or self:GetAttribute("LABdisableDragNDrop") then return false end local state = self:GetAttribute("state") local type = self:GetAttribute("type") -- if the button is empty, we can't drag anything off it if type == "empty" or type == "custom" then return false end -- Get the value for the action attribute local action_field = self:GetAttribute("action_field") local action = self:GetAttribute(action_field) -- non-action fields need to change their type to empty if type ~= "action" and type ~= "pet" then self:SetAttribute(format("labtype-%s", state), "empty") self:SetAttribute(format("labaction-%s", state), nil) -- update internal state self:RunAttribute("UpdateState", state) -- send a notification to the insecure code self:CallMethod("ButtonContentsChanged", state, "empty", nil) end -- return the button contents for pickup return self:RunAttribute("PickupButton", type, action) ]]) button:SetAttribute("OnReceiveDrag", [[ if self:GetAttribute("LABdisableDragNDrop") then return false end local kind, value, subtype, extra = ... if not kind or not value then return false end local state = self:GetAttribute("state") local buttonType, buttonAction = self:GetAttribute("type"), nil if buttonType == "custom" then return false end -- action buttons can do their magic themself -- for all other buttons, we'll need to update the content now if buttonType ~= "action" and buttonType ~= "pet" then -- with "spell" types, the 4th value contains the actual spell id if kind == "spell" then if extra then value = extra else print("no spell id?", ...) end elseif kind == "item" and value then value = format("item:%d", value) end -- Get the action that was on the button before if buttonType ~= "empty" then buttonAction = self:GetAttribute(self:GetAttribute("action_field")) end -- TODO: validate what kind of action is being fed in here -- We can only use a handful of the possible things on the cursor -- return false for all those we can't put on buttons self:SetAttribute(format("labtype-%s", state), kind) self:SetAttribute(format("labaction-%s", state), value) -- update internal state self:RunAttribute("UpdateState", state) -- send a notification to the insecure code self:CallMethod("ButtonContentsChanged", state, kind, value) else -- get the action for (pet-)action buttons buttonAction = self:GetAttribute("action") end return self:RunAttribute("PickupButton", buttonType, buttonAction) ]]) button:SetScript("OnDragStart", nil) -- Wrapped OnDragStart(self, button, kind, value, ...) SecureHandlerWrapScript(button, "OnDragStart", button.header, [[return self:RunAttribute("OnDragStart")]] ) -- Wrap twice, because the post-script is not run when the pre-script causes a pickup (doh) -- we also need some phony message, or it won't work =/ SecureHandlerWrapScript(button, "OnDragStart", button.header, [[return "message", "update"]], [[self:RunAttribute("UpdateState", self:GetAttribute("state"))]] ) button:SetScript("OnReceiveDrag", nil) -- Wrapped OnReceiveDrag(self, button, kind, value, ...) SecureHandlerWrapScript(button, "OnReceiveDrag", button.header, [[return self:RunAttribute("OnReceiveDrag", kind, value, ...)]] ) -- Wrap twice, because the post-script is not run when the pre-script causes a pickup (doh) -- we also need some phony message, or it won't work =/ SecureHandlerWrapScript(button, "OnReceiveDrag", button.header, [[return "message", "update"]], [[self:RunAttribute("UpdateState", self:GetAttribute("state"))]] ) end function WrapOnClick(button) -- Wrap OnClick, to catch changes to actions that are applied with a click on the button. SecureHandlerWrapScript(button, "OnClick", button.header, [[if self:GetAttribute("type") == "action" then local type, action = GetActionInfo(self:GetAttribute("action")) return nil, format("%s|%s", tostring(type), tostring(action)) end]], [[local type, action = GetActionInfo(self:GetAttribute("action")) if message ~= format("%s|%s", tostring(type), tostring(action)) then self:RunAttribute("UpdateState", self:GetAttribute("state")) end]] ) end ----------------------------------------------------------- --- utility function lib:GetAllButtons() local buttons = {} for button in next, ButtonRegistry do buttons[button] = true end return buttons end function Generic:ClearSetPoint(...) self:ClearAllPoints() self:SetPoint(...) end function Generic:NewHeader(header) self.header = header self:SetParent(header) SetupSecureSnippets(self) WrapOnClick(self) end ----------------------------------------------------------- --- state management function Generic:ClearStates() for state in pairs(self.state_types) do self:SetAttribute(format("labtype-%s", state), nil) self:SetAttribute(format("labaction-%s", state), nil) end wipe(self.state_types) wipe(self.state_actions) end function Generic:SetState(state, kind, action) if not state then state = self:GetAttribute("state") end state = tostring(state) -- we allow a nil kind for setting a empty state if not kind then kind = "empty" end if not type_meta_map[kind] then error("SetStateAction: unknown action type: " .. tostring(kind), 2) end if kind ~= "empty" and action == nil then error("SetStateAction: an action is required for non-empty states", 2) end if kind ~= "custom" and action ~= nil and type(action) ~= "number" and type(action) ~= "string" or (kind == "custom" and type(action) ~= "table") then error("SetStateAction: invalid action data type, only strings and numbers allowed", 2) end if kind == "item" then if tonumber(action) then action = format("item:%s", action) else local itemString = str_match(action, "^|c%x+|H(item[%d:]+)|h%[") if itemString then action = itemString end end end self.state_types[state] = kind self.state_actions[state] = action self:UpdateState(state) end function Generic:UpdateState(state) if not state then state = self:GetAttribute("state") end state = tostring(state) self:SetAttribute(format("labtype-%s", state), self.state_types[state]) self:SetAttribute(format("labaction-%s", state), self.state_actions[state]) if state ~= tostring(self:GetAttribute("state")) then return end if self.header then SecureHandlerSetFrameRef(self.header, "updateButton", self) SecureHandlerExecute(self.header, [[ local frame = self:GetAttribute("frameref-updateButton") control:RunFor(frame, frame:GetAttribute("UpdateState"), frame:GetAttribute("state")) ]]) else -- TODO end self:UpdateAction() end function Generic:GetAction(state) if not state then state = self:GetAttribute("state") end state = tostring(state) return self.state_types[state] or "empty", self.state_actions[state] end function Generic:UpdateAllStates() for state in pairs(self.state_types) do self:UpdateState(state) end end function Generic:ButtonContentsChanged(state, kind, value) state = tostring(state) self.state_types[state] = kind or "empty" self.state_actions[state] = value lib.callbacks:Fire("OnButtonContentsChanged", self, state, self.state_types[state], self.state_actions[state]) self:UpdateAction(self) end function Generic:DisableDragNDrop(flag) if InCombatLockdown() then error("LibActionButton-1.0: You can only toggle DragNDrop out of combat!", 2) end if flag then self:SetAttribute("LABdisableDragNDrop", true) else self:SetAttribute("LABdisableDragNDrop", nil) end end function Generic:AddToButtonFacade(group) if type(group) ~= "table" or type(group.AddButton) ~= "function" then error("LibActionButton-1.0:AddToButtonFacade: You need to supply a proper group to use!", 2) end group:AddButton(self) self.LBFSkinned = true end function Generic:AddToMasque(group) if type(group) ~= "table" or type(group.AddButton) ~= "function" then error("LibActionButton-1.0:AddToMasque: You need to supply a proper group to use!", 2) end group:AddButton(self) self.MasqueSkinned = true end function Generic:UpdateAlpha() UpdateCooldown(self) end ----------------------------------------------------------- --- frame scripts -- copied (and adjusted) from SecureHandlers.lua local function PickupAny(kind, target, detail, ...) if kind == "clear" then ClearCursor() kind, target, detail = target, detail, ... end if kind == 'action' then PickupAction(target) elseif kind == 'item' then PickupItem(target) elseif kind == 'macro' then PickupMacro(target) elseif kind == 'petaction' then PickupPetAction(target) elseif kind == 'spell' then PickupSpell(target) elseif kind == 'companion' then PickupCompanion(target, detail) elseif kind == 'equipmentset' then PickupEquipmentSet(target) end end function Generic:OnUpdate(elapsed) if not GetCVarBool('lockActionBars') then return; end self.lastupdate = (self.lastupdate or 0) + elapsed; if (self.lastupdate < .2) then return end self.lastupdate = 0 local isDragKeyDown if GetModifiedClick("PICKUPACTION") == 'ALT' then isDragKeyDown = IsAltKeyDown() elseif GetModifiedClick("PICKUPACTION") == 'CTRL' then isDragKeyDown = IsControlKeyDown() elseif GetModifiedClick("PICKUPACTION") == 'SHIFT' then isDragKeyDown = IsShiftKeyDown() end if isDragKeyDown and (self.clickState == 'AnyDown' or self.clickState == nil) then self.clickState = 'AnyUp' self:RegisterForClicks(self.clickState) elseif self.clickState == 'AnyUp' and not isDragKeyDown then self.clickState = 'AnyDown' self:RegisterForClicks(self.clickState) end end function Generic:OnEnter() if self.config.tooltip ~= "disabled" and (self.config.tooltip ~= "nocombat" or not InCombatLockdown()) then UpdateTooltip(self) end if KeyBound then KeyBound:Set(self) end if self._state_type == "action" and self.NewActionTexture then ClearNewActionHighlight(self._state_action, false, false) UpdateNewAction(self) end if self.config.clickOnDown then self:SetScript('OnUpdate', Generic.OnUpdate) end end function Generic:OnLeave() if GameTooltip:IsForbidden() then return end GameTooltip:Hide() self:SetScript('OnUpdate', nil) end -- Insecure drag handler to allow clicking on the button with an action on the cursor -- to place it on the button. Like action buttons work. function Generic:PreClick() if self._state_type == "action" or self._state_type == "pet" or InCombatLockdown() or self:GetAttribute("LABdisableDragNDrop") then return end -- check if there is actually something on the cursor local kind, value, subtype = GetCursorInfo() if not (kind and value) then return end self._old_type = self._state_type if self._state_type and self._state_type ~= "empty" then self._old_type = self._state_type self:SetAttribute("type", "empty") --self:SetState(nil, "empty", nil) end self._receiving_drag = true end local function formatHelper(input) if type(input) == "string" then return format("%q", input) else return tostring(input) end end function Generic:PostClick() UpdateButtonState(self) if self._receiving_drag and not InCombatLockdown() then if self._old_type then self:SetAttribute("type", self._old_type) self._old_type = nil end local oldType, oldAction = self._state_type, self._state_action local kind, data, subtype, extra = GetCursorInfo() SecureHandlerSetFrameRef(self.header, "updateButton", self) SecureHandlerExecute(self.header, format([[ local frame = self:GetAttribute("frameref-updateButton") control:RunFor(frame, frame:GetAttribute("OnReceiveDrag"), %s, %s, %s, %s) control:RunFor(frame, frame:GetAttribute("UpdateState"), %s) ]], formatHelper(kind), formatHelper(data), formatHelper(subtype), formatHelper(extra), formatHelper(self:GetAttribute("state")))) PickupAny("clear", oldType, oldAction) end self._receiving_drag = nil if self._state_type == "action" and lib.ACTION_HIGHLIGHT_MARKS[self._state_action] then ClearNewActionHighlight(self._state_action, false, false) end end ----------------------------------------------------------- --- configuration local function merge(target, source, default) for k,v in pairs(default) do if type(v) ~= "table" then if source and source[k] ~= nil then target[k] = source[k] else target[k] = v end else if type(target[k]) ~= "table" then target[k] = {} else wipe(target[k]) end merge(target[k], type(source) == "table" and source[k], v) end end return target end function Generic:UpdateConfig(config) if config and type(config) ~= "table" then error("LibActionButton-1.0: UpdateConfig requires a valid configuration!", 2) end self.config = {} -- merge the two configs merge(self.config, config, DefaultConfig) if self.config.hideElements.macro then self.Name:Hide() else self.Name:Show() end self:SetAttribute("flyoutDirection", self.config.flyoutDirection) UpdateHotkeys(self) UpdateGrid(self) Update(self, true) self:RegisterForClicks(self.config.clickOnDown and "AnyDown" or "AnyUp") end ----------------------------------------------------------- --- event handler function ForAllButtons(method, onlyWithAction) assert(type(method) == "function") for button in next, (onlyWithAction and ActiveButtons or ButtonRegistry) do method(button) end end function InitializeEventHandler() lib.eventFrame:SetScript("OnEvent", OnEvent) lib.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD") lib.eventFrame:RegisterEvent("ACTIONBAR_SHOWGRID") lib.eventFrame:RegisterEvent("ACTIONBAR_HIDEGRID") lib.eventFrame:RegisterEvent("PET_BAR_SHOWGRID") lib.eventFrame:RegisterEvent("PET_BAR_HIDEGRID") --lib.eventFrame:RegisterEvent("ACTIONBAR_PAGE_CHANGED") --lib.eventFrame:RegisterEvent("UPDATE_BONUS_ACTIONBAR") lib.eventFrame:RegisterEvent("ACTIONBAR_SLOT_CHANGED") lib.eventFrame:RegisterEvent("UPDATE_BINDINGS") lib.eventFrame:RegisterEvent("UPDATE_SHAPESHIFT_FORM") lib.eventFrame:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED") if not WoWClassic then lib.eventFrame:RegisterEvent("UPDATE_VEHICLE_ACTIONBAR") end lib.eventFrame:RegisterEvent("ACTIONBAR_UPDATE_STATE") lib.eventFrame:RegisterEvent("ACTIONBAR_UPDATE_USABLE") lib.eventFrame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN") lib.eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED") lib.eventFrame:RegisterEvent("TRADE_SKILL_SHOW") lib.eventFrame:RegisterEvent("TRADE_SKILL_CLOSE") lib.eventFrame:RegisterEvent("PLAYER_ENTER_COMBAT") lib.eventFrame:RegisterEvent("PLAYER_LEAVE_COMBAT") lib.eventFrame:RegisterEvent("START_AUTOREPEAT_SPELL") lib.eventFrame:RegisterEvent("STOP_AUTOREPEAT_SPELL") lib.eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED") lib.eventFrame:RegisterEvent("LEARNED_SPELL_IN_TAB") lib.eventFrame:RegisterEvent("PET_STABLE_UPDATE") lib.eventFrame:RegisterEvent("PET_STABLE_SHOW") lib.eventFrame:RegisterEvent("SPELL_UPDATE_CHARGES") lib.eventFrame:RegisterEvent("SPELL_UPDATE_ICON") if not WoWClassic then lib.eventFrame:RegisterEvent("ARCHAEOLOGY_CLOSED") lib.eventFrame:RegisterEvent("UNIT_ENTERED_VEHICLE") lib.eventFrame:RegisterEvent("UNIT_EXITED_VEHICLE") lib.eventFrame:RegisterEvent("COMPANION_UPDATE") lib.eventFrame:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_SHOW") lib.eventFrame:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_HIDE") lib.eventFrame:RegisterEvent("UPDATE_SUMMONPETS_ACTION") end -- With those two, do we still need the ACTIONBAR equivalents of them? lib.eventFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN") lib.eventFrame:RegisterEvent("SPELL_UPDATE_USABLE") lib.eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") lib.eventFrame:RegisterEvent("LOSS_OF_CONTROL_ADDED") lib.eventFrame:RegisterEvent("LOSS_OF_CONTROL_UPDATE") lib.eventFrame:Show() lib.eventFrame:SetScript("OnUpdate", OnUpdate) end function OnEvent(frame, event, arg1, ...) if (event == "UNIT_INVENTORY_CHANGED" and arg1 == "player") or event == "LEARNED_SPELL_IN_TAB" then local tooltipOwner = GameTooltip_GetOwnerForbidden() if tooltipOwner and ButtonRegistry[tooltipOwner] then tooltipOwner:SetTooltip() end elseif event == "ACTIONBAR_SLOT_CHANGED" then for button in next, ButtonRegistry do if button._state_type == "action" and (arg1 == 0 or arg1 == tonumber(button._state_action)) then ClearNewActionHighlight(button._state_action, true, false) Update(button) end end elseif event == "PLAYER_ENTERING_WORLD" or event == "UPDATE_SHAPESHIFT_FORM" or event == "UPDATE_VEHICLE_ACTIONBAR" then ForAllButtons(Update) elseif event == "ACTIONBAR_PAGE_CHANGED" or event == "UPDATE_BONUS_ACTIONBAR" then -- TODO: Are these even needed? elseif event == "ACTIONBAR_SHOWGRID" or event == "PET_BAR_SHOWGRID" then ShowGrid(event) elseif event == "ACTIONBAR_HIDEGRID" or event == "PET_BAR_HIDEGRID" then HideGrid(event) elseif event == "UPDATE_BINDINGS" then ForAllButtons(UpdateHotkeys) elseif event == "PLAYER_TARGET_CHANGED" then UpdateRangeTimer() elseif (event == "ACTIONBAR_UPDATE_STATE") or ((event == "UNIT_ENTERED_VEHICLE" or event == "UNIT_EXITED_VEHICLE") and (arg1 == "player")) or ((event == "COMPANION_UPDATE") and (arg1 == "MOUNT")) then ForAllButtons(UpdateButtonState, true) elseif event == "ACTIONBAR_UPDATE_USABLE" then for button in next, ActionButtons do UpdateUsable(button) end elseif event == "SPELL_UPDATE_USABLE" then for button in next, NonActionButtons do UpdateUsable(button) end elseif event == "PLAYER_MOUNT_DISPLAY_CHANGED" then for button in next, ActiveButtons do UpdateUsable(button) end elseif event == "ACTIONBAR_UPDATE_COOLDOWN" then for button in next, ActionButtons do UpdateCooldown(button) if GameTooltip_GetOwnerForbidden() == button then UpdateTooltip(button) end end elseif event == "SPELL_UPDATE_COOLDOWN" then for button in next, NonActionButtons do UpdateCooldown(button) if GameTooltip_GetOwnerForbidden() == button then UpdateTooltip(button) end end elseif event == "LOSS_OF_CONTROL_ADDED" then for button in next, ActiveButtons do UpdateCooldown(button) if GameTooltip_GetOwnerForbidden() == button then UpdateTooltip(button) end end elseif event == "LOSS_OF_CONTROL_UPDATE" then for button in next, ActiveButtons do UpdateCooldown(button) end elseif event == "TRADE_SKILL_SHOW" or event == "TRADE_SKILL_CLOSE" or event == "ARCHAEOLOGY_CLOSED" then ForAllButtons(UpdateButtonState, true) elseif event == "PLAYER_ENTER_COMBAT" then for button in next, ActiveButtons do if button:IsAttack() then StartFlash(button) end end elseif event == "PLAYER_LEAVE_COMBAT" then for button in next, ActiveButtons do if button:IsAttack() then StopFlash(button) end end elseif event == "START_AUTOREPEAT_SPELL" then for button in next, ActiveButtons do if button:IsAutoRepeat() then StartFlash(button) end end elseif event == "STOP_AUTOREPEAT_SPELL" then for button in next, ActiveButtons do if button.flashing == 1 and not button:IsAttack() then StopFlash(button) end end elseif event == "PET_STABLE_UPDATE" or event == "PET_STABLE_SHOW" then ForAllButtons(Update) elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" then for button in next, ActiveButtons do local spellId = button:GetSpellId() if spellId and spellId == arg1 then ShowOverlayGlow(button) else if button._state_type == "action" then local actionType, id = GetActionInfo(button._state_action) if actionType == "flyout" and FlyoutHasSpell(id, arg1) then ShowOverlayGlow(button) end end end end elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" then for button in next, ActiveButtons do local spellId = button:GetSpellId() if spellId and spellId == arg1 then HideOverlayGlow(button) else if button._state_type == "action" then local actionType, id = GetActionInfo(button._state_action) if actionType == "flyout" and FlyoutHasSpell(id, arg1) then HideOverlayGlow(button) end end end end elseif event == "PLAYER_EQUIPMENT_CHANGED" then for button in next, ActiveButtons do if button._state_type == "item" then Update(button) end end elseif event == "SPELL_UPDATE_CHARGES" then ForAllButtons(UpdateCount, true) elseif event == "UPDATE_SUMMONPETS_ACTION" then for button in next, ActiveButtons do if button._state_type == "action" then local actionType, id = GetActionInfo(button._state_action) if actionType == "summonpet" then local texture = GetActionTexture(button._state_action) if texture then button.icon:SetTexture(texture) end end end end elseif event == "SPELL_UPDATE_ICON" then ForAllButtons(Update, true) end end local flashTime = 0 local rangeTimer = -1 function OnUpdate(_, elapsed) flashTime = flashTime - elapsed rangeTimer = rangeTimer - elapsed -- Run the loop only when there is something to update if rangeTimer <= 0 or flashTime <= 0 then for button in next, ActiveButtons do -- Flashing if button.flashing == 1 and flashTime <= 0 then if button.Flash:IsShown() then button.Flash:Hide() else button.Flash:Show() end end -- Range if rangeTimer <= 0 then UpdateRange(button) -- Sezz end end -- Update values if flashTime <= 0 then flashTime = flashTime + ATTACK_BUTTON_FLASH_TIME end if rangeTimer <= 0 then rangeTimer = TOOLTIP_UPDATE_TIME end end end local gridCounter = 0 local isPetGrid = false function ShowGrid(event) if event == "PET_BAR_SHOWGRID" then isPetGrid = true elseif isPetGrid then return -- when PET_BAR_SHOWGRID fires then ACTIONBAR_SHOWGRID fires -- ACTIONBAR_HIDEGRID will not get called but PET_BAR_HIDEGRID does -- LIKELY A BLIZZARD ISSUE. end gridCounter = gridCounter + 1 if gridCounter >= 1 then for button in next, ButtonRegistry do if button:IsShown() then button:SetAlpha(1.0) end end end end function HideGrid(event) if event == "PET_BAR_HIDEGRID" then isPetGrid = false elseif isPetGrid then return --see comment above related to `isPetGrid` end if gridCounter > 0 then gridCounter = gridCounter - 1 end if gridCounter == 0 then for button in next, ButtonRegistry do if button:IsShown() and not button:HasAction() and not button.config.showGrid then button:SetAlpha(0.0) end end end end function UpdateGrid(self) if self.config.showGrid then self:SetAlpha(1.0) elseif gridCounter == 0 and self:IsShown() and not self:HasAction() then self:SetAlpha(0.0) end end function UpdateRange(self, force) -- Sezz: moved from OnUpdate local inRange = self:IsInRange() local oldRange = self.outOfRange self.outOfRange = (inRange == false) if force or (oldRange ~= self.outOfRange) then if self.config.outOfRangeColoring == "button" then UpdateUsable(self) elseif self.config.outOfRangeColoring == "hotkey" then local hotkey = self.HotKey if hotkey:GetText() == RANGE_INDICATOR then if inRange == false then hotkey:Show() else hotkey:Hide() end end if inRange == false then hotkey:SetVertexColor(unpack(self.config.colors.range)) else hotkey:SetVertexColor(unpack(self.config.colors.usable)) end end end end ----------------------------------------------------------- --- KeyBound integration function Generic:GetBindingAction() return self.config.keyBoundTarget or "CLICK "..self:GetName()..":LeftButton" end function Generic:GetHotkey() local name = "CLICK "..self:GetName()..":LeftButton" local key = GetBindingKey(self.config.keyBoundTarget or name) if not key and self.config.keyBoundTarget then key = GetBindingKey(name) end if key then return KeyBound and KeyBound:ToShortKey(key) or key end end local function getKeys(binding, keys) keys = keys or "" for i = 1, select("#", GetBindingKey(binding)) do local hotKey = select(i, GetBindingKey(binding)) if keys ~= "" then keys = keys .. ", " end keys = keys .. GetBindingText(hotKey) end return keys end function Generic:GetBindings() local keys if self.config.keyBoundTarget then keys = getKeys(self.config.keyBoundTarget) end keys = getKeys("CLICK "..self:GetName()..":LeftButton", keys) return keys end function Generic:SetKey(key) if self.config.keyBoundTarget then SetBinding(key, self.config.keyBoundTarget) else SetBindingClick(key, self:GetName(), "LeftButton") end lib.callbacks:Fire("OnKeybindingChanged", self, key) end local function clearBindings(binding) while GetBindingKey(binding) do SetBinding(GetBindingKey(binding), nil) end end function Generic:ClearBindings() if self.config.keyBoundTarget then clearBindings(self.config.keyBoundTarget) end clearBindings("CLICK "..self:GetName()..":LeftButton") lib.callbacks:Fire("OnKeybindingChanged", self, nil) end ----------------------------------------------------------- --- button management function Generic:UpdateAction(force) local action_type, action = self:GetAction() if force or (action_type ~= self._state_type) or (action ~= self._state_action) then -- type changed, update the metatable if force or (self._state_type ~= action_type) then local meta = type_meta_map[action_type] or type_meta_map.empty setmetatable(self, meta) self._state_type = action_type end self._state_action = action Update(self) end end function Update(self, fromUpdateConfig) if self:HasAction() then ActiveButtons[self] = true if self._state_type == "action" then ActionButtons[self] = true NonActionButtons[self] = nil else ActionButtons[self] = nil NonActionButtons[self] = true end self:SetAlpha(1.0) UpdateUsable(self) UpdateCooldown(self) UpdateFlash(self) else ActiveButtons[self] = nil ActionButtons[self] = nil NonActionButtons[self] = nil if gridCounter == 0 and not self.config.showGrid then self:SetAlpha(0.0) end self.cooldown:Hide() self:SetChecked(false) if self.chargeCooldown then EndChargeCooldown(self.chargeCooldown) end if self.LevelLinkLockIcon then self.LevelLinkLockIcon:SetShown(false) end end -- Add a green border if button is an equipped item if self:IsEquipped() and not self.config.hideElements.equipped then self.Border:SetVertexColor(0, 1.0, 0, 0.35) self.Border:Show() else self.Border:Hide() end -- Update Action Text if not self:IsConsumableOrStackable() then self.Name:SetText(self:GetActionText()) else self.Name:SetText("") end -- Update icon and hotkey local texture = self:GetTexture() -- Cooldown desaturate can control saturation, we don't want to override it here local allowSaturation = not self.saturationLocked and not self.LevelLinkLockIcon:IsShown() -- Zone ability button handling self.zoneAbilityDisabled = false if allowSaturation then self.icon:SetDesaturated(false) end if self._state_type == "action" then local action_type, id = GetActionInfo(self._state_action) if ((action_type == "spell" or action_type == "companion") and ZoneAbilityFrame and ZoneAbilityFrame.baseName and not HasZoneAbility()) then local name = GetSpellInfo(ZoneAbilityFrame.baseName) local abilityName = GetSpellInfo(id) if name == abilityName then texture = GetLastZoneAbilitySpellTexture() self.zoneAbilityDisabled = true if allowSaturation then self.icon:SetDesaturated(true) end end end end if texture then self.icon:SetTexture(texture) self.icon:Show() self.rangeTimer = - 1 self:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2") if not self.LBFSkinned and not self.MasqueSkinned then self.NormalTexture:SetTexCoord(0, 0, 0, 0) end else self.icon:Hide() self.cooldown:Hide() self.rangeTimer = nil self:SetNormalTexture("Interface\\Buttons\\UI-Quickslot") if not self.LBFSkinned and not self.MasqueSkinned then self.NormalTexture:SetTexCoord(-0.15, 1.15, -0.15, 1.17) end end self:UpdateLocal() UpdateRange(self, fromUpdateConfig) -- Sezz: update range check on state change UpdateCount(self) UpdateFlyout(self) UpdateOverlayGlow(self) UpdateNewAction(self) UpdateButtonState(self) UpdateSpellHighlight(self) if GameTooltip_GetOwnerForbidden() == self then UpdateTooltip(self) end -- this could've been a spec change, need to call OnStateChanged for action buttons, if present if not InCombatLockdown() and self._state_type == "action" then local onStateChanged = self:GetAttribute("OnStateChanged") if onStateChanged then SecureHandlerSetFrameRef(self.header, "updateButton", self) SecureHandlerExecute(self.header, ([[ local frame = self:GetAttribute("frameref-updateButton") control:RunFor(frame, frame:GetAttribute("OnStateChanged"), %s, %s, %s) ]]):format(formatHelper(self:GetAttribute("state")), formatHelper(self._state_type), formatHelper(self._state_action))) end end lib.callbacks:Fire("OnButtonUpdate", self) end function Generic:UpdateLocal() -- dummy function the other button types can override for special updating end function UpdateButtonState(self) if self:IsCurrentlyActive() or self:IsAutoRepeat() then self:SetChecked(true) else self:SetChecked(false) end lib.callbacks:Fire("OnButtonState", self) end function UpdateUsable(self) local isLevelLinkLocked if not WoWClassic and self._state_type == "action" then isLevelLinkLocked = C_LevelLink.IsActionLocked(self._state_action) if not self.icon:IsDesaturated() then self.icon:SetDesaturated(isLevelLinkLocked) end if self.LevelLinkLockIcon then self.LevelLinkLockIcon:SetShown(isLevelLinkLocked) end end if self.config.useColoring then if isLevelLinkLocked then self.icon:SetVertexColor(unpack(self.config.colors.notUsable)) elseif self.config.outOfRangeColoring == "button" and self.outOfRange then self.icon:SetVertexColor(unpack(self.config.colors.range)) else local isUsable, notEnoughMana = self:IsUsable() if isUsable then self.icon:SetVertexColor(unpack(self.config.colors.usable)) elseif notEnoughMana then self.icon:SetVertexColor(unpack(self.config.colors.mana)) else self.icon:SetVertexColor(unpack(self.config.colors.notUsable)) end end else self.icon:SetVertexColor(unpack(self.config.colors.usable)) end lib.callbacks:Fire("OnButtonUsable", self) end function UpdateCount(self) if not self:HasAction() then self.Count:SetText("") return end if self:IsConsumableOrStackable() then local count = self:GetCount() if count > (self.maxDisplayCount or 9999) then self.Count:SetText("*") else self.Count:SetText(count) end else local charges, maxCharges, chargeStart, chargeDuration = self:GetCharges() if charges and maxCharges and maxCharges > 1 then self.Count:SetText(charges) else self.Count:SetText("") end end end function EndChargeCooldown(self) self:Hide() self:SetParent(UIParent) self.parent.chargeCooldown = nil self.parent = nil tinsert(lib.ChargeCooldowns, self) end local function StartChargeCooldown(parent, chargeStart, chargeDuration, chargeModRate) if not parent.chargeCooldown then local cooldown = tremove(lib.ChargeCooldowns) if not cooldown then lib.NumChargeCooldowns = lib.NumChargeCooldowns + 1 cooldown = CreateFrame("Cooldown", "LAB10ChargeCooldown"..lib.NumChargeCooldowns, parent, "CooldownFrameTemplate"); cooldown:SetScript("OnCooldownDone", EndChargeCooldown) cooldown:SetHideCountdownNumbers(true) cooldown:SetDrawBling(false) lib.callbacks:Fire("OnChargeCreated", parent, cooldown) end cooldown:SetParent(parent) cooldown:SetAllPoints(parent) cooldown:SetFrameStrata(parent:GetFrameStrata()) cooldown:SetFrameLevel(parent:GetFrameLevel() + 1) cooldown:Show() parent.chargeCooldown = cooldown cooldown.parent = parent end -- set cooldown CooldownFrame_Set(parent.chargeCooldown, chargeStart, chargeDuration, true, true, chargeModRate) -- update charge cooldown skin when masque is used if Masque and Masque.UpdateCharge then Masque:UpdateCharge(parent) end if not chargeStart or chargeStart == 0 then EndChargeCooldown(parent.chargeCooldown) end end local function OnCooldownDone(self) local button = self:GetParent() if (self.currentCooldownType == COOLDOWN_TYPE_NORMAL) and button.locStart and (button.locStart > 0) then UpdateCooldown(button) elseif button.chargeCooldown then button.chargeCooldown:SetDrawSwipe(button.config.useDrawSwipeOnCharges) end lib.callbacks:Fire("OnCooldownDone", button, self) end function UpdateCooldown(self) local locStart, locDuration = self:GetLossOfControlCooldown() local start, duration, enable, modRate = self:GetCooldown() local charges, maxCharges, chargeStart, chargeDuration, chargeModRate = self:GetCharges() self.cooldown:SetDrawBling(self.config.useDrawBling and (self.cooldown:GetEffectiveAlpha() > 0.5)) self.cooldown:SetScript("OnCooldownDone", OnCooldownDone) self.cooldown.locStart = locStart self.cooldown.locDuration = locDuration if (locStart + locDuration) > (start + duration) then if self.cooldown.currentCooldownType ~= COOLDOWN_TYPE_LOSS_OF_CONTROL then self.cooldown:SetEdgeTexture("Interface\\Cooldown\\edge-LoC") self.cooldown:SetHideCountdownNumbers(true) self.cooldown.currentCooldownType = COOLDOWN_TYPE_LOSS_OF_CONTROL end CooldownFrame_Set(self.cooldown, locStart, locDuration, true, true, modRate) else if self.cooldown.currentCooldownType ~= COOLDOWN_TYPE_NORMAL then self.cooldown:SetEdgeTexture("Interface\\Cooldown\\edge") self.cooldown:SetHideCountdownNumbers(self.config.disableCountDownNumbers) self.cooldown.currentCooldownType = COOLDOWN_TYPE_NORMAL end if charges and maxCharges and maxCharges > 1 and charges < maxCharges then StartChargeCooldown(self, chargeStart, chargeDuration, chargeModRate) self.chargeCooldown:SetDrawSwipe(duration <= 0 and self.config.useDrawSwipeOnCharges) elseif self.chargeCooldown then EndChargeCooldown(self.chargeCooldown) end CooldownFrame_Set(self.cooldown, start, duration, enable, false, modRate) end lib.callbacks:Fire("OnCooldownUpdate", self, start, duration, enable, modRate) end function StartFlash(self) self.flashing = 1 flashTime = 0 UpdateButtonState(self) end function StopFlash(self) self.flashing = 0 self.Flash:Hide() UpdateButtonState(self) end function UpdateFlash(self) if (self:IsAttack() and self:IsCurrentlyActive()) or self:IsAutoRepeat() then StartFlash(self) else StopFlash(self) end end function UpdateTooltip(self) if GameTooltip:IsForbidden() then return end if (GetCVar("UberTooltips") == "1") then GameTooltip:ClearAllPoints(); GameTooltip_SetDefaultAnchor(GameTooltip, self); else GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); end if self:SetTooltip() then self.UpdateTooltip = UpdateTooltip else self.UpdateTooltip = nil end end function UpdateHotkeys(self) local key = self:GetHotkey() if not key or key == "" or self.config.hideElements.hotkey then self.HotKey:SetText(RANGE_INDICATOR) self.HotKey:Hide() else self.HotKey:SetText(key) self.HotKey:Show() end if self.postKeybind then self.postKeybind(nil, self) end end function ShowOverlayGlow(self) if LBG and self.config.handleOverlay then LBG.ShowOverlayGlow(self) end end function HideOverlayGlow(self) if LBG then LBG.HideOverlayGlow(self) end end function UpdateOverlayGlow(self) local spellId = self.config.handleOverlay and self:GetSpellId() if spellId and IsSpellOverlayed(spellId) then ShowOverlayGlow(self) else HideOverlayGlow(self) end end function ClearNewActionHighlight(action, preventIdenticalActionsFromClearing, value) lib.ACTION_HIGHLIGHT_MARKS[action] = value for button in next, ButtonRegistry do if button._state_type == "action" and action == tonumber(button._state_action) then UpdateNewAction(button) end end if preventIdenticalActionsFromClearing then return end -- iterate through actions and unmark all that are the same type local unmarkedType, unmarkedID = GetActionInfo(action) for actionKey, markValue in pairs(lib.ACTION_HIGHLIGHT_MARKS) do if markValue then local actionType, actionID = GetActionInfo(actionKey) if actionType == unmarkedType and actionID == unmarkedID then ClearNewActionHighlight(actionKey, true, value) end end end end hooksecurefunc("MarkNewActionHighlight", function(action) lib.ACTION_HIGHLIGHT_MARKS[action] = true for button in next, ButtonRegistry do if button._state_type == "action" and action == tonumber(button._state_action) then UpdateNewAction(button) end end end) hooksecurefunc("ClearNewActionHighlight", function(action, preventIdenticalActionsFromClearing) ClearNewActionHighlight(action, preventIdenticalActionsFromClearing, nil) end) function UpdateNewAction(self) -- special handling for "New Action" markers if self.NewActionTexture then if self._state_type == "action" and lib.ACTION_HIGHLIGHT_MARKS[self._state_action] then self.NewActionTexture:Show() else self.NewActionTexture:Hide() end end end hooksecurefunc("UpdateOnBarHighlightMarksBySpell", function(spellID) lib.ON_BAR_HIGHLIGHT_MARK_TYPE = "spell" lib.ON_BAR_HIGHLIGHT_MARK_ID = tonumber(spellID) for button in next, ButtonRegistry do UpdateSpellHighlight(button) end end) hooksecurefunc("UpdateOnBarHighlightMarksByFlyout", function(flyoutID) lib.ON_BAR_HIGHLIGHT_MARK_TYPE = "flyout" lib.ON_BAR_HIGHLIGHT_MARK_ID = tonumber(flyoutID) for button in next, ButtonRegistry do UpdateSpellHighlight(button) end end) hooksecurefunc("ClearOnBarHighlightMarks", function() lib.ON_BAR_HIGHLIGHT_MARK_TYPE = nil for button in next, ButtonRegistry do UpdateSpellHighlight(button) end end) function UpdateSpellHighlight(self) local shown = false local highlightType, id = lib.ON_BAR_HIGHLIGHT_MARK_TYPE, lib.ON_BAR_HIGHLIGHT_MARK_ID if highlightType == "spell" and self:GetSpellId() == id then shown = true elseif highlightType == "flyout" and self._state_type == "action" then local actionType, actionId = GetActionInfo(self._state_action) if actionType == "flyout" and actionId == id then shown = true end end if shown then self.SpellHighlightTexture:Show() self.SpellHighlightAnim:Play() else self.SpellHighlightTexture:Hide() self.SpellHighlightAnim:Stop() end end -- Hook UpdateFlyout so we can use the blizzy templates hooksecurefunc("ActionButton_UpdateFlyout", function(self, ...) if ButtonRegistry[self] then UpdateFlyout(self) end end) function UpdateFlyout(self) -- disabled FlyoutBorder/BorderShadow, those are not handled by LBF and look terrible self.FlyoutBorder:Hide() self.FlyoutBorderShadow:Hide() if self._state_type == "action" then -- based on ActionButton_UpdateFlyout in ActionButton.lua local actionType = GetActionInfo(self._state_action) if actionType == "flyout" then -- Update border and determine arrow position local arrowDistance if (SpellFlyout and SpellFlyout:IsShown() and SpellFlyout:GetParent() == self) or GetMouseFocus() == self then arrowDistance = 5 else arrowDistance = 2 end -- Update arrow self.FlyoutArrow:Show() self.FlyoutArrow:ClearAllPoints() local direction = self:GetAttribute("flyoutDirection"); if direction == "LEFT" then self.FlyoutArrow:SetPoint("LEFT", self, "LEFT", -arrowDistance, 0) SetClampedTextureRotation(self.FlyoutArrow, 270) elseif direction == "RIGHT" then self.FlyoutArrow:SetPoint("RIGHT", self, "RIGHT", arrowDistance, 0) SetClampedTextureRotation(self.FlyoutArrow, 90) elseif direction == "DOWN" then self.FlyoutArrow:SetPoint("BOTTOM", self, "BOTTOM", 0, -arrowDistance) SetClampedTextureRotation(self.FlyoutArrow, 180) else self.FlyoutArrow:SetPoint("TOP", self, "TOP", 0, arrowDistance) SetClampedTextureRotation(self.FlyoutArrow, 0) end if self.FlyoutUpdateFunc then self.FlyoutUpdateFunc(nil, self) end -- return here, otherwise flyout is hidden return end end self.FlyoutArrow:Hide() end function UpdateRangeTimer() rangeTimer = -1 end ----------------------------------------------------------- --- WoW API mapping --- Generic Button Generic.HasAction = function(self) return nil end Generic.GetActionText = function(self) return "" end Generic.GetTexture = function(self) return nil end Generic.GetCharges = function(self) return nil end Generic.GetCount = function(self) return 0 end Generic.GetCooldown = function(self) return 0, 0, 0 end Generic.IsAttack = function(self) return nil end Generic.IsEquipped = function(self) return nil end Generic.IsCurrentlyActive = function(self) return nil end Generic.IsAutoRepeat = function(self) return nil end Generic.IsUsable = function(self) return nil end Generic.IsConsumableOrStackable = function(self) return nil end Generic.IsUnitInRange = function(self, unit) return nil end Generic.IsInRange = function(self) local unit = self:GetAttribute("unit") if unit == "player" then unit = nil end local val = self:IsUnitInRange(unit) -- map 1/0 to true false, since the return values are inconsistent between actions and spells if val == 1 then val = true elseif val == 0 then val = false end return val end Generic.SetTooltip = function(self) return nil end Generic.GetSpellId = function(self) return nil end Generic.GetLossOfControlCooldown = function(self) return 0, 0 end ----------------------------------------------------------- --- Action Button Action.HasAction = function(self) return HasAction(self._state_action) end Action.GetActionText = function(self) return GetActionText(self._state_action) end Action.GetTexture = function(self) return GetActionTexture(self._state_action) end Action.GetCharges = function(self) return GetActionCharges(self._state_action) end Action.GetCount = function(self) return GetActionCount(self._state_action) end Action.GetCooldown = function(self) return GetActionCooldown(self._state_action) end Action.IsAttack = function(self) return IsAttackAction(self._state_action) end Action.IsEquipped = function(self) return IsEquippedAction(self._state_action) end Action.IsCurrentlyActive = function(self) return IsCurrentAction(self._state_action) end Action.IsAutoRepeat = function(self) return IsAutoRepeatAction(self._state_action) end Action.IsUsable = function(self) return IsUsableAction(self._state_action) end Action.IsConsumableOrStackable = function(self) return IsConsumableAction(self._state_action) or IsStackableAction(self._state_action) or (not IsItemAction(self._state_action) and GetActionCount(self._state_action) > 0) end Action.IsUnitInRange = function(self, unit) return IsActionInRange(self._state_action, unit) end Action.SetTooltip = function(self) return GameTooltip:SetAction(self._state_action) end Action.GetSpellId = function(self) local actionType, id, subType = GetActionInfo(self._state_action) if actionType == "spell" then return id elseif actionType == "macro" then return (GetMacroSpell(id)) end end Action.GetLossOfControlCooldown = function(self) return GetActionLossOfControlCooldown(self._state_action) end -- Classic overrides for item count breakage if WoWClassic then -- if the library is present, simply use it to override action counts local LibClassicSpellActionCount = LibStub("LibClassicSpellActionCount-1.0", true) if LibClassicSpellActionCount then Action.GetCount = function(self) return LibClassicSpellActionCount:GetActionCount(self._state_action) end else -- if we don't have the library, only show count for items, like the default UI Action.IsConsumableOrStackable = function(self) return IsItemAction(self._state_action) and (IsConsumableAction(self._state_action) or IsStackableAction(self._state_action)) end end -- disable loss of control cooldown on classic Action.GetLossOfControlCooldown = function(self) return 0,0 end end ----------------------------------------------------------- --- Spell Button Spell.HasAction = function(self) return true end Spell.GetActionText = function(self) return "" end Spell.GetTexture = function(self) return GetSpellTexture(self._state_action) end Spell.GetCharges = function(self) return GetSpellCharges(self._state_action) end Spell.GetCount = function(self) return GetSpellCount(self._state_action) end Spell.GetCooldown = function(self) return GetSpellCooldown(self._state_action) end Spell.IsAttack = function(self) return IsAttackSpell(FindSpellBookSlotBySpellID(self._state_action), "spell") end -- needs spell book id as of 4.0.1.13066 Spell.IsEquipped = function(self) return nil end Spell.IsCurrentlyActive = function(self) return IsCurrentSpell(self._state_action) end Spell.IsAutoRepeat = function(self) return IsAutoRepeatSpell(FindSpellBookSlotBySpellID(self._state_action), "spell") end -- needs spell book id as of 4.0.1.13066 Spell.IsUsable = function(self) return IsUsableSpell(self._state_action) end Spell.IsConsumableOrStackable = function(self) return IsConsumableSpell(self._state_action) end Spell.IsUnitInRange = function(self, unit) return IsSpellInRange(FindSpellBookSlotBySpellID(self._state_action), "spell", unit) end -- needs spell book id as of 4.0.1.13066 Spell.SetTooltip = function(self) return GameTooltip:SetSpellByID(self._state_action) end Spell.GetSpellId = function(self) return self._state_action end Spell.GetLossOfControlCooldown = function(self) return GetSpellLossOfControlCooldown(self._state_action) end ----------------------------------------------------------- --- Item Button local function getItemId(input) return input:match("^item:(%d+)") end Item.HasAction = function(self) return true end Item.GetActionText = function(self) return "" end Item.GetTexture = function(self) return GetItemIcon(self._state_action) end Item.GetCharges = function(self) return nil end Item.GetCount = function(self) return GetItemCount(self._state_action, nil, true) end Item.GetCooldown = function(self) return GetItemCooldown(getItemId(self._state_action)) end Item.IsAttack = function(self) return nil end Item.IsEquipped = function(self) return IsEquippedItem(self._state_action) end Item.IsCurrentlyActive = function(self) return IsCurrentItem(self._state_action) end Item.IsAutoRepeat = function(self) return nil end Item.IsUsable = function(self) return IsUsableItem(self._state_action) end Item.IsConsumableOrStackable = function(self) return IsConsumableItem(self._state_action) end Item.IsUnitInRange = function(self, unit) return IsItemInRange(self._state_action, unit) end Item.SetTooltip = function(self) return GameTooltip:SetHyperlink(self._state_action) end Item.GetSpellId = function(self) return nil end ----------------------------------------------------------- --- Macro Button -- TODO: map results of GetMacroSpell/GetMacroItem to proper results Macro.HasAction = function(self) return true end Macro.GetActionText = function(self) return (GetMacroInfo(self._state_action)) end Macro.GetTexture = function(self) return (select(2, GetMacroInfo(self._state_action))) end Macro.GetCharges = function(self) return nil end Macro.GetCount = function(self) return 0 end Macro.GetCooldown = function(self) return 0, 0, 0 end Macro.IsAttack = function(self) return nil end Macro.IsEquipped = function(self) return nil end Macro.IsCurrentlyActive = function(self) return nil end Macro.IsAutoRepeat = function(self) return nil end Macro.IsUsable = function(self) return nil end Macro.IsConsumableOrStackable = function(self) return nil end Macro.IsUnitInRange = function(self, unit) return nil end Macro.SetTooltip = function(self) return nil end Macro.GetSpellId = function(self) return nil end ----------------------------------------------------------- --- Toy Button Toy.HasAction = function(self) return true end Toy.GetActionText = function(self) return "" end Toy.GetTexture = function(self) return select(3, C_ToyBox.GetToyInfo(self._state_action)) end Toy.GetCharges = function(self) return nil end Toy.GetCount = function(self) return 0 end Toy.GetCooldown = function(self) return GetItemCooldown(self._state_action) end Toy.IsAttack = function(self) return nil end Toy.IsEquipped = function(self) return nil end Toy.IsCurrentlyActive = function(self) return nil end Toy.IsAutoRepeat = function(self) return nil end Toy.IsUsable = function(self) return nil end Toy.IsConsumableOrStackable = function(self) return nil end Toy.IsUnitInRange = function(self, unit) return nil end Toy.SetTooltip = function(self) return GameTooltip:SetToyByItemID(self._state_action) end Toy.GetSpellId = function(self) return nil end ----------------------------------------------------------- --- Custom Button Custom.HasAction = function(self) return true end Custom.GetActionText = function(self) return "" end Custom.GetTexture = function(self) return self._state_action.texture end Custom.GetCharges = function(self) return nil end Custom.GetCount = function(self) return 0 end Custom.GetCooldown = function(self) return 0, 0, 0 end Custom.IsAttack = function(self) return nil end Custom.IsEquipped = function(self) return nil end Custom.IsCurrentlyActive = function(self) return nil end Custom.IsAutoRepeat = function(self) return nil end Custom.IsUsable = function(self) return true end Custom.IsConsumableOrStackable = function(self) return nil end Custom.IsUnitInRange = function(self, unit) return nil end Custom.SetTooltip = function(self) return GameTooltip:SetText(self._state_action.tooltip) end Custom.GetSpellId = function(self) return nil end Custom.RunCustom = function(self, unit, button) return self._state_action.func(self, unit, button) end --- WoW Classic overrides if WoWClassic then UpdateOverlayGlow = function() end end ----------------------------------------------------------- --- Update old Buttons if oldversion and next(lib.buttonRegistry) then InitializeEventHandler() for button in next, lib.buttonRegistry do -- this refreshes the metatable on the button Generic.UpdateAction(button, true) SetupSecureSnippets(button) if oldversion < 12 then WrapOnClick(button) end if oldversion < 23 then if button.overlay then button.overlay:Hide() ActionButton_HideOverlayGlow(button) button.overlay = nil UpdateOverlayGlow(button) end end end end