changeset 167:eab7e7642dd6

Rewrote MultiCastButton to show prior to learning a summon ability, cleaned up implementation. NOTE: a typo fix will invalidate any existing keybindings to totem buttons.
author Flick <flickerstreak@gmail.com>
date Tue, 19 Oct 2010 16:49:40 +0000
parents 8241be11dcc0
children 07c76dbc0236
files classes/MultiCastButton.lua
diffstat 1 files changed, 150 insertions(+), 153 deletions(-) [+]
line wrap: on
line diff
--- a/classes/MultiCastButton.lua	Sat Oct 16 21:53:57 2010 +0000
+++ b/classes/MultiCastButton.lua	Tue Oct 19 16:49:40 2010 +0000
@@ -46,9 +46,8 @@
                so that's action ID 132-144.
 
     - spell1, spell2, spell3, ... = GetMultiCastTotemSpells(slot)
-        returns spellIDs for all totems that fit that slot. 
-        Note this is available in the secure environment, but because IsSpellKnown() is not,
-        it makes it pretty much useless.
+        returns spellIDs for all known totems that fit that slot. This function is available in
+        the secure environment.
 
   Blizzard textures:
     All the textures for the multicast bar (arrows, empty-slot icons, etc) are part of a single
@@ -71,8 +70,6 @@
     - Each button (except recall) has an arrow button which appears on mouseover and when clicked
       opens the flyout via a wrapped OnClick handler. When the flyout is open, the arrow does not
       appear.
-      TODO: add an alt-button ("SHOWMULTICASTFLYOUT") statemachine to the bar to listen for alt key
-      presses and open/close the bar. Tapping the alt key toggles the flyout.
     
     - A single flyout with N+1 (1 slot is to select no totem for the set) flyout-buttons is a child
       of the bar. Each time the flyout panel is opened, the individual  buttons grab their corresponding
@@ -84,9 +81,10 @@
       changes dynamically because each slot has a different number of items in the list).
 
     - Multicast sets are not stances, there's no need (or ability) to handle swapping sets if one of 
-      the summon spells is cast from elsewhere. Additionally, in the default UI Call of the Elements 
-      always appears as the active summon when the UI is loaded, which is bad design that could be improved
-      upon. TODO: Store state in a config variable and restore it at load time.
+      the summon spells is cast from elsewhere.
+
+    - The default UI has Call of the Elements always selected on UI load. This module remembers the last
+      selected one and restores it.
 
 
 ]]--
@@ -101,20 +99,25 @@
 [[
   -- set up some globals in the secure environment
   flyout               = self:GetFrameRef("flyout")
-  flyoutChildren       = newtable()
-  nMultiCastSlots      = self:GetAttribute("nMultiCastSlots")
+  flyoutSlot           = nil
+  summonSlot           = self:GetAttribute("summonSlot")
+  recallSlot           = self:GetAttribute("recallSlot")
   baseActionID         = self:GetAttribute("baseActionID")
-  currentMultiCastPage = currentMultiCastPage or self:GetAttribute("lastSummon") or 1
-  multiCastSpellList   = newtable()
-  for i = 1, nMultiCastSlots do
-    tinsert(multiCastSpellList, newtable())
+  slotsPerPage         = self:GetAttribute("slotsPerPage")
+  currentPage          = currentPage or self:GetAttribute("lastSummon") or 1
+
+  totemIDsBySlot = newtable()
+  for i = 1, slotsPerPage do
+    totemIDsBySlot[i] = self:GetAttribute("TOTEM_PRIORITY_"..i)
   end
+
+  summonSpells = summonSpells or newtable() -- set up in bar:SetupBarHeader()
 ]]
 
 local _onstate_multispellpage = -- function(self, stateid, newstate)
 [[
-  currentMultiCastPage = tonumber(newstate)
-  control:CallMethod("UpdateLastSummon",currentMultiCastPage)
+  currentPage = tonumber(newstate)
+  control:CallMethod("UpdateLastSummon",currentPage)
   control:ChildUpdate()
 ]]
 
