| flickerstreak@161 | 1 local ReAction = ReAction | 
| flickerstreak@161 | 2 local L = ReAction.L | 
| flickerstreak@161 | 3 local _G = _G | 
| flickerstreak@161 | 4 local CreateFrame = CreateFrame | 
| flickerstreak@161 | 5 local format = string.format | 
| flickerstreak@161 | 6 local unpack = unpack | 
| flickerstreak@161 | 7 local GetCVar = GetCVar | 
| flickerstreak@161 | 8 local GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor | 
| flickerstreak@161 | 9 local CooldownFrame_SetTimer = CooldownFrame_SetTimer | 
| flickerstreak@161 | 10 local InCombatLockdown = InCombatLockdown | 
| flickerstreak@161 | 11 local IsUsableSpell = IsUsableSpell | 
| flickerstreak@161 | 12 local IsUsableAction = IsUsableAction | 
| flickerstreak@161 | 13 local IsSpellKnown = IsSpellKnown | 
| flickerstreak@161 | 14 local IsSpellInRange = IsSpellInRange | 
| flickerstreak@161 | 15 local IsActionInRange = IsActionInRange | 
| flickerstreak@161 | 16 local GetSpellInfo = GetSpellInfo | 
| flickerstreak@161 | 17 local GetSpellCooldown = GetSpellCooldown | 
| flickerstreak@161 | 18 local GetActionCooldown = GetActionCooldown | 
| flickerstreak@161 | 19 local GetSpellTexture = GetSpellTexture | 
| flickerstreak@161 | 20 local GetActionTexture = GetActionTexture | 
| flickerstreak@161 | 21 local GetMultiCastTotemSpells = GetMultiCastTotemSpells | 
| flickerstreak@161 | 22 | 
| flickerstreak@161 | 23 --[[ | 
| flickerstreak@161 | 24   Blizzard Constants: | 
| flickerstreak@161 | 25     - NUM_MULTI_CAST_BUTTONS_PER_PAGE = 4 | 
| flickerstreak@161 | 26     - NUM_MULTI_CAST_PAGES = 3 | 
| flickerstreak@166 | 27     - SHAMAN_TOTEM_PRIORITIES = { } -- sets the order of the totems | 
| flickerstreak@161 | 28     - TOTEM_MULTI_CAST_SUMMON_SPELLS = { } -- list of summon spellIDs | 
| flickerstreak@161 | 29     - TOTEM_MULTI_CAST_RECALL_SPELLS = { } -- list of recall spellIDs | 
| flickerstreak@161 | 30 | 
| flickerstreak@161 | 31   Blizzard Events: | 
| flickerstreak@161 | 32     - UPDATE_MULTI_CAST_ACTIONBAR | 
| flickerstreak@161 | 33 | 
| flickerstreak@161 | 34   Blizzard APIs: | 
| flickerstreak@161 | 35     - GetMultiCastBarOffset() : returns 6 | 
| flickerstreak@161 | 36 | 
| flickerstreak@161 | 37     - SetMultiCastSpell(actionID, spellID) (protected) OR | 
| flickerstreak@161 | 38          SetAttribute("type","multispell") | 
| flickerstreak@161 | 39          SetAttribute("action",actionID) | 
| flickerstreak@161 | 40          SetAttribute("spell",spellID) | 
| flickerstreak@161 | 41 | 
| flickerstreak@161 | 42          note: multicast actionID page is NUM_ACTIONBAR_PAGES + GetMultiCastBarOffset(), | 
| flickerstreak@161 | 43                so that's action ID 132-144. | 
| flickerstreak@161 | 44 | 
| flickerstreak@161 | 45     - spell1, spell2, spell3, ... = GetMultiCastTotemSpells(slot) | 
| flickerstreak@167 | 46         returns spellIDs for all known totems that fit that slot. This function is available in | 
| flickerstreak@167 | 47         the secure environment. | 
| flickerstreak@161 | 48 | 
| flickerstreak@161 | 49   Blizzard textures: | 
| flickerstreak@161 | 50     All the textures for the multicast bar (arrows, empty-slot icons, etc) are part of a single | 
| flickerstreak@161 | 51     texture: each texture uses SetTexCoord() to display only a slice of the textures. I suppose | 
| flickerstreak@161 | 52     this is to slightly optimize texture load performance, but it makes the UI code more clumsy. | 
| flickerstreak@161 | 53 | 
| flickerstreak@161 | 54     Each totem button and arrow has a colored border indicating its elemental type. | 
| flickerstreak@161 | 55       TODO: update code to use these pretty per-slot icons, or start setting a border. | 
| flickerstreak@161 | 56 | 
| flickerstreak@161 | 57   Design Notes: | 
| flickerstreak@161 | 58     - Only the header has a secure context. All other frames execute in its context. | 
| flickerstreak@161 | 59 | 
| flickerstreak@161 | 60     - Each button is either type "spell" (summon/recall) or type "action" (totem action IDs are | 
| flickerstreak@161 | 61       GetBonusBarOffset()=6, 132-144) with 3 pages of 4 buttons. The paging is controlled by | 
| flickerstreak@161 | 62       the summon flyout, which is also paged with the summon spells (the recall button is not paged) | 
| flickerstreak@161 | 63 | 
| flickerstreak@161 | 64     - A spell list is updated in the secure context at setup time (TODO: redo setup when learning new | 
| flickerstreak@161 | 65       spells) with the list of spells known for each slot. | 
| flickerstreak@161 | 66 | 
| flickerstreak@161 | 67     - Each button (except recall) has an arrow button which appears on mouseover and when clicked | 
| flickerstreak@161 | 68       opens the flyout via a wrapped OnClick handler. When the flyout is open, the arrow does not | 
| flickerstreak@161 | 69       appear. | 
| flickerstreak@161 | 70 | 
| flickerstreak@161 | 71     - A single flyout with N+1 (1 slot is to select no totem for the set) flyout-buttons is a child | 
| flickerstreak@161 | 72       of the bar. Each time the flyout panel is opened, the individual  buttons grab their corresponding | 
| flickerstreak@161 | 73       spell/type from the list, according to the slot which opened the flyout. Each button either sets | 
| flickerstreak@161 | 74       the current page (summon) or sets a multispell to an actionID via type="multispell". None of them | 
| flickerstreak@161 | 75       actually cast any spells (though, I suppose we could modify this so that e.g. configurable | 
| flickerstreak@161 | 76       right-click casts the spell). The flyout also has a close button which closes the flyout: the | 
| flickerstreak@161 | 77       flyout-open code positions the close button anchored to the last button in the flyout (which | 
| flickerstreak@161 | 78       changes dynamically because each slot has a different number of items in the list). | 
| flickerstreak@161 | 79 | 
| flickerstreak@161 | 80     - Multicast sets are not stances, there's no need (or ability) to handle swapping sets if one of | 
| flickerstreak@167 | 81       the summon spells is cast from elsewhere. | 
| flickerstreak@167 | 82 | 
| flickerstreak@167 | 83     - The default UI has Call of the Elements always selected on UI load. This module remembers the last | 
| flickerstreak@167 | 84       selected one and restores it. | 
| flickerstreak@161 | 85 | 
| flickerstreak@161 | 86 | 
| flickerstreak@161 | 87 ]]-- | 
| flickerstreak@161 | 88 | 
| flickerstreak@161 | 89 | 
| flickerstreak@161 | 90 -- | 
| flickerstreak@161 | 91 -- Secure snippets | 
| flickerstreak@161 | 92 -- | 
| flickerstreak@161 | 93 | 
| flickerstreak@161 | 94 -- bar | 
| flickerstreak@161 | 95 local _bar_init = -- function(self) | 
| flickerstreak@161 | 96 [[ | 
| flickerstreak@161 | 97   -- set up some globals in the secure environment | 
| flickerstreak@161 | 98   flyout               = self:GetFrameRef("flyout") | 
| flickerstreak@167 | 99   flyoutSlot           = nil | 
| flickerstreak@167 | 100   summonSlot           = self:GetAttribute("summonSlot") | 
| flickerstreak@167 | 101   recallSlot           = self:GetAttribute("recallSlot") | 
| flickerstreak@161 | 102   baseActionID         = self:GetAttribute("baseActionID") | 
| flickerstreak@167 | 103   slotsPerPage         = self:GetAttribute("slotsPerPage") | 
| flickerstreak@167 | 104   currentPage          = currentPage or self:GetAttribute("lastSummon") or 1 | 
| flickerstreak@167 | 105 | 
| flickerstreak@167 | 106   totemIDsBySlot = newtable() | 
| flickerstreak@167 | 107   for i = 1, slotsPerPage do | 
| flickerstreak@167 | 108     totemIDsBySlot[i] = self:GetAttribute("TOTEM_PRIORITY_"..i) | 
| flickerstreak@161 | 109   end | 
| flickerstreak@167 | 110 | 
| flickerstreak@168 | 111   -- these are set up in bar:SetupBarHeader() | 
| flickerstreak@168 | 112   flyoutChildren = flyoutChildren or newtable() | 
| flickerstreak@168 | 113   summonSpells = summonSpells or newtable() | 
| flickerstreak@161 | 114 ]] | 
| flickerstreak@161 | 115 | 
| flickerstreak@161 | 116 local _onstate_multispellpage = -- function(self, stateid, newstate) | 
| flickerstreak@161 | 117 [[ | 
| flickerstreak@167 | 118   currentPage = tonumber(newstate) | 
| flickerstreak@167 | 119   control:CallMethod("UpdateLastSummon",currentPage) | 
| flickerstreak@161 | 120   control:ChildUpdate() | 
| flickerstreak@161 | 121 ]] | 
| flickerstreak@161 | 122 | 
| flickerstreak@161 | 123 | 
| flickerstreak@161 | 124 -- buttons | 
| flickerstreak@161 | 125 local _childupdate = -- function(self, snippetid, message) | 
| flickerstreak@161 | 126 [[ | 
| flickerstreak@167 | 127   local t = self:GetAttribute("type") | 
| flickerstreak@167 | 128   self:SetAttribute(t, self:GetAttribute(t.."-page"..currentPage)) | 
| flickerstreak@161 | 129 ]] | 
| flickerstreak@161 | 130 | 
| flickerstreak@161 | 131 local _onEnter = -- function(self) | 
| flickerstreak@161 | 132   -- for whatever reason, RegisterAutoHide is unreliable | 
| flickerstreak@161 | 133   -- unless you re-anchor the frame prior to calling it. | 
| flickerstreak@161 | 134   -- Even then, it's still not terribly reliable. | 
| flickerstreak@161 | 135 [[ | 
| flickerstreak@167 | 136   local slot = self:GetAttribute("bar-idx") | 
| flickerstreak@167 | 137   local arrow = owner:GetFrameRef("arrow-"..slot) | 
| flickerstreak@167 | 138   if arrow and not arrow:IsShown() and not (flyout:IsVisible() and flyoutSlot == slot) then | 
| flickerstreak@167 | 139     arrow:ClearAllPoints() | 
| flickerstreak@167 | 140     arrow:SetPoint("BOTTOM",self,"TOP",0,0) | 
| flickerstreak@167 | 141     arrow:Show() | 
| flickerstreak@161 | 142     arrow:RegisterAutoHide(0) | 
| flickerstreak@161 | 143     arrow:AddToAutoHide(self) | 
| flickerstreak@161 | 144   end | 
| flickerstreak@161 | 145 ]] | 
| flickerstreak@161 | 146 | 
| flickerstreak@161 | 147 | 
| flickerstreak@161 | 148 -- flyout arrow | 
| flickerstreak@161 | 149 local _arrow_openFlyout = -- function(self) | 
| flickerstreak@161 | 150 [[ | 
| flickerstreak@167 | 151   local slot = self:GetAttribute("bar-idx") | 
| flickerstreak@167 | 152   local lastButton, lastPage | 
| flickerstreak@167 | 153   for page, b in ipairs(flyoutChildren) do | 
| flickerstreak@167 | 154     b:Hide() | 
| flickerstreak@167 | 155     if slot == summonSlot then | 
| flickerstreak@167 | 156       local spellID = self:GetParent():GetAttribute("spell-page"..page) | 
| flickerstreak@167 | 157       print("got spell-page"..tostring(page).." = ".. tostring(spellID)) | 
| flickerstreak@167 | 158       if spellID then | 
| flickerstreak@161 | 159         b:SetAttribute("type","changePage") | 
| flickerstreak@167 | 160         b:SetAttribute("spell",spellID) | 
| flickerstreak@167 | 161         b:Show() | 
| flickerstreak@167 | 162         lastButton = b | 
| flickerstreak@167 | 163         lastPage = page | 
| flickerstreak@167 | 164       end | 
| flickerstreak@167 | 165     else | 
| flickerstreak@167 | 166       local offset = summonSlot or 0 | 
| flickerstreak@167 | 167       local totemID = totemIDsBySlot[slot - offset] | 
| flickerstreak@168 | 168       local spell = select(page, 0, GetMultiCastTotemSpells(totemID) ) | 
| flickerstreak@168 | 169       if spell then | 
| flickerstreak@161 | 170         b:SetAttribute("type","multispell") | 
| flickerstreak@167 | 171         b:SetAttribute("action", baseActionID + (currentPage - 1)*slotsPerPage + totemID) | 
| flickerstreak@168 | 172         b:SetAttribute("spell", spell) | 
| flickerstreak@167 | 173         b:Show() | 
| flickerstreak@167 | 174         lastButton = b | 
| flickerstreak@167 | 175         lastPage = page | 
| flickerstreak@161 | 176       end | 
| flickerstreak@161 | 177     end | 
| flickerstreak@161 | 178   end | 
| flickerstreak@161 | 179 | 
| flickerstreak@161 | 180   local close = owner:GetFrameRef("close") | 
| flickerstreak@161 | 181   if lastButton and close then | 
| flickerstreak@161 | 182     close:ClearAllPoints() | 
| flickerstreak@161 | 183     close:SetPoint("BOTTOM",lastButton,"TOP",0,0) -- TODO: better anchoring | 
| flickerstreak@161 | 184     close:Show() | 
| flickerstreak@161 | 185   end | 
| flickerstreak@161 | 186 | 
| flickerstreak@161 | 187   flyout:ClearAllPoints() | 
| flickerstreak@161 | 188   flyout:SetPoint("BOTTOM",self,"BOTTOM",0,0)  -- TODO: better anchoring | 
| flickerstreak@167 | 189   if lastPage then | 
| flickerstreak@167 | 190     flyout:SetHeight(lastPage * 27 + (close and close:GetHeight() or 0)) | 
| flickerstreak@161 | 191   end | 
| flickerstreak@161 | 192   flyout:Show() | 
| flickerstreak@161 | 193   flyout:RegisterAutoHide(1) -- TODO: configurable | 
| flickerstreak@161 | 194   flyout:AddToAutoHide(owner) | 
| flickerstreak@167 | 195   flyoutSlot = slot | 
| flickerstreak@161 | 196   self:Hide() | 
| flickerstreak@161 | 197 ]] | 
| flickerstreak@161 | 198 | 
| flickerstreak@161 | 199 local _closeFlyout = -- function(self) | 
| flickerstreak@161 | 200 [[ | 
| flickerstreak@161 | 201   flyout:Hide() | 
| flickerstreak@161 | 202 ]] | 
| flickerstreak@161 | 203 | 
| flickerstreak@161 | 204 | 
| flickerstreak@161 | 205 -- flyout child buttons | 
| flickerstreak@161 | 206 local _flyout_child_preClick = -- function(self, button, down) | 
| flickerstreak@161 | 207 [[ | 
| flickerstreak@161 | 208   local button = button | 
| flickerstreak@161 | 209   if self:GetAttribute("type") == "changePage" then | 
| flickerstreak@161 | 210     owner:SetAttribute("state-multispellpage",self:GetAttribute("index")) | 
| flickerstreak@161 | 211     self:GetParent():Hide() | 
| flickerstreak@161 | 212     return false | 
| flickerstreak@161 | 213   else | 
| flickerstreak@161 | 214     return nil, "close" | 
| flickerstreak@161 | 215   end | 
| flickerstreak@161 | 216 ]] | 
| flickerstreak@161 | 217 | 
| flickerstreak@161 | 218 local _flyout_child_postClick = -- function(self, message, button, down) | 
| flickerstreak@161 | 219 [[ | 
| flickerstreak@161 | 220   if message == "close" then | 
| flickerstreak@161 | 221     self:GetParent():Hide() -- hide flyout after selecting | 
| flickerstreak@161 | 222   end | 
| flickerstreak@161 | 223 ]] | 
| flickerstreak@161 | 224 | 
| flickerstreak@161 | 225 | 
| flickerstreak@161 | 226 -- | 
| flickerstreak@161 | 227 -- The Blizzard totem bar textures are all actually one big texture, | 
| flickerstreak@161 | 228 -- with texcoord offsets | 
| flickerstreak@161 | 229 -- | 
| flickerstreak@161 | 230 local TOTEM_TEXTURE = "Interface\\Buttons\\UI-TotemBar" | 
| flickerstreak@161 | 231 local FLYOUT_UP_BUTTON_TCOORDS      = { 99/128, 127/128, 84/256, 102/256 } | 
| flickerstreak@161 | 232 local FLYOUT_UP_BUTTON_HL_TCOORDS   = { 72/128,  92/128, 88/256,  98/256 } | 
| flickerstreak@161 | 233 local FLYOUT_DOWN_BUTTON_TCOORDS    = { 99/128, 127/128, 65/256,  83/256 } | 
| flickerstreak@161 | 234 local FLYOUT_DOWN_BUTTON_HL_TCOORDS = { 72/128,  92/128, 69/256,  79/256 } | 
| flickerstreak@161 | 235 local EMPTY_SLOT_TCOORDS            = { 66/128,  96/128,  3/256,  33/256 } | 
| flickerstreak@161 | 236 | 
| flickerstreak@161 | 237 local eventList = { | 
| flickerstreak@161 | 238   "ACTIONBAR_SLOT_CHANGED", | 
| flickerstreak@161 | 239   "ACTIONBAR_UPDATE_STATE", | 
| flickerstreak@161 | 240   "ACTIONBAR_UPDATE_USABLE", | 
| flickerstreak@161 | 241   "ACTIONBAR_UPDATE_COOLDOWN", | 
| flickerstreak@162 | 242   "UPDATE_BINDINGS", | 
| flickerstreak@161 | 243   "UPDATE_MULTI_CAST_ACTIONBAR", | 
| flickerstreak@161 | 244 } | 
| flickerstreak@161 | 245 | 
| flickerstreak@161 | 246 -- | 
| flickerstreak@161 | 247 -- MultiCast Button class | 
| flickerstreak@161 | 248 -- Inherits implementation methods from Action button class, but circumvents the constructor | 
| flickerstreak@161 | 249 -- and redefines/removes some methods. | 
| flickerstreak@161 | 250 -- | 
| flickerstreak@161 | 251 local Super = ReAction.Button | 
| flickerstreak@161 | 252 local Action = ReAction.Button.Action | 
| flickerstreak@161 | 253 local MultiCast = setmetatable( { }, { __index = Action } ) | 
| flickerstreak@161 | 254 ReAction.Button.MultiCast = MultiCast | 
| flickerstreak@161 | 255 | 
| flickerstreak@161 | 256 function MultiCast:New( idx, btnConfig, bar ) | 
| flickerstreak@167 | 257   local maxIndex = bar.nTotemSlots or 0 | 
| flickerstreak@167 | 258   if bar.summonSlot then | 
| flickerstreak@167 | 259     maxIndex = maxIndex + 1 | 
| flickerstreak@167 | 260   end | 
| flickerstreak@167 | 261   if bar.recallSlot then | 
| flickerstreak@167 | 262     maxIndex = maxIndex + 1 | 
| flickerstreak@161 | 263   end | 
| flickerstreak@161 | 264 | 
| flickerstreak@167 | 265   if not bar.hasMulticast or idx > maxIndex then | 
| flickerstreak@163 | 266     return false | 
| flickerstreak@163 | 267   end | 
| flickerstreak@163 | 268 | 
| flickerstreak@167 | 269   if idx < 1 then | 
| flickerstreak@167 | 270     error("invalid index") | 
| flickerstreak@167 | 271   end | 
| flickerstreak@167 | 272 | 
| flickerstreak@167 | 273   local name = format("ReAction_%s_Totem_%d",bar:GetName(),idx) | 
| flickerstreak@161 | 274 | 
| flickerstreak@161 | 275   self = Super.New(self, name, btnConfig, bar, idx, "SecureActionButtonTemplate, ActionButtonTemplate" ) | 
| flickerstreak@161 | 276 | 
| flickerstreak@161 | 277   local barFrame = bar:GetFrame() | 
| flickerstreak@161 | 278   local f = self:GetFrame() | 
| flickerstreak@161 | 279 | 
| flickerstreak@161 | 280   -- attributes | 
| flickerstreak@167 | 281   local page = (idx == bar.recallSlot) and 1 or bar:GetConfig().lastSummon or 1 | 
| flickerstreak@167 | 282   if idx == bar.recallSlot or idx == bar.summonSlot then | 
| flickerstreak@161 | 283     f:SetAttribute("type","spell") | 
| flickerstreak@167 | 284     local spells = (idx == bar.summonSlot) and TOTEM_MULTI_CAST_SUMMON_SPELLS or TOTEM_MULTI_CAST_RECALL_SPELLS | 
| flickerstreak@163 | 285     f:SetAttribute("spell",spells[page]) | 
| flickerstreak@161 | 286     for i, spell in ipairs(spells) do | 
| flickerstreak@161 | 287       if spell and IsSpellKnown(spell) then | 
| flickerstreak@167 | 288         print("setting attribute spell-page"..i.." to "..spell) | 
| flickerstreak@161 | 289         f:SetAttribute("spell-page"..i, spell) | 
| flickerstreak@161 | 290       end | 
| flickerstreak@161 | 291     end | 
| flickerstreak@161 | 292   else | 
| flickerstreak@167 | 293     local offset = bar.summonSlot and 1 or 0 | 
| flickerstreak@167 | 294     local slot = SHAMAN_TOTEM_PRIORITIES[idx - offset] | 
| flickerstreak@167 | 295     local baseAction = barFrame:GetAttribute("baseActionID") + slot | 
| flickerstreak@161 | 296     f:SetAttribute("type","action") | 
| flickerstreak@163 | 297     f:SetAttribute("action", baseAction + (page - 1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE) | 
| flickerstreak@161 | 298     for i = 1, NUM_MULTI_CAST_PAGES do | 
| flickerstreak@161 | 299       f:SetAttribute("action-page"..i, baseAction + (i-1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE) | 
| flickerstreak@161 | 300     end | 
| flickerstreak@161 | 301   end | 
| flickerstreak@161 | 302   f:SetAttribute("bar-idx",idx) | 
| flickerstreak@161 | 303 | 
| flickerstreak@161 | 304   -- non secure scripts | 
| flickerstreak@161 | 305   f:SetScript("OnEvent", function(frame, ...) self:OnEvent(...) end) | 
| flickerstreak@161 | 306   f:SetScript("OnEnter", function(frame) self:OnEnter() end) | 
| flickerstreak@161 | 307   f:SetScript("OnLeave", function(frame) self:OnLeave() end) | 
| flickerstreak@161 | 308   f:SetScript("OnAttributeChanged", function(frame, attr, value) self:OnAttributeChanged(attr, value) end) | 
| flickerstreak@161 | 309   f:SetScript("PostClick", function(frame, ...) self:PostClick(...) end) | 
| flickerstreak@161 | 310 | 
| flickerstreak@161 | 311   -- secure handlers | 
| flickerstreak@167 | 312   if idx ~= bar.recallSlot then | 
| flickerstreak@161 | 313     f:SetAttribute("_childupdate",_childupdate) | 
| flickerstreak@161 | 314   end | 
| flickerstreak@161 | 315   barFrame:WrapScript(f, "OnEnter", _onEnter) | 
| flickerstreak@161 | 316 | 
| flickerstreak@161 | 317   -- event registration | 
| flickerstreak@161 | 318   f:EnableMouse(true) | 
| flickerstreak@161 | 319   f:RegisterForClicks("AnyUp") | 
| flickerstreak@161 | 320   for _, evt in pairs(eventList) do | 
| flickerstreak@161 | 321     f:RegisterEvent(evt) | 
| flickerstreak@161 | 322   end | 
| flickerstreak@161 | 323 | 
| flickerstreak@162 | 324   -- Set up a proxy for the icon texture for use with ButtonFacade | 
| flickerstreak@162 | 325   self.frames.icon.SetTexCoordRaw = self.frames.icon.SetTexCoord | 
| flickerstreak@162 | 326   self.frames.icon.SetTexCoord = function( tx, ... ) | 
| flickerstreak@162 | 327     if self:GetIconTexture() == TOTEM_TEXTURE then | 
| flickerstreak@162 | 328       tx:SetTexCoordRaw(unpack(EMPTY_SLOT_TCOORDS)) | 
| flickerstreak@162 | 329     else | 
| flickerstreak@162 | 330       tx:SetTexCoordRaw(...) | 
| flickerstreak@162 | 331     end | 
| flickerstreak@162 | 332   end | 
| flickerstreak@162 | 333 | 
| flickerstreak@161 | 334   -- attach to skinner | 
| flickerstreak@161 | 335   bar:SkinButton(self) | 
| flickerstreak@161 | 336 | 
| flickerstreak@161 | 337   f:Show() | 
| flickerstreak@161 | 338 | 
| flickerstreak@161 | 339   -- open arrow | 
| flickerstreak@167 | 340   if idx ~= bar.recallSlot then | 
| flickerstreak@161 | 341     local arrow = CreateFrame("Button", nil, f, "SecureFrameTemplate") | 
| flickerstreak@161 | 342     arrow:SetWidth(28) | 
| flickerstreak@161 | 343     arrow:SetHeight(12) | 
| flickerstreak@161 | 344     arrow:SetPoint("BOTTOM",self:GetFrame(),"TOP",0,0) -- TODO: better anchoring | 
| flickerstreak@161 | 345     arrow:SetNormalTexture(TOTEM_TEXTURE) | 
| flickerstreak@161 | 346     arrow:GetNormalTexture():SetTexCoord( unpack(FLYOUT_UP_BUTTON_TCOORDS) ) | 
| flickerstreak@161 | 347     arrow:SetHighlightTexture(TOTEM_TEXTURE) | 
| flickerstreak@161 | 348     arrow:GetHighlightTexture():SetTexCoord( unpack(FLYOUT_UP_BUTTON_HL_TCOORDS) ) | 
| flickerstreak@161 | 349     arrow:SetAttribute("bar-idx",idx) | 
| flickerstreak@161 | 350     arrow:Hide() | 
| flickerstreak@161 | 351     barFrame:WrapScript(arrow, "OnClick", _arrow_openFlyout) | 
| flickerstreak@167 | 352     barFrame:SetFrameRef("arrow-"..idx,arrow) | 
| flickerstreak@161 | 353   end | 
| flickerstreak@161 | 354 | 
| flickerstreak@161 | 355   self:Refresh() | 
| flickerstreak@161 | 356 | 
| flickerstreak@161 | 357   return self | 
| flickerstreak@161 | 358 end | 
| flickerstreak@161 | 359 | 
| flickerstreak@161 | 360 function MultiCast:Destroy() | 
| flickerstreak@161 | 361   local barFrame = self:GetFrame() | 
| flickerstreak@161 | 362   Super.Destroy(self) | 
| flickerstreak@161 | 363 end | 
| flickerstreak@161 | 364 | 
| flickerstreak@161 | 365 function MultiCast:Refresh() | 
| flickerstreak@161 | 366   Super.Refresh(self) | 
| flickerstreak@161 | 367   self:UpdateAction() | 
| flickerstreak@161 | 368 end | 
| flickerstreak@161 | 369 | 
| flickerstreak@161 | 370 function MultiCast:ShowGrid( show ) | 
| flickerstreak@161 | 371 end | 
| flickerstreak@161 | 372 | 
| flickerstreak@161 | 373 function MultiCast:ShowGridTemp( show ) | 
| flickerstreak@161 | 374 end | 
| flickerstreak@161 | 375 | 
| flickerstreak@161 | 376 function MultiCast:AcquireActionID() | 
| flickerstreak@161 | 377 end | 
| flickerstreak@161 | 378 | 
| flickerstreak@161 | 379 function MultiCast:ReleaseActionID() | 
| flickerstreak@161 | 380 end | 
| flickerstreak@161 | 381 | 
| flickerstreak@161 | 382 function MultiCast:UpdateShowGrid() | 
| flickerstreak@161 | 383 end | 
| flickerstreak@161 | 384 | 
| flickerstreak@161 | 385 function MultiCast:UpdateBorder() | 
| flickerstreak@161 | 386 end | 
| flickerstreak@161 | 387 | 
| flickerstreak@161 | 388 function MultiCast:UpdateMacroText() | 
| flickerstreak@161 | 389 end | 
| flickerstreak@161 | 390 | 
| flickerstreak@161 | 391 function MultiCast:UpdateCount() | 
| flickerstreak@161 | 392 end | 
| flickerstreak@161 | 393 | 
| flickerstreak@161 | 394 function MultiCast:UpdateCheckedState() | 
| flickerstreak@161 | 395   local action = self:GetActionID() | 
| flickerstreak@161 | 396   if action and IsCurrentAction(action) then | 
| flickerstreak@161 | 397     self:GetFrame():SetChecked(1) | 
| flickerstreak@161 | 398   else | 
| flickerstreak@161 | 399     self:GetFrame():SetChecked(0) | 
| flickerstreak@161 | 400   end | 
| flickerstreak@161 | 401 end | 
| flickerstreak@161 | 402 | 
| flickerstreak@161 | 403 function MultiCast:RefreshHasActionAttributes() | 
| flickerstreak@161 | 404 end | 
| flickerstreak@161 | 405 | 
| flickerstreak@161 | 406 function MultiCast:UpdateFlash() | 
| flickerstreak@161 | 407 end | 
| flickerstreak@161 | 408 | 
| flickerstreak@161 | 409 function MultiCast:GetIconTexture() | 
| flickerstreak@161 | 410   local tx | 
| flickerstreak@161 | 411   if self.spellID then | 
| flickerstreak@161 | 412     tx = GetSpellTexture(GetSpellInfo(self.spellID)) | 
| flickerstreak@161 | 413   elseif self.actionID then | 
| flickerstreak@161 | 414     tx = GetActionTexture(self.actionID) | 
| flickerstreak@161 | 415   end | 
| flickerstreak@161 | 416   if tx then | 
| flickerstreak@161 | 417     return tx | 
| flickerstreak@161 | 418   else | 
| flickerstreak@161 | 419     return TOTEM_TEXTURE, unpack(EMPTY_SLOT_TCOORDS) | 
| flickerstreak@161 | 420   end | 
| flickerstreak@161 | 421 end | 
| flickerstreak@161 | 422 | 
| flickerstreak@161 | 423 function MultiCast:UpdateAction() | 
| flickerstreak@161 | 424   local action = self:GetActionID() | 
| flickerstreak@161 | 425   if action then | 
| flickerstreak@161 | 426     if action ~= self.actionID then | 
| flickerstreak@161 | 427       self.actionID = action | 
| flickerstreak@161 | 428       self:UpdateAll() | 
| flickerstreak@161 | 429     end | 
| flickerstreak@161 | 430   else | 
| flickerstreak@161 | 431     local spellID = self:GetSpellID() | 
| flickerstreak@161 | 432     if spellID ~= self.spellID then | 
| flickerstreak@161 | 433       self.spellID = spellID | 
| flickerstreak@161 | 434       self:UpdateAll() | 
| flickerstreak@161 | 435     end | 
| flickerstreak@161 | 436   end | 
| flickerstreak@161 | 437 end | 
| flickerstreak@161 | 438 | 
| flickerstreak@161 | 439 function MultiCast:GetActionID(page) | 
| flickerstreak@161 | 440   return self:GetFrame():GetAttribute("action") | 
| flickerstreak@161 | 441 end | 
| flickerstreak@161 | 442 | 
| flickerstreak@161 | 443 function MultiCast:GetSpellID(page) | 
| flickerstreak@161 | 444   return self:GetFrame():GetAttribute("spell") | 
| flickerstreak@161 | 445 end | 
| flickerstreak@161 | 446 | 
| flickerstreak@161 | 447 function MultiCast:SetActionID( id  ) | 
| flickerstreak@161 | 448   error("Can not set action ID of multicast buttons") | 
| flickerstreak@161 | 449 end | 
| flickerstreak@161 | 450 | 
| flickerstreak@161 | 451 function MultiCast:SetTooltip() | 
| flickerstreak@161 | 452   local barFrame = self:GetFrame() | 
| flickerstreak@161 | 453   if GetCVar("UberTooltips") == "1" then | 
| flickerstreak@161 | 454     GameTooltip_SetDefaultAnchor(GameTooltip, barFrame) | 
| flickerstreak@161 | 455   else | 
| flickerstreak@161 | 456     GameTooltip:SetOwner(barFrame) | 
| flickerstreak@161 | 457   end | 
| flickerstreak@161 | 458   if self.spellID then | 
| flickerstreak@161 | 459     GameTooltip:SetSpellByID(self.spellID,false,true) | 
| flickerstreak@161 | 460   elseif self.actionID then | 
| flickerstreak@161 | 461     GameTooltip:SetAction(self.actionID) | 
| flickerstreak@161 | 462   end | 
| flickerstreak@161 | 463 end | 
| flickerstreak@161 | 464 | 
| flickerstreak@161 | 465 function MultiCast:GetUsable() | 
| flickerstreak@161 | 466   if self.spellID then | 
| flickerstreak@161 | 467     return IsUsableSpell((GetSpellInfo(self.spellID))) | 
| flickerstreak@161 | 468   elseif self.actionID then | 
| flickerstreak@161 | 469     return IsUsableAction(self.actionID) | 
| flickerstreak@161 | 470   end | 
| flickerstreak@161 | 471 end | 
| flickerstreak@161 | 472 | 
| flickerstreak@161 | 473 function MultiCast:GetInRange() | 
| flickerstreak@161 | 474   if self.spellID then | 
| flickerstreak@161 | 475     return IsSpellInRange((GetSpellInfo(self.spellID))) == 0 | 
| flickerstreak@161 | 476   elseif self.actionID then | 
| flickerstreak@161 | 477     return IsActionInRange(self.actionID) == 0 | 
| flickerstreak@161 | 478   end | 
| flickerstreak@161 | 479 end | 
| flickerstreak@161 | 480 | 
| flickerstreak@161 | 481 function MultiCast:GetCooldown() | 
| flickerstreak@161 | 482   if self.spellID then | 
| flickerstreak@161 | 483     return GetSpellCooldown((GetSpellInfo(self.spellID))) | 
| flickerstreak@161 | 484   elseif self.actionID then | 
| flickerstreak@161 | 485     return GetActionCooldown(self.actionID) | 
| flickerstreak@161 | 486   else | 
| flickerstreak@161 | 487     return 0, 0, 0 | 
| flickerstreak@161 | 488   end | 
| flickerstreak@161 | 489 end | 
| flickerstreak@161 | 490 | 
| flickerstreak@161 | 491 function MultiCast:UPDATE_MULTI_CAST_ACTIONBAR() | 
| flickerstreak@161 | 492   self:UpdateAll() | 
| flickerstreak@161 | 493 end | 
| flickerstreak@161 | 494 | 
| flickerstreak@161 | 495 | 
| flickerstreak@161 | 496 -- | 
| flickerstreak@161 | 497 -- flyout setup | 
| flickerstreak@161 | 498 -- | 
| flickerstreak@167 | 499 local function ShowFlyoutTooltip(frame) | 
| flickerstreak@161 | 500   if GetCVar("UberTooltips") == "1" then | 
| flickerstreak@167 | 501     GameTooltip_SetDefaultAnchor(GameTooltip, frame) | 
| flickerstreak@161 | 502   else | 
| flickerstreak@167 | 503     GameTooltip:SetOwner(frame) | 
| flickerstreak@161 | 504   end | 
| flickerstreak@167 | 505   local spell = frame:GetAttribute("spell") | 
| flickerstreak@167 | 506   if spell == nil or spell == 0 then | 
| flickerstreak@161 | 507     GameTooltip:SetText(MULTI_CAST_TOOLTIP_NO_TOTEM, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) | 
| flickerstreak@161 | 508   else | 
| flickerstreak@167 | 509     GameTooltip:SetSpellByID(spell,false,true) | 
| flickerstreak@161 | 510   end | 
| flickerstreak@161 | 511 end | 
| flickerstreak@161 | 512 | 
| flickerstreak@161 | 513 local function HideFlyoutTooltip() | 
| flickerstreak@161 | 514   GameTooltip:Hide() | 
| flickerstreak@161 | 515 end | 
| flickerstreak@161 | 516 | 
| flickerstreak@161 | 517 local function UpdateFlyoutIcon(frame) | 
| flickerstreak@161 | 518   local spellID = frame:GetAttribute("spell") | 
| flickerstreak@167 | 519   if spellID == 0 or spellID == nil then | 
| flickerstreak@161 | 520     frame.icon:SetTexture(TOTEM_TEXTURE) | 
| flickerstreak@161 | 521     frame.icon:SetTexCoord( unpack(EMPTY_SLOT_TCOORDS) ) | 
| flickerstreak@167 | 522   else | 
| flickerstreak@161 | 523     frame.icon:SetTexture(GetSpellTexture(GetSpellInfo(spellID))) | 
| flickerstreak@161 | 524     frame.icon:SetTexCoord(0,1,0,1) | 
| flickerstreak@161 | 525   end | 
| flickerstreak@161 | 526 end | 
| flickerstreak@161 | 527 | 
| flickerstreak@161 | 528 function MultiCast.SetupBarHeader( bar ) -- call this as a static method | 
| flickerstreak@167 | 529   local slot = 0 | 
| flickerstreak@167 | 530   local nTotemSlots = 0 | 
| flickerstreak@167 | 531   local summonSlot = nil | 
| flickerstreak@167 | 532   local recallSlot = nil | 
| flickerstreak@161 | 533 | 
| flickerstreak@167 | 534   -- figure out the capabilities of the character | 
| flickerstreak@167 | 535 	for i, spell in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do | 
| flickerstreak@161 | 536     if spell and IsSpellKnown(spell) then | 
| flickerstreak@167 | 537       slot = 1 | 
| flickerstreak@167 | 538       summonSlot = 1 | 
| flickerstreak@161 | 539     end | 
| flickerstreak@161 | 540 	end | 
| flickerstreak@161 | 541 | 
| flickerstreak@167 | 542   for i = 1, NUM_MULTI_CAST_BUTTONS_PER_PAGE do | 
| flickerstreak@167 | 543 		local totem = SHAMAN_TOTEM_PRIORITIES[i]; | 
| flickerstreak@167 | 544 		if GetTotemInfo(totem) and GetMultiCastTotemSpells(totem) then | 
| flickerstreak@167 | 545       nTotemSlots = nTotemSlots + 1 | 
| flickerstreak@167 | 546       slot = slot + 1 | 
| flickerstreak@167 | 547     end | 
| flickerstreak@167 | 548   end | 
| flickerstreak@167 | 549 | 
| flickerstreak@167 | 550   slot = slot + 1 | 
| flickerstreak@167 | 551 	for i, spell in ipairs(TOTEM_MULTI_CAST_RECALL_SPELLS) do | 
| flickerstreak@161 | 552     if spell and IsSpellKnown(spell) then | 
| flickerstreak@167 | 553       recallSlot = slot | 
| flickerstreak@161 | 554     end | 
| flickerstreak@161 | 555 	end | 
| flickerstreak@161 | 556 | 
| flickerstreak@167 | 557   if nTotemSlots == 0 then | 
| flickerstreak@167 | 558     bar.hasMulticast = false -- no multicast capability | 
| flickerstreak@163 | 559     return | 
| flickerstreak@161 | 560   end | 
| flickerstreak@161 | 561 | 
| flickerstreak@167 | 562   bar.hasMulticast = true | 
| flickerstreak@167 | 563   bar.summonSlot   = summonSlot | 
| flickerstreak@167 | 564   bar.recallSlot   = recallSlot | 
| flickerstreak@167 | 565   bar.nTotemSlots  = nTotemSlots | 
| flickerstreak@161 | 566 | 
| flickerstreak@161 | 567 | 
| flickerstreak@167 | 568   local f = bar:GetFrame() | 
| flickerstreak@161 | 569 | 
| flickerstreak@161 | 570   -- init bar secure environment | 
| flickerstreak@167 | 571   f:SetAttribute("lastSummon", bar:GetConfig().lastSummon) | 
| flickerstreak@167 | 572   f:SetAttribute("summonSlot", summonSlot) | 
| flickerstreak@167 | 573   f:SetAttribute("recallSlot", recallSlot) | 
| flickerstreak@167 | 574   f:SetAttribute("slotsPerPage", NUM_MULTI_CAST_BUTTONS_PER_PAGE) | 
| flickerstreak@167 | 575   f:SetAttribute("baseActionID", (NUM_ACTIONBAR_PAGES + GetMultiCastBarOffset() - 1)*NUM_ACTIONBAR_BUTTONS) | 
| flickerstreak@167 | 576   for i, p in ipairs(SHAMAN_TOTEM_PRIORITIES) do | 
| flickerstreak@167 | 577     f:SetAttribute("TOTEM_PRIORITY_"..i,p) | 
| flickerstreak@167 | 578   end | 
| flickerstreak@167 | 579   f:SetAttribute("_onstate-multispellpage", _onstate_multispellpage) | 
| flickerstreak@161 | 580 | 
| flickerstreak@167 | 581   function f:UpdateLastSummon(value) | 
| flickerstreak@163 | 582     bar:GetConfig().lastSummon = value | 
| flickerstreak@163 | 583   end | 
| flickerstreak@163 | 584 | 
| flickerstreak@161 | 585   -- create flyout container frame and close arrow | 
| flickerstreak@161 | 586   local flyout = bar._flyoutFrame | 
| flickerstreak@161 | 587   if not flyout then | 
| flickerstreak@167 | 588     flyout = CreateFrame("Frame", nil, f, "SecureFrameTemplate") | 
| flickerstreak@161 | 589     bar._flyoutFrame = flyout | 
| flickerstreak@167 | 590     f:SetFrameRef("flyout",flyout) | 
| flickerstreak@161 | 591     flyout.buttons = { } | 
| flickerstreak@161 | 592     flyout:Hide() | 
| flickerstreak@161 | 593     flyout:SetWidth(24) | 
| flickerstreak@161 | 594     flyout:SetHeight(1) | 
| flickerstreak@167 | 595     flyout:SetPoint("BOTTOM",f,"TOP",0,0) | 
| flickerstreak@161 | 596 | 
| flickerstreak@161 | 597     local close = CreateFrame("Button", nil, flyout, "SecureFrameTemplate") | 
| flickerstreak@161 | 598     close:SetWidth(28) | 
| flickerstreak@161 | 599     close:SetHeight(12) | 
| flickerstreak@161 | 600     close:SetPoint("TOP") | 
| flickerstreak@161 | 601     close:SetNormalTexture(TOTEM_TEXTURE) | 
| flickerstreak@161 | 602     close:GetNormalTexture():SetTexCoord( unpack(FLYOUT_DOWN_BUTTON_TCOORDS) ) | 
| flickerstreak@161 | 603     close:SetHighlightTexture(TOTEM_TEXTURE) | 
| flickerstreak@161 | 604     close:GetHighlightTexture():SetTexCoord( unpack(FLYOUT_DOWN_BUTTON_HL_TCOORDS) ) | 
| flickerstreak@167 | 605     f:SetFrameRef("close",close) | 
| flickerstreak@167 | 606     f:WrapScript(close, "OnClick", _closeFlyout) | 
| flickerstreak@161 | 607 | 
| flickerstreak@167 | 608     -- create flyout buttons | 
| flickerstreak@167 | 609     for i = 1, 10 do -- maximum 9 spells + 1 empty slot | 
| flickerstreak@167 | 610       local b = CreateFrame("Button",nil,flyout,"SecureActionButtonTemplate") | 
| flickerstreak@167 | 611       b:SetWidth(24) | 
| flickerstreak@167 | 612       b:SetHeight(24) | 
| flickerstreak@167 | 613       local prev = flyout.buttons[i-1] | 
| flickerstreak@167 | 614       b:SetPoint("BOTTOM", prev or flyout, prev and "TOP" or "BOTTOM", 0, 3) -- TODO: better anchoring | 
| flickerstreak@167 | 615       b.icon = b:CreateTexture("BACKGROUND") | 
| flickerstreak@167 | 616       b.icon:SetAllPoints() | 
| flickerstreak@167 | 617       b.icon:Show() | 
| flickerstreak@167 | 618       b:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square") | 
| flickerstreak@167 | 619       b:GetHighlightTexture():SetBlendMode("ADD") | 
| flickerstreak@167 | 620       b:RegisterForClicks("AnyUp") | 
| flickerstreak@167 | 621       b:SetScript("OnShow",UpdateFlyoutIcon) | 
| flickerstreak@167 | 622       b:SetScript("OnEnter",ShowFlyoutTooltip) | 
| flickerstreak@167 | 623       b:SetScript("OnLeave",HideFlyoutTooltip) | 
| flickerstreak@167 | 624       b:SetAttribute("index",i) | 
| flickerstreak@167 | 625       f:SetAttribute("flyout-child-idx",i) | 
| flickerstreak@167 | 626       f:SetFrameRef("flyout-child",b) | 
| flickerstreak@167 | 627       f:Execute([[ | 
| flickerstreak@167 | 628           flyoutChildren = flyoutChildren or newtable() | 
| flickerstreak@167 | 629           flyoutChildren[self:GetAttribute("flyout-child-idx")] = self:GetFrameRef("flyout-child") | 
| flickerstreak@161 | 630         ]]) | 
| flickerstreak@167 | 631       f:WrapScript(b, "OnClick", _flyout_child_preClick, _flyout_child_postClick) | 
| flickerstreak@167 | 632       b:Show() | 
| flickerstreak@167 | 633       flyout.buttons[i] = b | 
| flickerstreak@161 | 634     end | 
| flickerstreak@161 | 635   end | 
| flickerstreak@161 | 636 | 
| flickerstreak@167 | 637   f:Execute(_bar_init) | 
| flickerstreak@161 | 638 end | 
| flickerstreak@161 | 639 |