· 3 years ago · May 15, 2022, 12:30 AM
1--[[
2Copyright (c) 2010-2021, Hendrik "nevcairiel" Leppkes <h.leppkes@gmail.com>
3
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are met:
8
9 * Redistributions of source code must retain the above copyright notice,
10 this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright notice,
12 this list of conditions and the following disclaimer in the documentation
13 and/or other materials provided with the distribution.
14 * Neither the name of the developer nor the names of its contributors
15 may be used to endorse or promote products derived from this software without
16 specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30]]
31local MAJOR_VERSION = "LibActionButton-1.0"
32local MINOR_VERSION = 82
33
34if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end
35local lib, oldversion = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
36if not lib then return end
37
38-- Lua functions
39local type, error, tostring, tonumber, assert, select = type, error, tostring, tonumber, assert, select
40local setmetatable, wipe, unpack, pairs, next = setmetatable, wipe, unpack, pairs, next
41local str_match, format, tinsert, tremove = string.match, format, tinsert, tremove
42
43local WoWClassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC)
44local WoWBCC = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC)
45
46local KeyBound = LibStub("LibKeyBound-1.0", true)
47local CBH = LibStub("CallbackHandler-1.0")
48local LBG = LibStub("LibButtonGlow-1.0", true)
49local Masque = LibStub("Masque", true)
50
51lib.eventFrame = lib.eventFrame or CreateFrame("Frame")
52lib.eventFrame:UnregisterAllEvents()
53
54lib.buttonRegistry = lib.buttonRegistry or {}
55lib.activeButtons = lib.activeButtons or {}
56lib.actionButtons = lib.actionButtons or {}
57lib.nonActionButtons = lib.nonActionButtons or {}
58
59lib.ChargeCooldowns = lib.ChargeCooldowns or {}
60lib.NumChargeCooldowns = lib.NumChargeCooldowns or 0
61
62lib.ACTION_HIGHLIGHT_MARKS = lib.ACTION_HIGHLIGHT_MARKS or setmetatable({}, { __index = ACTION_HIGHLIGHT_MARKS })
63
64lib.callbacks = lib.callbacks or CBH:New(lib)
65
66local Generic = CreateFrame("CheckButton")
67local Generic_MT = {__index = Generic}
68
69local Action = setmetatable({}, {__index = Generic})
70local Action_MT = {__index = Action}
71
72--local PetAction = setmetatable({}, {__index = Generic})
73--local PetAction_MT = {__index = PetAction}
74
75local Spell = setmetatable({}, {__index = Generic})
76local Spell_MT = {__index = Spell}
77
78local Item = setmetatable({}, {__index = Generic})
79local Item_MT = {__index = Item}
80
81local Macro = setmetatable({}, {__index = Generic})
82local Macro_MT = {__index = Macro}
83
84local Custom = setmetatable({}, {__index = Generic})
85local Custom_MT = {__index = Custom}
86
87local type_meta_map = {
88 empty = Generic_MT,
89 action = Action_MT,
90 --pet = PetAction_MT,
91 spell = Spell_MT,
92 item = Item_MT,
93 macro = Macro_MT,
94 custom = Custom_MT
95}
96
97local ButtonRegistry, ActiveButtons, ActionButtons, NonActionButtons = lib.buttonRegistry, lib.activeButtons, lib.actionButtons, lib.nonActionButtons
98
99local Update, UpdateButtonState, UpdateUsable, UpdateCount, UpdateCooldown, UpdateTooltip, UpdateNewAction, UpdateSpellHighlight, ClearNewActionHighlight
100local StartFlash, StopFlash, UpdateFlash, UpdateHotkeys, UpdateRangeTimer, UpdateOverlayGlow
101local UpdateFlyout, ShowGrid, HideGrid, UpdateGrid, SetupSecureSnippets, WrapOnClick
102local ShowOverlayGlow, HideOverlayGlow
103local EndChargeCooldown
104
105local InitializeEventHandler, OnEvent, ForAllButtons, OnUpdate
106
107local function GameTooltip_GetOwnerForbidden()
108 if GameTooltip:IsForbidden() then
109 return nil
110 end
111 return GameTooltip:GetOwner()
112end
113
114local DefaultConfig = {
115 outOfRangeColoring = "button",
116 tooltip = "enabled",
117 showGrid = false,
118 colors = {
119 range = { 0.8, 0.1, 0.1 },
120 mana = { 0.5, 0.5, 1.0 }
121 },
122 hideElements = {
123 macro = false,
124 hotkey = false,
125 equipped = false,
126 },
127 keyBoundTarget = false,
128 clickOnDown = false,
129 flyoutDirection = "UP",
130}
131
132--- Create a new action button.
133-- @param id Internal id of the button (not used by LibActionButton-1.0, only for tracking inside the calling addon)
134-- @param name Name of the button frame to be created (not used by LibActionButton-1.0 aside from naming the frame)
135-- @param header Header that drives these action buttons (if any)
136function lib:CreateButton(id, name, header, config)
137 if type(name) ~= "string" then
138 error("Usage: CreateButton(id, name. header): Buttons must have a valid name!", 2)
139 end
140 if not header then
141 error("Usage: CreateButton(id, name, header): Buttons without a secure header are not yet supported!", 2)
142 end
143
144 if not KeyBound then
145 KeyBound = LibStub("LibKeyBound-1.0", true)
146 end
147
148 local button = setmetatable(CreateFrame("CheckButton", name, header, "SecureActionButtonTemplate, ActionButtonTemplate"), Generic_MT)
149 button:RegisterForDrag("LeftButton", "RightButton")
150 button:RegisterForClicks("AnyUp")
151
152 -- Frame Scripts
153 button:SetScript("OnEnter", Generic.OnEnter)
154 button:SetScript("OnLeave", Generic.OnLeave)
155 button:SetScript("PreClick", Generic.PreClick)
156 button:SetScript("PostClick", Generic.PostClick)
157
158 button.id = id
159 button.header = header
160 -- Mapping of state -> action
161 button.state_types = {}
162 button.state_actions = {}
163
164 -- Store the LAB Version that created this button for debugging
165 button.__LAB_Version = MINOR_VERSION
166
167 -- just in case we're not run by a header, default to state 0
168 button:SetAttribute("state", 0)
169
170 SetupSecureSnippets(button)
171 WrapOnClick(button)
172
173 -- adjust hotkey style for better readability
174 button.HotKey:SetFont(button.HotKey:GetFont(), 13, "OUTLINE")
175 button.HotKey:SetVertexColor(0.75, 0.75, 0.75)
176 button.HotKey:SetPoint("TOPLEFT", button, "TOPLEFT", -2, -4)
177
178 -- adjust count/stack size
179 button.Count:SetFont(button.Count:GetFont(), 16, "OUTLINE")
180
181 -- Store the button in the registry, needed for event and OnUpdate handling
182 if not next(ButtonRegistry) then
183 InitializeEventHandler()
184 end
185 ButtonRegistry[button] = true
186
187 button:UpdateConfig(config)
188
189 -- run an initial update
190 button:UpdateAction()
191 UpdateHotkeys(button)
192
193 -- somewhat of a hack for the Flyout buttons to not error.
194 button.action = 0
195
196 lib.callbacks:Fire("OnButtonCreated", button)
197
198 return button
199end
200
201function SetupSecureSnippets(button)
202 button:SetAttribute("_custom", Custom.RunCustom)
203 -- secure UpdateState(self, state)
204 -- update the type and action of the button based on the state
205 button:SetAttribute("UpdateState", [[
206 local state = ...
207 self:SetAttribute("state", state)
208 local type, action = (self:GetAttribute(format("labtype-%s", state)) or "empty"), self:GetAttribute(format("labaction-%s", state))
209
210 self:SetAttribute("type", type)
211 if type ~= "empty" and type ~= "custom" then
212 local action_field = (type == "pet") and "action" or type
213 self:SetAttribute(action_field, action)
214 self:SetAttribute("action_field", action_field)
215 end
216 local onStateChanged = self:GetAttribute("OnStateChanged")
217 if onStateChanged then
218 self:Run(onStateChanged, state, type, action)
219 end
220 ]])
221
222 -- this function is invoked by the header when the state changes
223 button:SetAttribute("_childupdate-state", [[
224 self:RunAttribute("UpdateState", message)
225 self:CallMethod("UpdateAction")
226 ]])
227
228 -- secure PickupButton(self, kind, value, ...)
229 -- utility function to place a object on the cursor
230 button:SetAttribute("PickupButton", [[
231 local kind, value = ...
232 if kind == "empty" then
233 return "clear"
234 elseif kind == "action" or kind == "pet" then
235 local actionType = (kind == "pet") and "petaction" or kind
236 return actionType, value
237 elseif kind == "spell" or kind == "item" or kind == "macro" then
238 return "clear", kind, value
239 else
240 print("LibActionButton-1.0: Unknown type: " .. tostring(kind))
241 return false
242 end
243 ]])
244
245 button:SetAttribute("OnDragStart", [[
246 if (self:GetAttribute("buttonlock") and not IsModifiedClick("PICKUPACTION")) or self:GetAttribute("LABdisableDragNDrop") then return false end
247 local state = self:GetAttribute("state")
248 local type = self:GetAttribute("type")
249 -- if the button is empty, we can't drag anything off it
250 if type == "empty" or type == "custom" then
251 return false
252 end
253 -- Get the value for the action attribute
254 local action_field = self:GetAttribute("action_field")
255 local action = self:GetAttribute(action_field)
256
257 -- non-action fields need to change their type to empty
258 if type ~= "action" and type ~= "pet" then
259 self:SetAttribute(format("labtype-%s", state), "empty")
260 self:SetAttribute(format("labaction-%s", state), nil)
261 -- update internal state
262 self:RunAttribute("UpdateState", state)
263 -- send a notification to the insecure code
264 self:CallMethod("ButtonContentsChanged", state, "empty", nil)
265 end
266 -- return the button contents for pickup
267 return self:RunAttribute("PickupButton", type, action)
268 ]])
269
270 button:SetAttribute("OnReceiveDrag", [[
271 if self:GetAttribute("LABdisableDragNDrop") then return false end
272 local kind, value, subtype, extra = ...
273 if not kind or not value then return false end
274 local state = self:GetAttribute("state")
275 local buttonType, buttonAction = self:GetAttribute("type"), nil
276 if buttonType == "custom" then return false end
277 -- action buttons can do their magic themself
278 -- for all other buttons, we'll need to update the content now
279 if buttonType ~= "action" and buttonType ~= "pet" then
280 -- with "spell" types, the 4th value contains the actual spell id
281 if kind == "spell" then
282 if extra then
283 value = extra
284 else
285 print("no spell id?", ...)
286 end
287 elseif kind == "item" and value then
288 value = format("item:%d", value)
289 end
290
291 -- Get the action that was on the button before
292 if buttonType ~= "empty" then
293 buttonAction = self:GetAttribute(self:GetAttribute("action_field"))
294 end
295
296 -- TODO: validate what kind of action is being fed in here
297 -- We can only use a handful of the possible things on the cursor
298 -- return false for all those we can't put on buttons
299
300 self:SetAttribute(format("labtype-%s", state), kind)
301 self:SetAttribute(format("labaction-%s", state), value)
302 -- update internal state
303 self:RunAttribute("UpdateState", state)
304 -- send a notification to the insecure code
305 self:CallMethod("ButtonContentsChanged", state, kind, value)
306 else
307 -- get the action for (pet-)action buttons
308 buttonAction = self:GetAttribute("action")
309 end
310 return self:RunAttribute("PickupButton", buttonType, buttonAction)
311 ]])
312
313 button:SetScript("OnDragStart", nil)
314 -- Wrapped OnDragStart(self, button, kind, value, ...)
315 button.header:WrapScript(button, "OnDragStart", [[
316 return self:RunAttribute("OnDragStart")
317 ]])
318 -- Wrap twice, because the post-script is not run when the pre-script causes a pickup (doh)
319 -- we also need some phony message, or it won't work =/
320 button.header:WrapScript(button, "OnDragStart", [[
321 return "message", "update"
322 ]], [[
323 self:RunAttribute("UpdateState", self:GetAttribute("state"))
324 ]])
325
326 button:SetScript("OnReceiveDrag", nil)
327 -- Wrapped OnReceiveDrag(self, button, kind, value, ...)
328 button.header:WrapScript(button, "OnReceiveDrag", [[
329 return self:RunAttribute("OnReceiveDrag", kind, value, ...)
330 ]])
331 -- Wrap twice, because the post-script is not run when the pre-script causes a pickup (doh)
332 -- we also need some phony message, or it won't work =/
333 button.header:WrapScript(button, "OnReceiveDrag", [[
334 return "message", "update"
335 ]], [[
336 self:RunAttribute("UpdateState", self:GetAttribute("state"))
337 ]])
338end
339
340function WrapOnClick(button)
341 -- Wrap OnClick, to catch changes to actions that are applied with a click on the button.
342 button.header:WrapScript(button, "OnClick", [[
343 if self:GetAttribute("type") == "action" then
344 local type, action = GetActionInfo(self:GetAttribute("action"))
345 return nil, format("%s|%s", tostring(type), tostring(action))
346 end
347 ]], [[
348 local type, action = GetActionInfo(self:GetAttribute("action"))
349 if message ~= format("%s|%s", tostring(type), tostring(action)) then
350 self:RunAttribute("UpdateState", self:GetAttribute("state"))
351 end
352 ]])
353end
354
355-----------------------------------------------------------
356--- utility
357
358function lib:GetAllButtons()
359 local buttons = {}
360 for button in next, ButtonRegistry do
361 buttons[button] = true
362 end
363 return buttons
364end
365
366function Generic:ClearSetPoint(...)
367 self:ClearAllPoints()
368 self:SetPoint(...)
369end
370
371function Generic:NewHeader(header)
372 self.header = header
373 self:SetParent(header)
374 SetupSecureSnippets(self)
375 WrapOnClick(self)
376end
377
378
379-----------------------------------------------------------
380--- state management
381
382function Generic:ClearStates()
383 for state in pairs(self.state_types) do
384 self:SetAttribute(format("labtype-%s", state), nil)
385 self:SetAttribute(format("labaction-%s", state), nil)
386 end
387 wipe(self.state_types)
388 wipe(self.state_actions)
389end
390
391function Generic:SetState(state, kind, action)
392 if not state then state = self:GetAttribute("state") end
393 state = tostring(state)
394 -- we allow a nil kind for setting a empty state
395 if not kind then kind = "empty" end
396 if not type_meta_map[kind] then
397 error("SetStateAction: unknown action type: " .. tostring(kind), 2)
398 end
399 if kind ~= "empty" and action == nil then
400 error("SetStateAction: an action is required for non-empty states", 2)
401 end
402 if kind ~= "custom" and action ~= nil and type(action) ~= "number" and type(action) ~= "string" or (kind == "custom" and type(action) ~= "table") then
403 error("SetStateAction: invalid action data type, only strings and numbers allowed", 2)
404 end
405
406 if kind == "item" then
407 if tonumber(action) then
408 action = format("item:%s", action)
409 else
410 local itemString = str_match(action, "^|c%x+|H(item[%d:]+)|h%[")
411 if itemString then
412 action = itemString
413 end
414 end
415 end
416
417 self.state_types[state] = kind
418 self.state_actions[state] = action
419 self:UpdateState(state)
420end
421
422function Generic:UpdateState(state)
423 if not state then state = self:GetAttribute("state") end
424 state = tostring(state)
425 self:SetAttribute(format("labtype-%s", state), self.state_types[state])
426 self:SetAttribute(format("labaction-%s", state), self.state_actions[state])
427 if state ~= tostring(self:GetAttribute("state")) then return end
428 if self.header then
429 self.header:SetFrameRef("updateButton", self)
430 self.header:Execute([[
431 local frame = self:GetFrameRef("updateButton")
432 control:RunFor(frame, frame:GetAttribute("UpdateState"), frame:GetAttribute("state"))
433 ]])
434 else
435 -- TODO
436 end
437 self:UpdateAction()
438end
439
440function Generic:GetAction(state)
441 if not state then state = self:GetAttribute("state") end
442 state = tostring(state)
443 return self.state_types[state] or "empty", self.state_actions[state]
444end
445
446function Generic:UpdateAllStates()
447 for state in pairs(self.state_types) do
448 self:UpdateState(state)
449 end
450end
451
452function Generic:ButtonContentsChanged(state, kind, value)
453 state = tostring(state)
454 self.state_types[state] = kind or "empty"
455 self.state_actions[state] = value
456 lib.callbacks:Fire("OnButtonContentsChanged", self, state, self.state_types[state], self.state_actions[state])
457 self:UpdateAction(self)
458end
459
460function Generic:DisableDragNDrop(flag)
461 if InCombatLockdown() then
462 error("LibActionButton-1.0: You can only toggle DragNDrop out of combat!", 2)
463 end
464 if flag then
465 self:SetAttribute("LABdisableDragNDrop", true)
466 else
467 self:SetAttribute("LABdisableDragNDrop", nil)
468 end
469end
470
471function Generic:AddToButtonFacade(group)
472 if type(group) ~= "table" or type(group.AddButton) ~= "function" then
473 error("LibActionButton-1.0:AddToButtonFacade: You need to supply a proper group to use!", 2)
474 end
475 group:AddButton(self)
476 self.LBFSkinned = true
477end
478
479function Generic:AddToMasque(group)
480 if type(group) ~= "table" or type(group.AddButton) ~= "function" then
481 error("LibActionButton-1.0:AddToMasque: You need to supply a proper group to use!", 2)
482 end
483 group:AddButton(self, nil, "Action")
484 self.MasqueSkinned = true
485end
486
487function Generic:UpdateAlpha()
488 UpdateCooldown(self)
489end
490
491-----------------------------------------------------------
492--- frame scripts
493
494-- copied (and adjusted) from SecureHandlers.lua
495local function PickupAny(kind, target, detail, ...)
496 if kind == "clear" then
497 ClearCursor()
498 kind, target, detail = target, detail, ...
499 end
500
501 if kind == 'action' then
502 PickupAction(target)
503 elseif kind == 'item' then
504 PickupItem(target)
505 elseif kind == 'macro' then
506 PickupMacro(target)
507 elseif kind == 'petaction' then
508 PickupPetAction(target)
509 elseif kind == 'spell' then
510 PickupSpell(target)
511 elseif kind == 'companion' then
512 PickupCompanion(target, detail)
513 elseif kind == 'equipmentset' then
514 PickupEquipmentSet(target)
515 end
516end
517
518function Generic:OnEnter()
519 if self.config.tooltip ~= "disabled" and (self.config.tooltip ~= "nocombat" or not InCombatLockdown()) then
520 UpdateTooltip(self)
521 end
522 if KeyBound then
523 KeyBound:Set(self)
524 end
525
526 if self._state_type == "action" and self.NewActionTexture then
527 ClearNewActionHighlight(self._state_action, false, false)
528 UpdateNewAction(self)
529 end
530end
531
532function Generic:OnLeave()
533 if GameTooltip:IsForbidden() then return end
534 GameTooltip:Hide()
535end
536
537-- Insecure drag handler to allow clicking on the button with an action on the cursor
538-- to place it on the button. Like action buttons work.
539function Generic:PreClick()
540 if self._state_type == "action" or self._state_type == "pet"
541 or InCombatLockdown() or self:GetAttribute("LABdisableDragNDrop")
542 then
543 return
544 end
545 -- check if there is actually something on the cursor
546 local kind, value, _subtype = GetCursorInfo()
547 if not (kind and value) then return end
548 self._old_type = self._state_type
549 if self._state_type and self._state_type ~= "empty" then
550 self._old_type = self._state_type
551 self:SetAttribute("type", "empty")
552 --self:SetState(nil, "empty", nil)
553 end
554 self._receiving_drag = true
555end
556
557local function formatHelper(input)
558 if type(input) == "string" then
559 return format("%q", input)
560 else
561 return tostring(input)
562 end
563end
564
565function Generic:PostClick()
566 UpdateButtonState(self)
567 if self._receiving_drag and not InCombatLockdown() then
568 if self._old_type then
569 self:SetAttribute("type", self._old_type)
570 self._old_type = nil
571 end
572 local oldType, oldAction = self._state_type, self._state_action
573 local kind, data, subtype, extra = GetCursorInfo()
574 self.header:SetFrameRef("updateButton", self)
575 self.header:Execute(format([[
576 local frame = self:GetFrameRef("updateButton")
577 control:RunFor(frame, frame:GetAttribute("OnReceiveDrag"), %s, %s, %s, %s)
578 control:RunFor(frame, frame:GetAttribute("UpdateState"), %s)
579 ]], formatHelper(kind), formatHelper(data), formatHelper(subtype), formatHelper(extra), formatHelper(self:GetAttribute("state"))))
580 PickupAny("clear", oldType, oldAction)
581 end
582 self._receiving_drag = nil
583
584 if self._state_type == "action" and lib.ACTION_HIGHLIGHT_MARKS[self._state_action] then
585 ClearNewActionHighlight(self._state_action, false, false)
586 end
587end
588
589-----------------------------------------------------------
590--- configuration
591
592local function merge(target, source, default)
593 for k,v in pairs(default) do
594 if type(v) ~= "table" then
595 if source and source[k] ~= nil then
596 target[k] = source[k]
597 else
598 target[k] = v
599 end
600 else
601 if type(target[k]) ~= "table" then target[k] = {} else wipe(target[k]) end
602 merge(target[k], type(source) == "table" and source[k], v)
603 end
604 end
605 return target
606end
607
608function Generic:UpdateConfig(config)
609 if config and type(config) ~= "table" then
610 error("LibActionButton-1.0: UpdateConfig requires a valid configuration!", 2)
611 end
612 local oldconfig = self.config
613 self.config = {}
614 -- merge the two configs
615 merge(self.config, config, DefaultConfig)
616
617 if self.config.outOfRangeColoring == "button" or (oldconfig and oldconfig.outOfRangeColoring == "button") then
618 UpdateUsable(self)
619 end
620 if self.config.outOfRangeColoring == "hotkey" then
621 self.outOfRange = nil
622 elseif oldconfig and oldconfig.outOfRangeColoring == "hotkey" then
623 self.HotKey:SetVertexColor(0.75, 0.75, 0.75)
624 end
625
626 if self.config.hideElements.macro then
627 self.Name:Hide()
628 else
629 self.Name:Show()
630 end
631
632 self:SetAttribute("flyoutDirection", self.config.flyoutDirection)
633
634 UpdateHotkeys(self)
635 UpdateGrid(self)
636 Update(self)
637 self:RegisterForClicks(self.config.clickOnDown and "AnyDown" or "AnyUp")
638end
639
640-----------------------------------------------------------
641--- event handler
642
643function ForAllButtons(method, onlyWithAction)
644 assert(type(method) == "function")
645 for button in next, (onlyWithAction and ActiveButtons or ButtonRegistry) do
646 method(button)
647 end
648end
649
650function InitializeEventHandler()
651 lib.eventFrame:SetScript("OnEvent", OnEvent)
652 lib.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
653 lib.eventFrame:RegisterEvent("ACTIONBAR_SHOWGRID")
654 lib.eventFrame:RegisterEvent("ACTIONBAR_HIDEGRID")
655 --lib.eventFrame:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
656 --lib.eventFrame:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
657 lib.eventFrame:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
658 lib.eventFrame:RegisterEvent("UPDATE_BINDINGS")
659 lib.eventFrame:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
660 lib.eventFrame:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED")
661 if not WoWClassic and not WoWBCC then
662 lib.eventFrame:RegisterEvent("UPDATE_VEHICLE_ACTIONBAR")
663 end
664
665 lib.eventFrame:RegisterEvent("ACTIONBAR_UPDATE_STATE")
666 lib.eventFrame:RegisterEvent("ACTIONBAR_UPDATE_USABLE")
667 lib.eventFrame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
668 lib.eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
669 lib.eventFrame:RegisterEvent("TRADE_SKILL_SHOW")
670 lib.eventFrame:RegisterEvent("TRADE_SKILL_CLOSE")
671
672 lib.eventFrame:RegisterEvent("PLAYER_ENTER_COMBAT")
673 lib.eventFrame:RegisterEvent("PLAYER_LEAVE_COMBAT")
674 lib.eventFrame:RegisterEvent("START_AUTOREPEAT_SPELL")
675 lib.eventFrame:RegisterEvent("STOP_AUTOREPEAT_SPELL")
676 lib.eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
677 lib.eventFrame:RegisterEvent("LEARNED_SPELL_IN_TAB")
678 lib.eventFrame:RegisterEvent("PET_STABLE_UPDATE")
679 lib.eventFrame:RegisterEvent("PET_STABLE_SHOW")
680 lib.eventFrame:RegisterEvent("SPELL_UPDATE_CHARGES")
681 lib.eventFrame:RegisterEvent("SPELL_UPDATE_ICON")
682 if not WoWClassic and not WoWBCC then
683 lib.eventFrame:RegisterEvent("ARCHAEOLOGY_CLOSED")
684 lib.eventFrame:RegisterEvent("UNIT_ENTERED_VEHICLE")
685 lib.eventFrame:RegisterEvent("UNIT_EXITED_VEHICLE")
686 lib.eventFrame:RegisterEvent("COMPANION_UPDATE")
687 lib.eventFrame:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_SHOW")
688 lib.eventFrame:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_HIDE")
689 lib.eventFrame:RegisterEvent("UPDATE_SUMMONPETS_ACTION")
690 end
691
692 -- With those two, do we still need the ACTIONBAR equivalents of them?
693 lib.eventFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
694 lib.eventFrame:RegisterEvent("SPELL_UPDATE_USABLE")
695 lib.eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
696
697 lib.eventFrame:RegisterEvent("LOSS_OF_CONTROL_ADDED")
698 lib.eventFrame:RegisterEvent("LOSS_OF_CONTROL_UPDATE")
699
700 lib.eventFrame:Show()
701 lib.eventFrame:SetScript("OnUpdate", OnUpdate)
702end
703
704function OnEvent(frame, event, arg1, ...)
705 if (event == "UNIT_INVENTORY_CHANGED" and arg1 == "player") or event == "LEARNED_SPELL_IN_TAB" then
706 local tooltipOwner = GameTooltip_GetOwnerForbidden()
707 if tooltipOwner and ButtonRegistry[tooltipOwner] then
708 tooltipOwner:SetTooltip()
709 end
710 elseif event == "ACTIONBAR_SLOT_CHANGED" then
711 for button in next, ButtonRegistry do
712 if button._state_type == "action" and (arg1 == 0 or arg1 == tonumber(button._state_action)) then
713 ClearNewActionHighlight(button._state_action, true, false)
714 Update(button)
715 end
716 end
717 elseif event == "PLAYER_ENTERING_WORLD" or event == "UPDATE_SHAPESHIFT_FORM" or event == "UPDATE_VEHICLE_ACTIONBAR" then
718 ForAllButtons(Update)
719 elseif event == "ACTIONBAR_PAGE_CHANGED" or event == "UPDATE_BONUS_ACTIONBAR" then
720 -- TODO: Are these even needed?
721 elseif event == "ACTIONBAR_SHOWGRID" then
722 ShowGrid()
723 elseif event == "ACTIONBAR_HIDEGRID" then
724 HideGrid()
725 elseif event == "UPDATE_BINDINGS" then
726 ForAllButtons(UpdateHotkeys)
727 elseif event == "PLAYER_TARGET_CHANGED" then
728 UpdateRangeTimer()
729 elseif (event == "ACTIONBAR_UPDATE_STATE") or
730 ((event == "UNIT_ENTERED_VEHICLE" or event == "UNIT_EXITED_VEHICLE") and (arg1 == "player")) or
731 ((event == "COMPANION_UPDATE") and (arg1 == "MOUNT")) then
732 ForAllButtons(UpdateButtonState, true)
733 elseif event == "ACTIONBAR_UPDATE_USABLE" then
734 for button in next, ActionButtons do
735 UpdateUsable(button)
736 end
737 elseif event == "SPELL_UPDATE_USABLE" then
738 for button in next, NonActionButtons do
739 UpdateUsable(button)
740 end
741 elseif event == "PLAYER_MOUNT_DISPLAY_CHANGED" then
742 for button in next, ActiveButtons do
743 UpdateUsable(button)
744 end
745 elseif event == "ACTIONBAR_UPDATE_COOLDOWN" then
746 for button in next, ActionButtons do
747 UpdateCooldown(button)
748 if GameTooltip_GetOwnerForbidden() == button then
749 UpdateTooltip(button)
750 end
751 end
752 elseif event == "SPELL_UPDATE_COOLDOWN" then
753 for button in next, NonActionButtons do
754 UpdateCooldown(button)
755 if GameTooltip_GetOwnerForbidden() == button then
756 UpdateTooltip(button)
757 end
758 end
759 elseif event == "LOSS_OF_CONTROL_ADDED" then
760 for button in next, ActiveButtons do
761 UpdateCooldown(button)
762 if GameTooltip_GetOwnerForbidden() == button then
763 UpdateTooltip(button)
764 end
765 end
766 elseif event == "LOSS_OF_CONTROL_UPDATE" then
767 for button in next, ActiveButtons do
768 UpdateCooldown(button)
769 end
770 elseif event == "TRADE_SKILL_SHOW" or event == "TRADE_SKILL_CLOSE" or event == "ARCHAEOLOGY_CLOSED" then
771 ForAllButtons(UpdateButtonState, true)
772 elseif event == "PLAYER_ENTER_COMBAT" then
773 for button in next, ActiveButtons do
774 if button:IsAttack() then
775 StartFlash(button)
776 end
777 end
778 elseif event == "PLAYER_LEAVE_COMBAT" then
779 for button in next, ActiveButtons do
780 if button:IsAttack() then
781 StopFlash(button)
782 end
783 end
784 elseif event == "START_AUTOREPEAT_SPELL" then
785 for button in next, ActiveButtons do
786 if button:IsAutoRepeat() then
787 StartFlash(button)
788 end
789 end
790 elseif event == "STOP_AUTOREPEAT_SPELL" then
791 for button in next, ActiveButtons do
792 if button.flashing == 1 and not button:IsAttack() then
793 StopFlash(button)
794 end
795 end
796 elseif event == "PET_STABLE_UPDATE" or event == "PET_STABLE_SHOW" then
797 ForAllButtons(Update)
798 elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" then
799 for button in next, ActiveButtons do
800 local spellId = button:GetSpellId()
801 if spellId and spellId == arg1 then
802 ShowOverlayGlow(button)
803 else
804 if button._state_type == "action" then
805 local actionType, id = GetActionInfo(button._state_action)
806 if actionType == "flyout" and FlyoutHasSpell(id, arg1) then
807 ShowOverlayGlow(button)
808 end
809 end
810 end
811 end
812 elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" then
813 for button in next, ActiveButtons do
814 local spellId = button:GetSpellId()
815 if spellId and spellId == arg1 then
816 HideOverlayGlow(button)
817 else
818 if button._state_type == "action" then
819 local actionType, id = GetActionInfo(button._state_action)
820 if actionType == "flyout" and FlyoutHasSpell(id, arg1) then
821 HideOverlayGlow(button)
822 end
823 end
824 end
825 end
826 elseif event == "PLAYER_EQUIPMENT_CHANGED" then
827 for button in next, ActiveButtons do
828 if button._state_type == "item" then
829 Update(button)
830 end
831 end
832 elseif event == "SPELL_UPDATE_CHARGES" then
833 ForAllButtons(UpdateCount, true)
834 elseif event == "UPDATE_SUMMONPETS_ACTION" then
835 for button in next, ActiveButtons do
836 if button._state_type == "action" then
837 local actionType, _id = GetActionInfo(button._state_action)
838 if actionType == "summonpet" then
839 local texture = GetActionTexture(button._state_action)
840 if texture then
841 button.icon:SetTexture(texture)
842 end
843 end
844 end
845 end
846 elseif event == "SPELL_UPDATE_ICON" then
847 ForAllButtons(Update, true)
848 end
849end
850
851local flashTime = 0
852local rangeTimer = -1
853function OnUpdate(_, elapsed)
854 flashTime = flashTime - elapsed
855 rangeTimer = rangeTimer - elapsed
856 -- Run the loop only when there is something to update
857 if rangeTimer <= 0 or flashTime <= 0 then
858 for button in next, ActiveButtons do
859 -- Flashing
860 if button.flashing == 1 and flashTime <= 0 then
861 if button.Flash:IsShown() then
862 button.Flash:Hide()
863 else
864 button.Flash:Show()
865 end
866 end
867
868 -- Range
869 if rangeTimer <= 0 then
870 local inRange = button:IsInRange()
871 local oldRange = button.outOfRange
872 button.outOfRange = (inRange == false)
873 if oldRange ~= button.outOfRange then
874 if button.config.outOfRangeColoring == "button" then
875 UpdateUsable(button)
876 elseif button.config.outOfRangeColoring == "hotkey" then
877 local hotkey = button.HotKey
878 if hotkey:GetText() == RANGE_INDICATOR then
879 if inRange == false then
880 hotkey:Show()
881 else
882 hotkey:Hide()
883 end
884 end
885 if inRange == false then
886 hotkey:SetVertexColor(unpack(button.config.colors.range))
887 else
888 hotkey:SetVertexColor(0.75, 0.75, 0.75)
889 end
890 end
891 end
892 end
893 end
894
895 -- Update values
896 if flashTime <= 0 then
897 flashTime = flashTime + ATTACK_BUTTON_FLASH_TIME
898 end
899 if rangeTimer <= 0 then
900 rangeTimer = TOOLTIP_UPDATE_TIME
901 end
902 end
903end
904
905local gridCounter = 0
906function ShowGrid()
907 gridCounter = gridCounter + 1
908 if gridCounter >= 1 then
909 for button in next, ButtonRegistry do
910 if button:IsShown() then
911 button:SetAlpha(1.0)
912 end
913 end
914 end
915end
916
917function HideGrid()
918 if gridCounter > 0 then
919 gridCounter = gridCounter - 1
920 end
921 if gridCounter == 0 then
922 for button in next, ButtonRegistry do
923 if button:IsShown() and not button:HasAction() and not button.config.showGrid then
924 button:SetAlpha(0.0)
925 end
926 end
927 end
928end
929
930function UpdateGrid(self)
931 if self.config.showGrid then
932 self:SetAlpha(1.0)
933 elseif gridCounter == 0 and self:IsShown() and not self:HasAction() then
934 self:SetAlpha(0.0)
935 end
936end
937
938-----------------------------------------------------------
939--- KeyBound integration
940
941function Generic:GetBindingAction()
942 return self.config.keyBoundTarget or "CLICK "..self:GetName()..":LeftButton"
943end
944
945function Generic:GetHotkey()
946 local name = "CLICK "..self:GetName()..":LeftButton"
947 local key = GetBindingKey(self.config.keyBoundTarget or name)
948 if not key and self.config.keyBoundTarget then
949 key = GetBindingKey(name)
950 end
951 if key then
952 return KeyBound and KeyBound:ToShortKey(key) or key
953 end
954end
955
956local function getKeys(binding, keys)
957 keys = keys or ""
958 for i = 1, select("#", GetBindingKey(binding)) do
959 local hotKey = select(i, GetBindingKey(binding))
960 if keys ~= "" then
961 keys = keys .. ", "
962 end
963 keys = keys .. GetBindingText(hotKey)
964 end
965 return keys
966end
967
968function Generic:GetBindings()
969 local keys
970
971 if self.config.keyBoundTarget then
972 keys = getKeys(self.config.keyBoundTarget)
973 end
974
975 keys = getKeys("CLICK "..self:GetName()..":LeftButton", keys)
976
977 return keys
978end
979
980function Generic:SetKey(key)
981 if self.config.keyBoundTarget then
982 SetBinding(key, self.config.keyBoundTarget)
983 else
984 SetBindingClick(key, self:GetName(), "LeftButton")
985 end
986 lib.callbacks:Fire("OnKeybindingChanged", self, key)
987end
988
989local function clearBindings(binding)
990 while GetBindingKey(binding) do
991 SetBinding(GetBindingKey(binding), nil)
992 end
993end
994
995function Generic:ClearBindings()
996 if self.config.keyBoundTarget then
997 clearBindings(self.config.keyBoundTarget)
998 end
999 clearBindings("CLICK "..self:GetName()..":LeftButton")
1000 lib.callbacks:Fire("OnKeybindingChanged", self, nil)
1001end
1002
1003-----------------------------------------------------------
1004--- button management
1005
1006function Generic:UpdateAction(force)
1007 local action_type, action = self:GetAction()
1008 if force or action_type ~= self._state_type or action ~= self._state_action then
1009 -- type changed, update the metatable
1010 if force or self._state_type ~= action_type then
1011 local meta = type_meta_map[action_type] or type_meta_map.empty
1012 setmetatable(self, meta)
1013 self._state_type = action_type
1014 end
1015 self._state_action = action
1016 Update(self)
1017 end
1018end
1019
1020function Update(self)
1021 if self:HasAction() then
1022 ActiveButtons[self] = true
1023 if self._state_type == "action" then
1024 ActionButtons[self] = true
1025 NonActionButtons[self] = nil
1026 else
1027 ActionButtons[self] = nil
1028 NonActionButtons[self] = true
1029 end
1030 self:SetAlpha(1.0)
1031 UpdateButtonState(self)
1032 UpdateUsable(self)
1033 UpdateCooldown(self)
1034 UpdateFlash(self)
1035 else
1036 ActiveButtons[self] = nil
1037 ActionButtons[self] = nil
1038 NonActionButtons[self] = nil
1039 if gridCounter == 0 and not self.config.showGrid then
1040 self:SetAlpha(0.0)
1041 end
1042 self.cooldown:Hide()
1043 self:SetChecked(false)
1044
1045 if self.chargeCooldown then
1046 EndChargeCooldown(self.chargeCooldown)
1047 end
1048
1049 if self.LevelLinkLockIcon then
1050 self.LevelLinkLockIcon:SetShown(false)
1051 end
1052 end
1053
1054 -- Add a green border if button is an equipped item
1055 if self:IsEquipped() and not self.config.hideElements.equipped then
1056 self.Border:SetVertexColor(0, 1.0, 0, 0.35)
1057 self.Border:Show()
1058 else
1059 self.Border:Hide()
1060 end
1061
1062 -- Update Action Text
1063 if not self:IsConsumableOrStackable() then
1064 self.Name:SetText(self:GetActionText())
1065 else
1066 self.Name:SetText("")
1067 end
1068
1069 -- Update icon and hotkey
1070 local texture = self:GetTexture()
1071
1072 -- Zone ability button handling
1073 self.zoneAbilityDisabled = false
1074 self.icon:SetDesaturated(false)
1075
1076 if texture then
1077 self.icon:SetTexture(texture)
1078 self.icon:Show()
1079 self.rangeTimer = - 1
1080 self:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
1081 if not self.LBFSkinned and not self.MasqueSkinned then
1082 self.NormalTexture:SetTexCoord(0, 0, 0, 0)
1083 end
1084 else
1085 self.icon:Hide()
1086 self.cooldown:Hide()
1087 self.rangeTimer = nil
1088 self:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
1089 if self.HotKey:GetText() == RANGE_INDICATOR then
1090 self.HotKey:Hide()
1091 else
1092 self.HotKey:SetVertexColor(0.75, 0.75, 0.75)
1093 end
1094 if not self.LBFSkinned and not self.MasqueSkinned then
1095 self.NormalTexture:SetTexCoord(-0.15, 1.15, -0.15, 1.17)
1096 end
1097 end
1098
1099 self:UpdateLocal()
1100
1101 UpdateCount(self)
1102
1103 UpdateFlyout(self)
1104
1105 UpdateOverlayGlow(self)
1106
1107 UpdateNewAction(self)
1108
1109 UpdateSpellHighlight(self)
1110
1111 if GameTooltip_GetOwnerForbidden() == self then
1112 UpdateTooltip(self)
1113 end
1114
1115 -- this could've been a spec change, need to call OnStateChanged for action buttons, if present
1116 if not InCombatLockdown() and self._state_type == "action" then
1117 local onStateChanged = self:GetAttribute("OnStateChanged")
1118 if onStateChanged then
1119 self.header:SetFrameRef("updateButton", self)
1120 self.header:Execute(([[
1121 local frame = self:GetFrameRef("updateButton")
1122 control:RunFor(frame, frame:GetAttribute("OnStateChanged"), %s, %s, %s)
1123 ]]):format(formatHelper(self:GetAttribute("state")), formatHelper(self._state_type), formatHelper(self._state_action)))
1124 end
1125 end
1126 lib.callbacks:Fire("OnButtonUpdate", self)
1127end
1128
1129function Generic:UpdateLocal()
1130-- dummy function the other button types can override for special updating
1131end
1132
1133function UpdateButtonState(self)
1134 if self:IsCurrentlyActive() or self:IsAutoRepeat() then
1135 self:SetChecked(true)
1136 else
1137 self:SetChecked(false)
1138 end
1139 lib.callbacks:Fire("OnButtonState", self)
1140end
1141
1142function UpdateUsable(self)
1143 -- TODO: make the colors configurable
1144 -- TODO: allow disabling of the whole recoloring
1145 if self.config.outOfRangeColoring == "button" and self.outOfRange then
1146 self.icon:SetVertexColor(unpack(self.config.colors.range))
1147 else
1148 local isUsable, notEnoughMana = self:IsUsable()
1149 if isUsable then
1150 self.icon:SetVertexColor(1.0, 1.0, 1.0)
1151 --self.NormalTexture:SetVertexColor(1.0, 1.0, 1.0)
1152 elseif notEnoughMana then
1153 self.icon:SetVertexColor(unpack(self.config.colors.mana))
1154 --self.NormalTexture:SetVertexColor(0.5, 0.5, 1.0)
1155 else
1156 self.icon:SetVertexColor(0.4, 0.4, 0.4)
1157 --self.NormalTexture:SetVertexColor(1.0, 1.0, 1.0)
1158 end
1159 end
1160
1161 if not WoWClassic and not WoWBCC and self._state_type == "action" then
1162 local isLevelLinkLocked = C_LevelLink.IsActionLocked(self._state_action)
1163 if not self.icon:IsDesaturated() then
1164 self.icon:SetDesaturated(isLevelLinkLocked)
1165 end
1166
1167 if self.LevelLinkLockIcon then
1168 self.LevelLinkLockIcon:SetShown(isLevelLinkLocked)
1169 end
1170 end
1171
1172 lib.callbacks:Fire("OnButtonUsable", self)
1173end
1174
1175function UpdateCount(self)
1176 if not self:HasAction() then
1177 self.Count:SetText("")
1178 return
1179 end
1180 if self:IsConsumableOrStackable() then
1181 local count = self:GetCount()
1182 if count > (self.maxDisplayCount or 9999) then
1183 self.Count:SetText("*")
1184 else
1185 self.Count:SetText(count)
1186 end
1187 else
1188 local charges, maxCharges, _chargeStart, _chargeDuration = self:GetCharges()
1189 if charges and maxCharges and maxCharges > 1 then
1190 self.Count:SetText(charges)
1191 else
1192 self.Count:SetText("")
1193 end
1194 end
1195end
1196
1197function EndChargeCooldown(self)
1198 self:Hide()
1199 self:SetParent(UIParent)
1200 self.parent.chargeCooldown = nil
1201 self.parent = nil
1202 tinsert(lib.ChargeCooldowns, self)
1203end
1204
1205local function StartChargeCooldown(parent, chargeStart, chargeDuration, chargeModRate)
1206 if not parent.chargeCooldown then
1207 local cooldown = tremove(lib.ChargeCooldowns)
1208 if not cooldown then
1209 lib.NumChargeCooldowns = lib.NumChargeCooldowns + 1
1210 cooldown = CreateFrame("Cooldown", "LAB10ChargeCooldown"..lib.NumChargeCooldowns, parent, "CooldownFrameTemplate");
1211 cooldown:SetScript("OnCooldownDone", EndChargeCooldown)
1212 cooldown:SetHideCountdownNumbers(true)
1213 cooldown:SetDrawSwipe(false)
1214 end
1215 cooldown:SetParent(parent)
1216 cooldown:SetAllPoints(parent)
1217 cooldown:SetFrameStrata("TOOLTIP")
1218 cooldown:Show()
1219 parent.chargeCooldown = cooldown
1220 cooldown.parent = parent
1221 end
1222 -- set cooldown
1223 parent.chargeCooldown:SetDrawBling(parent.chargeCooldown:GetEffectiveAlpha() > 0.5)
1224 CooldownFrame_Set(parent.chargeCooldown, chargeStart, chargeDuration, true, true, chargeModRate)
1225
1226 -- update charge cooldown skin when masque is used
1227 if Masque and Masque.UpdateCharge then
1228 Masque:UpdateCharge(parent)
1229 end
1230
1231 if not chargeStart or chargeStart == 0 then
1232 EndChargeCooldown(parent.chargeCooldown)
1233 end
1234end
1235
1236local function OnCooldownDone(self)
1237 self:SetScript("OnCooldownDone", nil)
1238 UpdateCooldown(self:GetParent())
1239end
1240
1241function UpdateCooldown(self)
1242 local locStart, locDuration = self:GetLossOfControlCooldown()
1243 local start, duration, enable, modRate = self:GetCooldown()
1244 local charges, maxCharges, chargeStart, chargeDuration, chargeModRate = self:GetCharges()
1245
1246 self.cooldown:SetDrawBling(self.cooldown:GetEffectiveAlpha() > 0.5)
1247
1248 if (locStart + locDuration) > (start + duration) then
1249 if self.cooldown.currentCooldownType ~= COOLDOWN_TYPE_LOSS_OF_CONTROL then
1250 self.cooldown:SetEdgeTexture("Interface\\Cooldown\\edge-LoC")
1251 self.cooldown:SetSwipeColor(0.17, 0, 0)
1252 self.cooldown:SetHideCountdownNumbers(true)
1253 self.cooldown.currentCooldownType = COOLDOWN_TYPE_LOSS_OF_CONTROL
1254 end
1255 CooldownFrame_Set(self.cooldown, locStart, locDuration, true, true, modRate)
1256 else
1257 if self.cooldown.currentCooldownType ~= COOLDOWN_TYPE_NORMAL then
1258 self.cooldown:SetEdgeTexture("Interface\\Cooldown\\edge")
1259 self.cooldown:SetSwipeColor(0, 0, 0)
1260 self.cooldown:SetHideCountdownNumbers(false)
1261 self.cooldown.currentCooldownType = COOLDOWN_TYPE_NORMAL
1262 end
1263 if locStart > 0 then
1264 self.cooldown:SetScript("OnCooldownDone", OnCooldownDone)
1265 end
1266
1267 if charges and maxCharges and maxCharges > 1 and charges < maxCharges then
1268 StartChargeCooldown(self, chargeStart, chargeDuration, chargeModRate)
1269 elseif self.chargeCooldown then
1270 EndChargeCooldown(self.chargeCooldown)
1271 end
1272 CooldownFrame_Set(self.cooldown, start, duration, enable, false, modRate)
1273 end
1274end
1275
1276function StartFlash(self)
1277 self.flashing = 1
1278 flashTime = 0
1279 UpdateButtonState(self)
1280end
1281
1282function StopFlash(self)
1283 self.flashing = 0
1284 self.Flash:Hide()
1285 UpdateButtonState(self)
1286end
1287
1288function UpdateFlash(self)
1289 if (self:IsAttack() and self:IsCurrentlyActive()) or self:IsAutoRepeat() then
1290 StartFlash(self)
1291 else
1292 StopFlash(self)
1293 end
1294end
1295
1296function UpdateTooltip(self)
1297 if GameTooltip:IsForbidden() then return end
1298 if (GetCVar("UberTooltips") == "1") then
1299 GameTooltip_SetDefaultAnchor(GameTooltip, self);
1300 else
1301 GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
1302 end
1303 if self:SetTooltip() then
1304 self.UpdateTooltip = UpdateTooltip
1305 else
1306 self.UpdateTooltip = nil
1307 end
1308end
1309
1310function UpdateHotkeys(self)
1311 local key = self:GetHotkey()
1312 if not key or key == "" or self.config.hideElements.hotkey then
1313 self.HotKey:SetText(RANGE_INDICATOR)
1314 self.HotKey:Hide()
1315 else
1316 self.HotKey:SetText(key)
1317 self.HotKey:Show()
1318 end
1319end
1320
1321function ShowOverlayGlow(self)
1322 if LBG then
1323 LBG.ShowOverlayGlow(self)
1324 end
1325end
1326
1327function HideOverlayGlow(self)
1328 if LBG then
1329 LBG.HideOverlayGlow(self)
1330 end
1331end
1332
1333function UpdateOverlayGlow(self)
1334 local spellId = self:GetSpellId()
1335 if spellId and IsSpellOverlayed(spellId) then
1336 ShowOverlayGlow(self)
1337 else
1338 HideOverlayGlow(self)
1339 end
1340end
1341
1342function ClearNewActionHighlight(action, preventIdenticalActionsFromClearing, value)
1343 lib.ACTION_HIGHLIGHT_MARKS[action] = value
1344
1345 for button in next, ButtonRegistry do
1346 if button._state_type == "action" and action == tonumber(button._state_action) then
1347 UpdateNewAction(button)
1348 end
1349 end
1350
1351 if preventIdenticalActionsFromClearing then
1352 return
1353 end
1354
1355 -- iterate through actions and unmark all that are the same type
1356 local unmarkedType, unmarkedID = GetActionInfo(action)
1357 for actionKey, markValue in pairs(lib.ACTION_HIGHLIGHT_MARKS) do
1358 if markValue then
1359 local actionType, actionID = GetActionInfo(actionKey)
1360 if actionType == unmarkedType and actionID == unmarkedID then
1361 ClearNewActionHighlight(actionKey, true, value)
1362 end
1363 end
1364 end
1365end
1366
1367hooksecurefunc("MarkNewActionHighlight", function(action)
1368 lib.ACTION_HIGHLIGHT_MARKS[action] = true
1369 for button in next, ButtonRegistry do
1370 if button._state_type == "action" and action == tonumber(button._state_action) then
1371 UpdateNewAction(button)
1372 end
1373 end
1374end)
1375
1376hooksecurefunc("ClearNewActionHighlight", function(action, preventIdenticalActionsFromClearing)
1377 ClearNewActionHighlight(action, preventIdenticalActionsFromClearing, nil)
1378end)
1379
1380function UpdateNewAction(self)
1381 -- special handling for "New Action" markers
1382 if self.NewActionTexture then
1383 if self._state_type == "action" and lib.ACTION_HIGHLIGHT_MARKS[self._state_action] then
1384 self.NewActionTexture:Show()
1385 else
1386 self.NewActionTexture:Hide()
1387 end
1388 end
1389end
1390
1391hooksecurefunc("UpdateOnBarHighlightMarksBySpell", function(spellID)
1392 lib.ON_BAR_HIGHLIGHT_MARK_TYPE = "spell"
1393 lib.ON_BAR_HIGHLIGHT_MARK_ID = tonumber(spellID)
1394
1395 for button in next, ButtonRegistry do
1396 UpdateSpellHighlight(button)
1397 end
1398end)
1399
1400hooksecurefunc("UpdateOnBarHighlightMarksByFlyout", function(flyoutID)
1401 lib.ON_BAR_HIGHLIGHT_MARK_TYPE = "flyout"
1402 lib.ON_BAR_HIGHLIGHT_MARK_ID = tonumber(flyoutID)
1403
1404 for button in next, ButtonRegistry do
1405 UpdateSpellHighlight(button)
1406 end
1407end)
1408
1409hooksecurefunc("ClearOnBarHighlightMarks", function()
1410 lib.ON_BAR_HIGHLIGHT_MARK_TYPE = nil
1411
1412 for button in next, ButtonRegistry do
1413 UpdateSpellHighlight(button)
1414 end
1415end)
1416
1417function UpdateSpellHighlight(self)
1418 local shown = false
1419
1420 local highlightType, id = lib.ON_BAR_HIGHLIGHT_MARK_TYPE, lib.ON_BAR_HIGHLIGHT_MARK_ID
1421 if highlightType == "spell" and self:GetSpellId() == id then
1422 shown = true
1423 elseif highlightType == "flyout" and self._state_type == "action" then
1424 local actionType, actionId = GetActionInfo(self._state_action)
1425 if actionType == "flyout" and actionId == id then
1426 shown = true
1427 end
1428 end
1429
1430 if shown then
1431 self.SpellHighlightTexture:Show()
1432 self.SpellHighlightAnim:Play()
1433 else
1434 self.SpellHighlightTexture:Hide()
1435 self.SpellHighlightAnim:Stop()
1436 end
1437end
1438
1439-- Hook UpdateFlyout so we can use the blizzy templates
1440hooksecurefunc("ActionButton_UpdateFlyout", function(self, ...)
1441 if ButtonRegistry[self] then
1442 UpdateFlyout(self)
1443 end
1444end)
1445
1446function UpdateFlyout(self)
1447 -- disabled FlyoutBorder/BorderShadow, those are not handled by LBF and look terrible
1448 self.FlyoutBorder:Hide()
1449 self.FlyoutBorderShadow:Hide()
1450 if self._state_type == "action" then
1451 -- based on ActionButton_UpdateFlyout in ActionButton.lua
1452 local actionType = GetActionInfo(self._state_action)
1453 if actionType == "flyout" then
1454 -- Update border and determine arrow position
1455 local arrowDistance
1456 if (SpellFlyout and SpellFlyout:IsShown() and SpellFlyout:GetParent() == self) or GetMouseFocus() == self then
1457 arrowDistance = 5
1458 else
1459 arrowDistance = 2
1460 end
1461
1462 -- Update arrow
1463 self.FlyoutArrow:Show()
1464 self.FlyoutArrow:ClearAllPoints()
1465 local direction = self:GetAttribute("flyoutDirection");
1466 if direction == "LEFT" then
1467 self.FlyoutArrow:SetPoint("LEFT", self, "LEFT", -arrowDistance, 0)
1468 SetClampedTextureRotation(self.FlyoutArrow, 270)
1469 elseif direction == "RIGHT" then
1470 self.FlyoutArrow:SetPoint("RIGHT", self, "RIGHT", arrowDistance, 0)
1471 SetClampedTextureRotation(self.FlyoutArrow, 90)
1472 elseif direction == "DOWN" then
1473 self.FlyoutArrow:SetPoint("BOTTOM", self, "BOTTOM", 0, -arrowDistance)
1474 SetClampedTextureRotation(self.FlyoutArrow, 180)
1475 else
1476 self.FlyoutArrow:SetPoint("TOP", self, "TOP", 0, arrowDistance)
1477 SetClampedTextureRotation(self.FlyoutArrow, 0)
1478 end
1479
1480 -- return here, otherwise flyout is hidden
1481 return
1482 end
1483 end
1484 self.FlyoutArrow:Hide()
1485end
1486
1487function UpdateRangeTimer()
1488 rangeTimer = -1
1489end
1490
1491-----------------------------------------------------------
1492--- WoW API mapping
1493--- Generic Button
1494Generic.HasAction = function(self) return nil end
1495Generic.GetActionText = function(self) return "" end
1496Generic.GetTexture = function(self) return nil end
1497Generic.GetCharges = function(self) return nil end
1498Generic.GetCount = function(self) return 0 end
1499Generic.GetCooldown = function(self) return 0, 0, 0 end
1500Generic.IsAttack = function(self) return nil end
1501Generic.IsEquipped = function(self) return nil end
1502Generic.IsCurrentlyActive = function(self) return nil end
1503Generic.IsAutoRepeat = function(self) return nil end
1504Generic.IsUsable = function(self) return nil end
1505Generic.IsConsumableOrStackable = function(self) return nil end
1506Generic.IsUnitInRange = function(self, unit) return nil end
1507Generic.IsInRange = function(self)
1508 local unit = self:GetAttribute("unit")
1509 if unit == "player" then
1510 unit = nil
1511 end
1512 local val = self:IsUnitInRange(unit)
1513 -- map 1/0 to true false, since the return values are inconsistent between actions and spells
1514 if val == 1 then val = true elseif val == 0 then val = false end
1515 return val
1516end
1517Generic.SetTooltip = function(self) return nil end
1518Generic.GetSpellId = function(self) return nil end
1519Generic.GetLossOfControlCooldown = function(self) return 0, 0 end
1520
1521-----------------------------------------------------------
1522--- Action Button
1523Action.HasAction = function(self) return HasAction(self._state_action) end
1524Action.GetActionText = function(self) return GetActionText(self._state_action) end
1525Action.GetTexture = function(self) return GetActionTexture(self._state_action) end
1526Action.GetCharges = function(self) return GetActionCharges(self._state_action) end
1527Action.GetCount = function(self) return GetActionCount(self._state_action) end
1528Action.GetCooldown = function(self) return GetActionCooldown(self._state_action) end
1529Action.IsAttack = function(self) return IsAttackAction(self._state_action) end
1530Action.IsEquipped = function(self) return IsEquippedAction(self._state_action) end
1531Action.IsCurrentlyActive = function(self) return IsCurrentAction(self._state_action) end
1532Action.IsAutoRepeat = function(self) return IsAutoRepeatAction(self._state_action) end
1533Action.IsUsable = function(self) return IsUsableAction(self._state_action) end
1534Action.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
1535Action.IsUnitInRange = function(self, unit) return IsActionInRange(self._state_action, unit) end
1536Action.SetTooltip = function(self) return GameTooltip:SetAction(self._state_action) end
1537Action.GetSpellId = function(self)
1538 local actionType, id, _subType = GetActionInfo(self._state_action)
1539 if actionType == "spell" then
1540 return id
1541 elseif actionType == "macro" then
1542 return (GetMacroSpell(id))
1543 end
1544end
1545Action.GetLossOfControlCooldown = function(self) return GetActionLossOfControlCooldown(self._state_action) end
1546
1547-- Classic overrides for item count breakage
1548if WoWClassic then
1549 -- if the library is present, simply use it to override action counts
1550 local LibClassicSpellActionCount = LibStub("LibClassicSpellActionCount-1.0", true)
1551 if LibClassicSpellActionCount then
1552 Action.GetCount = function(self) return LibClassicSpellActionCount:GetActionCount(self._state_action) end
1553 else
1554 -- if we don't have the library, only show count for items, like the default UI
1555 Action.IsConsumableOrStackable = function(self) return IsItemAction(self._state_action) and (IsConsumableAction(self._state_action) or IsStackableAction(self._state_action)) end
1556 end
1557end
1558
1559if WoWClassic or WoWBCC then
1560 -- disable loss of control cooldown on classic
1561 Action.GetLossOfControlCooldown = function(self) return 0,0 end
1562end
1563
1564-----------------------------------------------------------
1565--- Spell Button
1566Spell.HasAction = function(self) return true end
1567Spell.GetActionText = function(self) return "" end
1568Spell.GetTexture = function(self) return GetSpellTexture(self._state_action) end
1569Spell.GetCharges = function(self) return GetSpellCharges(self._state_action) end
1570Spell.GetCount = function(self) return GetSpellCount(self._state_action) end
1571Spell.GetCooldown = function(self) return GetSpellCooldown(self._state_action) end
1572Spell.IsAttack = function(self) return IsAttackSpell(FindSpellBookSlotBySpellID(self._state_action), "spell") end -- needs spell book id as of 4.0.1.13066
1573Spell.IsEquipped = function(self) return nil end
1574Spell.IsCurrentlyActive = function(self) return IsCurrentSpell(self._state_action) end
1575Spell.IsAutoRepeat = function(self) return IsAutoRepeatSpell(FindSpellBookSlotBySpellID(self._state_action), "spell") end -- needs spell book id as of 4.0.1.13066
1576Spell.IsUsable = function(self) return IsUsableSpell(self._state_action) end
1577Spell.IsConsumableOrStackable = function(self) return IsConsumableSpell(self._state_action) end
1578Spell.IsUnitInRange = function(self, unit) return IsSpellInRange(FindSpellBookSlotBySpellID(self._state_action), "spell", unit) end -- needs spell book id as of 4.0.1.13066
1579Spell.SetTooltip = function(self) return GameTooltip:SetSpellByID(self._state_action) end
1580Spell.GetSpellId = function(self) return self._state_action end
1581Spell.GetLossOfControlCooldown = function(self) return GetSpellLossOfControlCooldown(self._state_action) end
1582
1583-----------------------------------------------------------
1584--- Item Button
1585local function getItemId(input)
1586 return input:match("^item:(%d+)")
1587end
1588
1589Item.HasAction = function(self) return true end
1590Item.GetActionText = function(self) return "" end
1591Item.GetTexture = function(self) return GetItemIcon(self._state_action) end
1592Item.GetCharges = function(self) return nil end
1593Item.GetCount = function(self) return GetItemCount(self._state_action, nil, true) end
1594Item.GetCooldown = function(self) return GetItemCooldown(getItemId(self._state_action)) end
1595Item.IsAttack = function(self) return nil end
1596Item.IsEquipped = function(self) return IsEquippedItem(self._state_action) end
1597Item.IsCurrentlyActive = function(self) return IsCurrentItem(self._state_action) end
1598Item.IsAutoRepeat = function(self) return nil end
1599Item.IsUsable = function(self) return IsUsableItem(self._state_action) end
1600Item.IsConsumableOrStackable = function(self) return IsConsumableItem(self._state_action) end
1601Item.IsUnitInRange = function(self, unit) return IsItemInRange(self._state_action, unit) end
1602Item.SetTooltip = function(self) return GameTooltip:SetHyperlink(self._state_action) end
1603Item.GetSpellId = function(self) return nil end
1604
1605-----------------------------------------------------------
1606--- Macro Button
1607-- TODO: map results of GetMacroSpell/GetMacroItem to proper results
1608Macro.HasAction = function(self) return true end
1609Macro.GetActionText = function(self) return (GetMacroInfo(self._state_action)) end
1610Macro.GetTexture = function(self) return (select(2, GetMacroInfo(self._state_action))) end
1611Macro.GetCharges = function(self) return nil end
1612Macro.GetCount = function(self) return 0 end
1613Macro.GetCooldown = function(self) return 0, 0, 0 end
1614Macro.IsAttack = function(self) return nil end
1615Macro.IsEquipped = function(self) return nil end
1616Macro.IsCurrentlyActive = function(self) return nil end
1617Macro.IsAutoRepeat = function(self) return nil end
1618Macro.IsUsable = function(self) return nil end
1619Macro.IsConsumableOrStackable = function(self) return nil end
1620Macro.IsUnitInRange = function(self, unit) return nil end
1621Macro.SetTooltip = function(self) return nil end
1622Macro.GetSpellId = function(self) return nil end
1623
1624-----------------------------------------------------------
1625--- Custom Button
1626Custom.HasAction = function(self) return true end
1627Custom.GetActionText = function(self) return "" end
1628Custom.GetTexture = function(self) return self._state_action.texture end
1629Custom.GetCharges = function(self) return nil end
1630Custom.GetCount = function(self) return 0 end
1631Custom.GetCooldown = function(self) return 0, 0, 0 end
1632Custom.IsAttack = function(self) return nil end
1633Custom.IsEquipped = function(self) return nil end
1634Custom.IsCurrentlyActive = function(self) return nil end
1635Custom.IsAutoRepeat = function(self) return nil end
1636Custom.IsUsable = function(self) return true end
1637Custom.IsConsumableOrStackable = function(self) return nil end
1638Custom.IsUnitInRange = function(self, unit) return nil end
1639Custom.SetTooltip = function(self) return GameTooltip:SetText(self._state_action.tooltip) end
1640Custom.GetSpellId = function(self) return nil end
1641Custom.RunCustom = function(self, unit, button) return self._state_action.func(self, unit, button) end
1642
1643--- WoW Classic overrides
1644if WoWClassic or WoWBCC then
1645 UpdateOverlayGlow = function() end
1646end
1647
1648-----------------------------------------------------------
1649--- Update old Buttons
1650if oldversion and next(lib.buttonRegistry) then
1651 InitializeEventHandler()
1652 for button in next, lib.buttonRegistry do
1653 -- this refreshes the metatable on the button
1654 Generic.UpdateAction(button, true)
1655 SetupSecureSnippets(button)
1656 if oldversion < 12 then
1657 WrapOnClick(button)
1658 end
1659 if oldversion < 23 then
1660 if button.overlay then
1661 button.overlay:Hide()
1662 ActionButton_HideOverlayGlow(button)
1663 button.overlay = nil
1664 UpdateOverlayGlow(button)
1665 end
1666 end
1667 end
1668end
1669