@@ -122,11 +125,8 @@
 -- buttons
 local _childupdate = -- function(self, snippetid, message)
 [[
-  if self:GetAttribute("type") == "spell" then
-    self:SetAttribute("spell", self:GetAttribute("spell-page"..currentMultiCastPage))
-  elseif self:GetAttribute("type") == "action" then
-    self:SetAttribute("action", self:GetAttribute("action-page"..currentMultiCastPage))
-  end
+  local t = self:GetAttribute("type")
+  self:SetAttribute(t, self:GetAttribute(t.."-page"..currentPage))
 ]]
 
 local _onEnter = -- function(self)
@@ -134,24 +134,12 @@
   -- unless you re-anchor the frame prior to calling it.
   -- Even then, it's still not terribly reliable.
 [[
-  local idx = self:GetAttribute("bar-idx")
-  if not (flyout:IsVisible() and flyoutIdx == idx) then
-    local arrow = owner:GetFrameRef("arrow-"..idx)
-    if arrow and not arrow:IsShown() then
-      arrow:ClearAllPoints()
-      arrow:SetPoint("BOTTOM",self,"TOP",0,0) -- TODO: better anchoring
-      arrow:Show()
-      arrow:RegisterAutoHide(0)
-      arrow:AddToAutoHide(self)
-    end
-  end
-]]
-
-local _onLeave = -- function(self)
-  -- to increase reliability (somewhat), re-register it for hide on leave
-[[
-  local arrow = owner:GetFrameRef("arrow-"..self:GetAttribute("bar-idx"))
-  if arrow then
+  local slot = self:GetAttribute("bar-idx")
+  local arrow = owner:GetFrameRef("arrow-"..slot)
+  if arrow and not arrow:IsShown() and not (flyout:IsVisible() and flyoutSlot == slot) then
+    arrow:ClearAllPoints()
+    arrow:SetPoint("BOTTOM",self,"TOP",0,0)
+    arrow:Show()
     arrow:RegisterAutoHide(0)
     arrow:AddToAutoHide(self)
   end
@@ -161,23 +149,32 @@
 -- flyout arrow
 local _arrow_openFlyout = -- function(self)
 [[
-  local currentMultiCastSlot = self:GetAttribute("bar-idx")
-  local lastButton, lastIdx
-  for idx, b in ipairs(flyoutChildren) do
-    b:Hide() -- force the OnShow handler to run later
-    local spellID = multiCastSpellList[currentMultiCastSlot][idx]
-    if spellID then
-      b:SetAttribute("spell",spellID) -- does passing 0 work for no-totem? Do we have to convert to nil?
-      if currentMultiCastSlot == 1 then
+  local slot = self:GetAttribute("bar-idx")
+  local lastButton, lastPage
+  for page, b in ipairs(flyoutChildren) do
+    b:Hide()
+    if slot == summonSlot then
+      local spellID = self:GetParent():GetAttribute("spell-page"..page)
+      print("got spell-page"..tostring(page).." = ".. tostring(spellID))
+      if spellID then
         b:SetAttribute("type","changePage")
-      else
+        b:SetAttribute("spell",spellID)
+        b:Show()
+        lastButton = b
+        lastPage = page
+      end
+    else
+      local offset = summonSlot or 0
+      local totemID = totemIDsBySlot[slot - offset]
+      local spells = newtable( 0, GetMultiCastTotemSpells(totemID) )
+      if spells[page] then
         b:SetAttribute("type","multispell")
-        local totemID = owner:GetAttribute("TOTEM_PRIORITY_"..(currentMultiCastSlot - 1))
-        b:SetAttribute("action", baseActionID + (currentMultiCastPage - 1)*(nMultiCastSlots-2) + totemID)
+        b:SetAttribute("action", baseActionID + (currentPage - 1)*slotsPerPage + totemID)
+        b:SetAttribute("spell", spells[page])
+        b:Show()
+        lastButton = b
+        lastPage = page
       end
-      b:Show()
-      lastButton = b
-      lastIdx = idx
     end
   end
 
@@ -190,13 +187,13 @@
 
   flyout:ClearAllPoints()
   flyout:SetPoint("BOTTOM",self,"BOTTOM",0,0)  -- TODO: better anchoring
-  if lastIdx then
-    flyout:SetHeight(lastIdx * 27 + (close and close:GetHeight() or 0))
+  if lastPage then
+    flyout:SetHeight(lastPage * 27 + (close and close:GetHeight() or 0))
   end
   flyout:Show()
   flyout:RegisterAutoHide(1) -- TODO: configurable
   flyout:AddToAutoHide(owner)
-  flyoutIdx = currentMultiCastSlot
+  flyoutSlot = slot
   self:Hide()
 ]]
 
@@ -258,15 +255,23 @@
 ReAction.Button.MultiCast = MultiCast
 
 function MultiCast:New( idx, btnConfig, bar )
-  if idx < 1 or idx > NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2 then
-    error("Multicast button index out of range")
+  local maxIndex = bar.nTotemSlots or 0
+  if bar.summonSlot then
+    maxIndex = maxIndex + 1
+  end
+  if bar.recallSlot then
+    maxIndex = maxIndex + 1
   end
 
-  if idx > bar.nMultiCastSlots then
+  if not bar.hasMulticast or idx > maxIndex then
     return false
   end
 
-  local name = format("ReAction_%s_Action_%d",bar:GetName(),idx)
+  if idx < 1 then
+    error("invalid index")
+  end
+
+  local name = format("ReAction_%s_Totem_%d",bar:GetName(),idx)
  
   self = Super.New(self, name, btnConfig, bar, idx, "SecureActionButtonTemplate, ActionButtonTemplate" )
 
@@ -274,18 +279,21 @@
   local f = self:GetFrame()
 
   -- attributes
-  local page = (idx == NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2) and 1 or (bar:GetConfig().lastSummon or 1)
-  if idx == 1 or idx == NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2 then
+  local page = (idx == bar.recallSlot) and 1 or bar:GetConfig().lastSummon or 1
+  if idx == bar.recallSlot or idx == bar.summonSlot then
     f:SetAttribute("type","spell")
-    local spells = idx == 1 and TOTEM_MULTI_CAST_SUMMON_SPELLS or TOTEM_MULTI_CAST_RECALL_SPELLS
+    local spells = (idx == bar.summonSlot) and TOTEM_MULTI_CAST_SUMMON_SPELLS or TOTEM_MULTI_CAST_RECALL_SPELLS
     f:SetAttribute("spell",spells[page])
     for i, spell in ipairs(spells) do 
       if spell and IsSpellKnown(spell) then
+        print("setting attribute spell-page"..i.." to "..spell)
         f:SetAttribute("spell-page"..i, spell)
       end
     end
   else
-    local baseAction = barFrame:GetAttribute("baseActionID") + SHAMAN_TOTEM_PRIORITIES[idx-1]
+    local offset = bar.summonSlot and 1 or 0
+    local slot = SHAMAN_TOTEM_PRIORITIES[idx - offset]
+    local baseAction = barFrame:GetAttribute("baseActionID") + slot
     f:SetAttribute("type","action")
     f:SetAttribute("action", baseAction + (page - 1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE)
     for i = 1, NUM_MULTI_CAST_PAGES do
@@ -293,7 +301,6 @@
     end
   end
   f:SetAttribute("bar-idx",idx)
-  barFrame:SetFrameRef("slot-"..idx,f)
 
   -- non secure scripts
   f:SetScript("OnEvent", function(frame, ...) self:OnEvent(...) end)
@@ -303,7 +310,7 @@
   f:SetScript("PostClick", function(frame, ...) self:PostClick(...) end)
 
   -- secure handlers
-  if idx ~= NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2 then
+  if idx ~= bar.recallSlot then
     f:SetAttribute("_childupdate",_childupdate)
   end
   barFrame:WrapScript(f, "OnEnter", _onEnter)
@@ -331,7 +338,7 @@
   f:Show()
 
   -- open arrow
-  if idx ~= NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2 then
+  if idx ~= bar.recallSlot then
     local arrow = CreateFrame("Button", nil, f, "SecureFrameTemplate")
     arrow:SetWidth(28)
     arrow:SetHeight(12)
@@ -343,8 +350,7 @@
     arrow:SetAttribute("bar-idx",idx)
     arrow:Hide()
     barFrame:WrapScript(arrow, "OnClick", _arrow_openFlyout)
-    local arrowRef = "arrow-"..idx
-    barFrame:SetFrameRef(arrowRef,arrow)
+    barFrame:SetFrameRef("arrow-"..idx,arrow)
   end
 
   self:Refresh()
@@ -491,17 +497,17 @@
 --
 -- flyout setup
 --
-local function ShowFlyoutTooltip(barFrame)
+local function ShowFlyoutTooltip(frame)
   if GetCVar("UberTooltips") == "1" then
-    GameTooltip_SetDefaultAnchor(GameTooltip, barFrame)
+    GameTooltip_SetDefaultAnchor(GameTooltip, frame)
   else
-    GameTooltip:SetOwner(barFrame)
+    GameTooltip:SetOwner(frame)
   end
-  local spell = barFrame:GetAttribute("spell")
-  if barFrame == 0 then
+  local spell = frame:GetAttribute("spell")
+  if spell == nil or spell == 0 then
     GameTooltip:SetText(MULTI_CAST_TOOLTIP_NO_TOTEM, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
   else
-    GameTooltip:SetSpellByID(barFrame:GetAttribute("spell"),false,true)
+    GameTooltip:SetSpellByID(spell,false,true)
   end
 end
 
@@ -511,79 +517,83 @@
 
 local function UpdateFlyoutIcon(frame)
   local spellID = frame:GetAttribute("spell")
-  if spellID == 0 then
+  if spellID == 0 or spellID == nil then
     frame.icon:SetTexture(TOTEM_TEXTURE)
     frame.icon:SetTexCoord( unpack(EMPTY_SLOT_TCOORDS) )
-  elseif spellID then
+  else
     frame.icon:SetTexture(GetSpellTexture(GetSpellInfo(spellID)))
     frame.icon:SetTexCoord(0,1,0,1)
   end
 end
 
 function MultiCast.SetupBarHeader( bar ) -- call this as a static method
-  local summon = { }
-  local recall = { }
-  local maxIdx = 1
+  local slot = 0
+  local nTotemSlots = 0
+  local summonSlot = nil
+  local recallSlot = nil
 
-	for idx, spell in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do 
+  -- figure out the capabilities of the character
+	for i, spell in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do 
     if spell and IsSpellKnown(spell) then
-      tinsert(summon,spell)
-      maxIdx = max(idx,maxIdx)
+      slot = 1
+      summonSlot = 1
     end
 	end
 
-	for idx, spell in ipairs(TOTEM_MULTI_CAST_RECALL_SPELLS) do 
+  for i = 1, NUM_MULTI_CAST_BUTTONS_PER_PAGE do
+		local totem = SHAMAN_TOTEM_PRIORITIES[i];
+		if GetTotemInfo(totem) and GetMultiCastTotemSpells(totem) then
+      nTotemSlots = nTotemSlots + 1
+      slot = slot + 1
+    end
+  end
+
+  slot = slot + 1
+	for i, spell in ipairs(TOTEM_MULTI_CAST_RECALL_SPELLS) do 
     if spell and IsSpellKnown(spell) then
-      tinsert(recall,spell)
-      maxIdx = max(idx,maxIdx)
+      recallSlot = slot
     end
 	end
 
-  if #summon == 0 and #recall == 0 then
-    bar.nMultiCastSlots = 0 -- no multicast capability
+  if nTotemSlots == 0 then
+    bar.hasMulticast = false -- no multicast capability
     return
   end
 
-  local slots = { }
-  
-  tinsert(slots, summon)
+  bar.hasMulticast = true
+  bar.summonSlot   = summonSlot
+  bar.recallSlot   = recallSlot
+  bar.nTotemSlots  = nTotemSlots
 
-  for i = 1, NUM_MULTI_CAST_BUTTONS_PER_PAGE do
-    local slotSpells = { 0, GetMultiCastTotemSpells(SHAMAN_TOTEM_PRIORITIES[i]) }
-    maxIdx = max(maxIdx, #slotSpells)
-    tinsert(slots,slotSpells)
-  end
 
-  tinsert(slots, recall)
-
-  local barFrame = bar:GetFrame()
+  local f = bar:GetFrame()
 
   -- init bar secure environment
-  barFrame:SetAttribute("lastSummon",bar:GetConfig().lastSummon)
-  barFrame:SetAttribute("nMultiCastSlots",#slots)
-  barFrame:SetAttribute("baseActionID", (NUM_ACTIONBAR_PAGES + GetMultiCastBarOffset() - 1)*NUM_ACTIONBAR_BUTTONS)
-  barFrame:SetAttribute("_onstate-multispellpage", _onstate_multispellpage)
-  barFrame:Execute(_bar_init)
+  f:SetAttribute("lastSummon", bar:GetConfig().lastSummon)
+  f:SetAttribute("summonSlot", summonSlot)
+  f:SetAttribute("recallSlot", recallSlot)
+  f:SetAttribute("slotsPerPage", NUM_MULTI_CAST_BUTTONS_PER_PAGE)
+  f:SetAttribute("baseActionID", (NUM_ACTIONBAR_PAGES + GetMultiCastBarOffset() - 1)*NUM_ACTIONBAR_BUTTONS)
+  for i, p in ipairs(SHAMAN_TOTEM_PRIORITIES) do
+    f:SetAttribute("TOTEM_PRIORITY_"..i,p)
+  end
+  f:SetAttribute("_onstate-multispellpage", _onstate_multispellpage)
 
-  function barFrame:UpdateLastSummon(value)
+  function f:UpdateLastSummon(value)
     bar:GetConfig().lastSummon = value
   end
 
-  for i, p in ipairs(SHAMAN_TOTEM_PRIORITIES) do
-    barFrame:SetAttribute("TOTEM_PRIORITY_"..i,p)
-  end
-
   -- create flyout container frame and close arrow
   local flyout = bar._flyoutFrame
   if not flyout then
-    flyout = CreateFrame("Frame", nil, barFrame, "SecureFrameTemplate")
+    flyout = CreateFrame("Frame", nil, f, "SecureFrameTemplate")
     bar._flyoutFrame = flyout
-    barFrame:SetFrameRef("flyout",flyout)
+    f:SetFrameRef("flyout",flyout)
     flyout.buttons = { }
     flyout:Hide()
     flyout:SetWidth(24)
     flyout:SetHeight(1)
-    flyout:SetPoint("BOTTOM",barFrame,"TOP",0,0)
+    flyout:SetPoint("BOTTOM",f,"TOP",0,0)
 
     local close = CreateFrame("Button", nil, flyout, "SecureFrameTemplate")
     close:SetWidth(28)
@@ -593,51 +603,38 @@
     close:GetNormalTexture():SetTexCoord( unpack(FLYOUT_DOWN_BUTTON_TCOORDS) )
     close:SetHighlightTexture(TOTEM_TEXTURE)
     close:GetHighlightTexture():SetTexCoord( unpack(FLYOUT_DOWN_BUTTON_HL_TCOORDS) )
-    barFrame:SetFrameRef("close",close)
-    barFrame:WrapScript(close, "OnClick", _closeFlyout)
-  end
+    f:SetFrameRef("close",close)
+    f:WrapScript(close, "OnClick", _closeFlyout)
 
-  -- create flyout buttons
-  for i = #flyout.buttons + 1, maxIdx do
-    local b = CreateFrame("Button",nil,flyout,"SecureActionButtonTemplate")
-    b:SetWidth(24)
-    b:SetHeight(24)
-    local prev = flyout.buttons[i-1]
-    b:SetPoint("BOTTOM", prev or flyout, prev and "TOP" or "BOTTOM", 0, 3) -- TODO: better anchoring
-    b.icon = b:CreateTexture("BACKGROUND")
-    b.icon:SetAllPoints()
-    b.icon:Show()
-    b:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
-    b:GetHighlightTexture():SetBlendMode("ADD")
-    b:RegisterForClicks("AnyUp")
-    b:SetScript("OnShow",UpdateFlyoutIcon)
-    b:SetScript("OnEnter",ShowFlyoutTooltip)
-    b:SetScript("OnLeave",HideFlyoutTooltip)
-    b:SetAttribute("index",i)
-    b:Show()
-    barFrame:WrapScript(b, "OnClick", _flyout_child_preClick, _flyout_child_postClick)
-    flyout.buttons[i] = b
-  end
-
-  for i, b in ipairs(flyout.buttons) do
-    barFrame:SetFrameRef("flyout-child",b)
-    barFrame:Execute([[
-        tinsert(flyoutChildren,self:GetFrameRef("flyout-child"))
-      ]])
-  end
-
-  -- transfer the table of spell IDs into the secure environment
-  for i, spells in ipairs(slots) do
-    barFrame:SetAttribute("spell-slot", i)
-    for j, spell in ipairs(spells) do
-      barFrame:SetAttribute("spell-index", j)
-      barFrame:SetAttribute("spell-id", spell)
-      barFrame:Execute([[
-          multiCastSpellList[self:GetAttribute("spell-slot")][self:GetAttribute("spell-index")] = self:GetAttribute("spell-id")
+    -- create flyout buttons
+    for i = 1, 10 do -- maximum 9 spells + 1 empty slot
+      local b = CreateFrame("Button",nil,flyout,"SecureActionButtonTemplate")
+      b:SetWidth(24)
+      b:SetHeight(24)
+      local prev = flyout.buttons[i-1]
+      b:SetPoint("BOTTOM", prev or flyout, prev and "TOP" or "BOTTOM", 0, 3) -- TODO: better anchoring
+      b.icon = b:CreateTexture("BACKGROUND")
+      b.icon:SetAllPoints()
+      b.icon:Show()
+      b:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
+      b:GetHighlightTexture():SetBlendMode("ADD")
+      b:RegisterForClicks("AnyUp")
+      b:SetScript("OnShow",UpdateFlyoutIcon)
+      b:SetScript("OnEnter",ShowFlyoutTooltip)
+      b:SetScript("OnLeave",HideFlyoutTooltip)
+      b:SetAttribute("index",i)
+      f:SetAttribute("flyout-child-idx",i)
+      f:SetFrameRef("flyout-child",b)
+      f:Execute([[
+          flyoutChildren = flyoutChildren or newtable()
+          flyoutChildren[self:GetAttribute("flyout-child-idx")] = self:GetFrameRef("flyout-child")
         ]])
+      f:WrapScript(b, "OnClick", _flyout_child_preClick, _flyout_child_postClick)
+      b:Show()
+      flyout.buttons[i] = b
     end
   end
 
-  bar.nMultiCastSlots = #slots
+  f:Execute(_bar_init)
 end