Mercurial > wow > reaction
view classes/MultiCastButton.lua @ 164:363dd8130205
minor tweak to facilitate picking up the bar when training Call of Elements
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Sat, 22 Aug 2009 00:26:29 +0000 |
parents | ab5c37989986 |
children | 8241be11dcc0 |
line wrap: on
line source
local ReAction = ReAction local L = ReAction.L local _G = _G local CreateFrame = CreateFrame local format = string.format local unpack = unpack local GetCVar = GetCVar local GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor local CooldownFrame_SetTimer = CooldownFrame_SetTimer local InCombatLockdown = InCombatLockdown local IsUsableSpell = IsUsableSpell local IsUsableAction = IsUsableAction local IsSpellKnown = IsSpellKnown local IsSpellInRange = IsSpellInRange local IsActionInRange = IsActionInRange local GetSpellInfo = GetSpellInfo local GetSpellCooldown = GetSpellCooldown local GetActionCooldown = GetActionCooldown local GetSpellTexture = GetSpellTexture local GetActionTexture = GetActionTexture local GetMultiCastTotemSpells = GetMultiCastTotemSpells ReAction:UpdateRevision("$Revision: 154 $") --[[ Blizzard Constants: - NUM_MULTI_CAST_BUTTONS_PER_PAGE = 4 - NUM_MULTI_CAST_PAGES = 3 - TOTEM_PRIORITIES = { } -- sets the order of the totems - TOTEM_MULTI_CAST_SUMMON_SPELLS = { } -- list of summon spellIDs - TOTEM_MULTI_CAST_RECALL_SPELLS = { } -- list of recall spellIDs Blizzard Events: - UPDATE_MULTI_CAST_ACTIONBAR Blizzard APIs: - GetMultiCastBarOffset() : returns 6 - SetMultiCastSpell(actionID, spellID) (protected) OR SetAttribute("type","multispell") SetAttribute("action",actionID) SetAttribute("spell",spellID) note: multicast actionID page is NUM_ACTIONBAR_PAGES + GetMultiCastBarOffset(), 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. Blizzard textures: All the textures for the multicast bar (arrows, empty-slot icons, etc) are part of a single texture: each texture uses SetTexCoord() to display only a slice of the textures. I suppose this is to slightly optimize texture load performance, but it makes the UI code more clumsy. Each totem button and arrow has a colored border indicating its elemental type. TODO: update code to use these pretty per-slot icons, or start setting a border. Design Notes: - Only the header has a secure context. All other frames execute in its context. - Each button is either type "spell" (summon/recall) or type "action" (totem action IDs are GetBonusBarOffset()=6, 132-144) with 3 pages of 4 buttons. The paging is controlled by the summon flyout, which is also paged with the summon spells (the recall button is not paged) - A spell list is updated in the secure context at setup time (TODO: redo setup when learning new spells) with the list of spells known for each slot. - 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 spell/type from the list, according to the slot which opened the flyout. Each button either sets the current page (summon) or sets a multispell to an actionID via type="multispell". None of them actually cast any spells (though, I suppose we could modify this so that e.g. configurable right-click casts the spell). The flyout also has a close button which closes the flyout: the flyout-open code positions the close button anchored to the last button in the flyout (which 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. ]]-- -- -- Secure snippets -- -- bar local _bar_init = -- function(self) [[ -- set up some globals in the secure environment flyout = self:GetFrameRef("flyout") flyoutChildren = newtable() nMultiCastSlots = self:GetAttribute("nMultiCastSlots") baseActionID = self:GetAttribute("baseActionID") currentMultiCastPage = currentMultiCastPage or self:GetAttribute("lastSummon") or 1 multiCastSpellList = newtable() for i = 1, nMultiCastSlots do tinsert(multiCastSpellList, newtable()) end ]] local _onstate_multispellpage = -- function(self, stateid, newstate) [[ currentMultiCastPage = tonumber(newstate) control:CallMethod("UpdateLastSummon",currentMultiCastPage) control:ChildUpdate() ]] -- 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 _onEnter = -- function(self) -- for whatever reason, RegisterAutoHide is unreliable -- 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 arrow:RegisterAutoHide(0) arrow:AddToAutoHide(self) end ]] -- 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 b:SetAttribute("type","changePage") else b:SetAttribute("type","multispell") local totemID = owner:GetAttribute("TOTEM_PRIORITY_"..(currentMultiCastSlot - 1)) b:SetAttribute("action", baseActionID + (currentMultiCastPage - 1)*(nMultiCastSlots-2) + totemID) end b:Show() lastButton = b lastIdx = idx end end local close = owner:GetFrameRef("close") if lastButton and close then close:ClearAllPoints() close:SetPoint("BOTTOM",lastButton,"TOP",0,0) -- TODO: better anchoring close:Show() end 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)) end flyout:Show() flyout:RegisterAutoHide(1) -- TODO: configurable flyout:AddToAutoHide(owner) flyoutIdx = currentMultiCastSlot self:Hide() ]] local _closeFlyout = -- function(self) [[ flyout:Hide() ]] -- flyout child buttons local _flyout_child_preClick = -- function(self, button, down) [[ local button = button if self:GetAttribute("type") == "changePage" then owner:SetAttribute("state-multispellpage",self:GetAttribute("index")) self:GetParent():Hide() return false else return nil, "close" end ]] local _flyout_child_postClick = -- function(self, message, button, down) [[ if message == "close" then self:GetParent():Hide() -- hide flyout after selecting end ]] -- -- The Blizzard totem bar textures are all actually one big texture, -- with texcoord offsets -- local TOTEM_TEXTURE = "Interface\\Buttons\\UI-TotemBar" local FLYOUT_UP_BUTTON_TCOORDS = { 99/128, 127/128, 84/256, 102/256 } local FLYOUT_UP_BUTTON_HL_TCOORDS = { 72/128, 92/128, 88/256, 98/256 } local FLYOUT_DOWN_BUTTON_TCOORDS = { 99/128, 127/128, 65/256, 83/256 } local FLYOUT_DOWN_BUTTON_HL_TCOORDS = { 72/128, 92/128, 69/256, 79/256 } local EMPTY_SLOT_TCOORDS = { 66/128, 96/128, 3/256, 33/256 } local eventList = { "ACTIONBAR_SLOT_CHANGED", "ACTIONBAR_UPDATE_STATE", "ACTIONBAR_UPDATE_USABLE", "ACTIONBAR_UPDATE_COOLDOWN", "UPDATE_BINDINGS", "UPDATE_MULTI_CAST_ACTIONBAR", } -- -- MultiCast Button class -- Inherits implementation methods from Action button class, but circumvents the constructor -- and redefines/removes some methods. -- local Super = ReAction.Button local Action = ReAction.Button.Action local MultiCast = setmetatable( { }, { __index = Action } ) 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") end if idx > bar.nMultiCastSlots then return false end local name = format("ReAction_%s_Action_%d",bar:GetName(),idx) self = Super.New(self, name, btnConfig, bar, idx, "SecureActionButtonTemplate, ActionButtonTemplate" ) local barFrame = bar:GetFrame() 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 f:SetAttribute("type","spell") local spells = idx == 1 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 f:SetAttribute("spell-page"..i, spell) end end else local baseAction = barFrame:GetAttribute("baseActionID") + TOTEM_PRIORITIES[idx-1] f:SetAttribute("type","action") f:SetAttribute("action", baseAction + (page - 1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE) for i = 1, NUM_MULTI_CAST_PAGES do f:SetAttribute("action-page"..i, baseAction + (i-1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE) end end f:SetAttribute("bar-idx",idx) barFrame:SetFrameRef("slot-"..idx,f) -- non secure scripts f:SetScript("OnEvent", function(frame, ...) self:OnEvent(...) end) f:SetScript("OnEnter", function(frame) self:OnEnter() end) f:SetScript("OnLeave", function(frame) self:OnLeave() end) f:SetScript("OnAttributeChanged", function(frame, attr, value) self:OnAttributeChanged(attr, value) end) f:SetScript("PostClick", function(frame, ...) self:PostClick(...) end) -- secure handlers if idx ~= NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2 then f:SetAttribute("_childupdate",_childupdate) end barFrame:WrapScript(f, "OnEnter", _onEnter) -- event registration f:EnableMouse(true) f:RegisterForClicks("AnyUp") for _, evt in pairs(eventList) do f:RegisterEvent(evt) end -- Set up a proxy for the icon texture for use with ButtonFacade self.frames.icon.SetTexCoordRaw = self.frames.icon.SetTexCoord self.frames.icon.SetTexCoord = function( tx, ... ) if self:GetIconTexture() == TOTEM_TEXTURE then tx:SetTexCoordRaw(unpack(EMPTY_SLOT_TCOORDS)) else tx:SetTexCoordRaw(...) end end -- attach to skinner bar:SkinButton(self) f:Show() -- open arrow if idx ~= NUM_MULTI_CAST_BUTTONS_PER_PAGE + 2 then local arrow = CreateFrame("Button", nil, f, "SecureFrameTemplate") arrow:SetWidth(28) arrow:SetHeight(12) arrow:SetPoint("BOTTOM",self:GetFrame(),"TOP",0,0) -- TODO: better anchoring arrow:SetNormalTexture(TOTEM_TEXTURE) arrow:GetNormalTexture():SetTexCoord( unpack(FLYOUT_UP_BUTTON_TCOORDS) ) arrow:SetHighlightTexture(TOTEM_TEXTURE) arrow:GetHighlightTexture():SetTexCoord( unpack(FLYOUT_UP_BUTTON_HL_TCOORDS) ) arrow:SetAttribute("bar-idx",idx) arrow:Hide() barFrame:WrapScript(arrow, "OnClick", _arrow_openFlyout) local arrowRef = "arrow-"..idx barFrame:SetFrameRef(arrowRef,arrow) end self:Refresh() return self end function MultiCast:Destroy() local barFrame = self:GetFrame() Super.Destroy(self) end function MultiCast:Refresh() Super.Refresh(self) self:UpdateAction() end function MultiCast:ShowGrid( show ) end function MultiCast:ShowGridTemp( show ) end function MultiCast:AcquireActionID() end function MultiCast:ReleaseActionID() end function MultiCast:UpdateShowGrid() end function MultiCast:UpdateBorder() end function MultiCast:UpdateMacroText() end function MultiCast:UpdateCount() end function MultiCast:UpdateCheckedState() local action = self:GetActionID() if action and IsCurrentAction(action) then self:GetFrame():SetChecked(1) else self:GetFrame():SetChecked(0) end end function MultiCast:RefreshHasActionAttributes() end function MultiCast:UpdateFlash() end function MultiCast:GetIconTexture() local tx if self.spellID then tx = GetSpellTexture(GetSpellInfo(self.spellID)) elseif self.actionID then tx = GetActionTexture(self.actionID) end if tx then return tx else return TOTEM_TEXTURE, unpack(EMPTY_SLOT_TCOORDS) end end function MultiCast:UpdateAction() local action = self:GetActionID() if action then if action ~= self.actionID then self.actionID = action self:UpdateAll() end else local spellID = self:GetSpellID() if spellID ~= self.spellID then self.spellID = spellID self:UpdateAll() end end end function MultiCast:GetActionID(page) return self:GetFrame():GetAttribute("action") end function MultiCast:GetSpellID(page) return self:GetFrame():GetAttribute("spell") end function MultiCast:SetActionID( id ) error("Can not set action ID of multicast buttons") end function MultiCast:SetTooltip() local barFrame = self:GetFrame() if GetCVar("UberTooltips") == "1" then GameTooltip_SetDefaultAnchor(GameTooltip, barFrame) else GameTooltip:SetOwner(barFrame) end if self.spellID then GameTooltip:SetSpellByID(self.spellID,false,true) elseif self.actionID then GameTooltip:SetAction(self.actionID) end end function MultiCast:GetUsable() if self.spellID then return IsUsableSpell((GetSpellInfo(self.spellID))) elseif self.actionID then return IsUsableAction(self.actionID) end end function MultiCast:GetInRange() if self.spellID then return IsSpellInRange((GetSpellInfo(self.spellID))) == 0 elseif self.actionID then return IsActionInRange(self.actionID) == 0 end end function MultiCast:GetCooldown() if self.spellID then return GetSpellCooldown((GetSpellInfo(self.spellID))) elseif self.actionID then return GetActionCooldown(self.actionID) else return 0, 0, 0 end end function MultiCast:UPDATE_MULTI_CAST_ACTIONBAR() self:UpdateAll() end -- -- flyout setup -- local function ShowFlyoutTooltip(barFrame) if GetCVar("UberTooltips") == "1" then GameTooltip_SetDefaultAnchor(GameTooltip, barFrame) else GameTooltip:SetOwner(barFrame) end local spell = barFrame:GetAttribute("spell") if barFrame == 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) end end local function HideFlyoutTooltip() GameTooltip:Hide() end local function UpdateFlyoutIcon(frame) local spellID = frame:GetAttribute("spell") if spellID == 0 then frame.icon:SetTexture(TOTEM_TEXTURE) frame.icon:SetTexCoord( unpack(EMPTY_SLOT_TCOORDS) ) elseif spellID then 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 for idx, spell in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do if spell and IsSpellKnown(spell) then tinsert(summon,spell) maxIdx = max(idx,maxIdx) end end for idx, spell in ipairs(TOTEM_MULTI_CAST_RECALL_SPELLS) do if spell and IsSpellKnown(spell) then tinsert(recall,spell) maxIdx = max(idx,maxIdx) end end if #summon == 0 and #recall == 0 then bar.nMultiCastSlots = 0 -- no multicast capability return end local slots = { } tinsert(slots, summon) for i = 1, NUM_MULTI_CAST_BUTTONS_PER_PAGE do local slotSpells = { 0, GetMultiCastTotemSpells(TOTEM_PRIORITIES[i]) } maxIdx = max(maxIdx, #slotSpells) tinsert(slots,slotSpells) end tinsert(slots, recall) local barFrame = 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) function barFrame:UpdateLastSummon(value) bar:GetConfig().lastSummon = value end for i, p in ipairs(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") bar._flyoutFrame = flyout barFrame:SetFrameRef("flyout",flyout) flyout.buttons = { } flyout:Hide() flyout:SetWidth(24) flyout:SetHeight(1) flyout:SetPoint("BOTTOM",barFrame,"TOP",0,0) local close = CreateFrame("Button", nil, flyout, "SecureFrameTemplate") close:SetWidth(28) close:SetHeight(12) close:SetPoint("TOP") close:SetNormalTexture(TOTEM_TEXTURE) 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 -- 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") ]]) end end bar.nMultiCastSlots = #slots end