Mercurial > wow > reaction
view Button.lua @ 2:8e0ff8ae4c08
Version 0.2
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Tue, 20 Mar 2007 21:08:31 +0000 |
parents | c11ca1d8ed91 |
children |
line wrap: on
line source
-- private constants local namePrefix = "ReActionButton" local _G = getfenv(0) local ACTION_FREE = { } local MAX_ACTIONS = 120 local hotKeyDefaultColor = { r=1.0, g=1.0, b=1.0, a=1.0 } local hotKeyDisabledColor = { r=0.6, g=0.6, b=0.6, a=1.0 } local hotKeyOutOfRangeColor = { r=1.0, g=0.2, b=0.2, a=1.0 } local hotKeyModifierColors = { S = { r=0.6, g=0.6, b=1.0, a=1.0 }, -- shift C = { r=1.0, g=0.82, b=0, a=1.0 }, -- ctrl A = { r=0.1, g=1.0, b=0.1, a=1.0 }, -- alt } -- TODO: localize these key names with GetBindingText(KEY_) local keybindAbbreviations = { ["Mouse Button "] = "M-", ["Spacebar"] = "Sp", ["Num Pad "] = "Num-", ["Page Up"] = "PgUp", ["Page Down"] = "PgDn", [" Arrow"] = "", } local equippedActionBorderColor = { r=0, g=1.0, b=0, a=0.35 } local actionUsableColor = { r=1.0, g=1.0, b=1.0, a=1.0 } local actionNotUsableColor = { r=0.4, g=0.4, b=0.4, a=1.0 } local actionNotEnoughManaColor = { r=0.2, g=0.2, b=0.7, a=1.0 } local actionOutOfRangeColor = { r=1.0, g=0.2, b=0.2, a=1.0 } -- private variables local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc local actionButtonTbl = { } -- ReActionButton is a class prototype object. ReActionButton = AceLibrary("AceOO-2.0").Class("AceEvent-2.0") ------------------- -- Class methods ------------------- -- In addition to supporting the 'new' creation method (in which case an action ID must be specified directly), -- ReActionButton supports the 'acquire'/'release' creation method, which recycles objects and manages actionID assignment. function ReActionButton:acquire(parent, config, barIdx) local id = nil for i = 1, MAX_ACTIONS do if actionButtonTbl[i] == nil or actionButtonTbl[i].inUse == false then id = i break end end if id == nil then return nil end -- all buttons and action ids are in use local hint = config.actionIDs[barIdx] if hint and (actionButtonTbl[hint] == nil or actionButtonTbl[hint].inUse == false) then id = hint end if actionButtonTbl[id] == nil then actionButtonTbl[id] = { } end local t = actionButtonTbl[id] t.inUse = true if t.button then t.button:Configure(parent,config,barIdx) else t.button = self:new(parent,config,barIdx,id) end -- fix screwy config with overlapping IDs config.actionIDs[barIdx] = id return t.button end function ReActionButton:release( b ) if b then actionButtonTbl[b:GetActionID()].inUse = false b:Recycle() end end function ReActionButton:ShowAllActionIDs() for _, b in ipairs(actionButtonTbl) do b:ShowActionID() end end function ReActionButton:HideAllActionIDs() for _, b in ipairs(actionButtonTbl) do b:HideActionID() end end ---------------------- -- Instance methods ---------------------- function ReActionButton.prototype:init( parentFrame, config, barIdx, id ) ReActionButton.super.prototype.init(self) -- create the button widget self.name = namePrefix..id self.button = CreateFrame("CheckButton", self.name, parentFrame, "ReActionButtonTemplate") -- store references to the various sub-frames so we don't have to look it up all the time self.frames = { hotkey = _G[self.name.."HotKey"], count = _G[self.name.."Count"], cooldown = _G[self.name.."Cooldown"], -- nCooldown = _G[self.name.."CooldownNumeric"], macro = _G[self.name.."Name"], icon = _G[self.name.."Icon"], border = _G[self.name.."Border"], normalTexture = _G[self.name.."NormalTexture"], flash = _G[self.name.."Flash"], actionID = _G[self.name.."ActionID"], } -- provide a reference back to this object for the frame to use in event handlers self.button.rxnBtn = self -- set the action ID self:SetActionID(id) -- register button with ReBinder for keybinding ReBinder:AddKeybindTarget(self.button) -- initialize self:Configure(parentFrame, config, barIdx) end local function tcopy(t) local r = { } for k, v in pairs(t) do r[k] = (type(v) == "table" and tcopy(v) or v) end return r end function ReActionButton.prototype:Recycle() local b = self.button self:SetKeyBinding(nil) self:UpdateDisplay() b:UnregisterAllEvents() b:SetParent(ReActionButtonRecycleFrame) b:ClearAllPoints() b:SetPoint("TOPLEFT",0,0) b:Hide() self.config = tcopy(self.config) -- ew, but necessary end function ReActionButton.prototype:BarUnlocked() self:ShowGridTmp() end function ReActionButton.prototype:BarLocked() self:HideGridTmp() end -- set the button location function ReActionButton.prototype:PlaceButton(point, x, y, sz) local b = self.button local scale = sz / 36 b:ClearAllPoints() b:SetScale( scale ) b:SetPoint(point,x/scale,y/scale) end function ReActionButton.prototype:ShowActionID() self.frames.actionID:Show() end function ReActionButton.prototype:HideActionID() self.frames.actionID:Hide() end -- configuration and setup function ReActionButton.prototype:Configure( parentFrame, config, barIdx ) self.config = config self.barIdx = barIdx self.showGridTmp_ = 0 self.button:ClearAllPoints() self.button:SetParent(parentFrame) self:SetupAttributes() self:RegisterStaticEvents() self:ApplyLayout() self:ApplyStyle() self:UpdateDisplay() end function ReActionButton.prototype:SetupAttributes() local b = self.button b:SetAttribute("type", "action") b:SetAttribute("shift-type*", ATTRIBUTE_NOOP) b:SetAttribute("checkselfcast", true) b:SetAttribute("useparent-unit", true) end function ReActionButton.prototype:RegisterStaticEvents() self:RegisterEvent("PLAYER_ENTERING_WORLD") self:RegisterEvent("ACTIONBAR_SLOT_CHANGED") self:RegisterEvent("UPDATE_BINDINGS") self:RegisterEvent("ACTIONBAR_SHOWGRID") self:RegisterEvent("ACTIONBAR_HIDEGRID") end function ReActionButton.prototype:RegisterActionEvents() self:RegisterEvent("ACTIONBAR_UPDATE_STATE") self:RegisterEvent("ACTIONBAR_UPDATE_USABLE") self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", "ACTIONBAR_UPDATE_USABLE") self:RegisterEvent("UPDATE_INVENTORY_ALERTS", "ACTIONBAR_UPDATE_USABLE") self:RegisterEvent("PLAYER_AURAS_CHANGED", "ACTIONBAR_UPDATE_USABLE") self:RegisterEvent("PLAYER_TARGET_CHANGED", "ACTIONBAR_UPDATE_USABLE") self:RegisterEvent("UNIT_INVENTORY_CHANGED") self:RegisterEvent("CRAFT_SHOW") self:RegisterEvent("CRAFT_CLOSE", "CRAFT_SHOW") self:RegisterEvent("TRADE_SKILL_SHOW", "CRAFT_SHOW") self:RegisterEvent("TRADE_SKILL_CLOSE", "CRAFT_SHOW") self:RegisterEvent("PLAYER_ENTER_COMBAT", "CRAFT_SHOW") self:RegisterEvent("PLAYER_LEAVE_COMBAT") self:RegisterEvent("START_AUTOREPEAT_SPELL") self:RegisterEvent("STOP_AUTOREPEAT_SPELL") self.button:SetScript("OnUpdate", function() self:OnUpdate(arg1) end) self.actionEventsRegistered = true end function ReActionButton.prototype:UnregisterActionEvents() self:UnregisterEvent("ACTIONBAR_UPDATE_STATE") self:UnregisterEvent("ACTIONBAR_UPDATE_USABLE") self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN") self:UnregisterEvent("UPDATE_INVENTORY_ALERTS") self:UnregisterEvent("PLAYER_AURAS_CHANGED") self:UnregisterEvent("PLAYER_TARGET_CHANGED") self:UnregisterEvent("UNIT_INVENTORY_CHANGED") self:UnregisterEvent("CRAFT_SHOW") self:UnregisterEvent("CRAFT_CLOSE") self:UnregisterEvent("TRADE_SKILL_SHOW") self:UnregisterEvent("TRADE_SKILL_CLOSE") self:UnregisterEvent("PLAYER_ENTER_COMBAT") self:UnregisterEvent("PLAYER_LEAVE_COMBAT") self:UnregisterEvent("START_AUTOREPEAT_SPELL") self:UnregisterEvent("STOP_AUTOREPEAT_SPELL") self.button:SetScript("OnUpdate", nil) self.actionEventsRegistered = false end -- event handlers function ReActionButton.prototype:ACTIONBAR_SLOT_CHANGED() if arg1 == 0 or arg1 == self:GetActionID() then self:UpdateDisplay() end end function ReActionButton.prototype:PLAYER_ENTERING_WORLD() self:UpdateDisplay() end function ReActionButton.prototype:UPDATE_BINDINGS() self:UpdateDisplay() end function ReActionButton.prototype:ACTIONBAR_SHOWGRID() self:ShowGridTmp() end function ReActionButton.prototype:ACTIONBAR_HIDEGRID() self:HideGridTmp() end function ReActionButton.prototype:ACTIONBAR_UPDATE_STATE() self:UpdateCheckedState() end function ReActionButton.prototype:ACTIONBAR_UPDATE_USABLE() self:UpdateUsable() self:UpdateCooldown() self:ColorHotKey() end function ReActionButton.prototype:UNIT_INVENTORY_CHANGED() if arg1 == "player" then self:UpdateDisplay() end end function ReActionButton.prototype:CRAFT_SHOW() self:UpdateCheckedState() end function ReActionButton.prototype:PLAYER_ENTER_COMBAT() if IsAttackAction(self:GetActionID()) then self:StartFlash() end end function ReActionButton.prototype:PLAYER_LEAVE_COMBAT() if IsAttackAction(self:GetActionID()) then self:StopFlash() end end function ReActionButton.prototype:START_AUTOREPEAT_SPELL() if IsAutoRepeatAction(self:GetActionID()) then self:StartFlash() end end function ReActionButton.prototype:STOP_AUTOREPEAT_SPELL() if self:IsFlashing() and not IsAttackAction(self:GetActionID()) then self:StopFlash() end end -- OnUpdate handler function ReActionButton.prototype:OnUpdate(elapsed) local action = self:GetActionID() local f = self.frames -- handle flashing if self:IsFlashing() then self.flashtime = self.flashtime - elapsed if self.flashtime <= 0 then local overtime = -self.flashtime if overtime >= ATTACK_BUTTON_FLASH_TIME then overtime = 0 end self.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime if f.flash:IsVisible() then f.flash:Hide() else f.flash:Show() end end end -- Handle range indicator if self.rangeTimer then self.rangeTimer = self.rangeTimer - elapsed if self.rangeTimer <= 0 then self:ColorHotKey() self:UpdateUsable() self.rangeTimer = TOOLTIP_UPDATE_TIME end end -- handle toltip update if self.tooltipTime then self.tooltipTime = self.tooltipTime - elapsed if self.tooltipTime <= 0 then if GameTooltip:IsOwned(self.button) then self:UpdateTooltip() else self.tooltipTime = nil end end end end -- keybinding functions function ReActionButton.prototype:SetKeyBinding( k ) if k == nil or kbValidate(k) then local current = self:GetKeyBinding() ClearOverrideBindings(self.button) if current then SetBinding(current,nil) end if k then SetBindingClick(k, self.name, "LeftButton") end end end function ReActionButton.prototype:GetKeyBinding() return GetBindingKey("CLICK "..self.name..":LeftButton") end -- action ID functions function ReActionButton.prototype:SetActionID( id ) self.actionID = tonumber(id) -- force data integrity self:ApplyActionID() end function ReActionButton.prototype:GetActionID() return self.actionID end function ReActionButton.prototype:ApplyActionID() local action = tonumber(self:GetActionID()) self.button:SetAttribute("action",action) self.frames.actionID:SetText(action or "") end function ReActionButton.prototype:ShowActionID() self.frames.actionID:Show() end function ReActionButton.prototype:HideActionID() self.frames.actionID:Hide() end function ReActionButton:ShowAllActionIDs() -- class-wide function for _, tbl in pairs(actionButtonTbl) do if tbl.button then tbl.button:ShowActionID() end end end function ReActionButton:HideAllActionIDs() -- class-wide function for _, tbl in pairs(actionButtonTbl) do if tbl.button then tbl.button:HideActionID() end end end -- action transfer functions function ReActionButton.prototype:ShouldPickupAction(mouseButton) return IsShiftKeyDown() and not SecureButton_GetModifiedAttribute(self.button, "type", mouseButton) end function ReActionButton.prototype:ShowGridTmp() self.showGridTmp_ = self.showGridTmp_ + 1 self:UpdateVisibility() end function ReActionButton.prototype:HideGridTmp() self.showGridTmp_ = self.showGridTmp_ - 1 self:UpdateVisibility() end function ReActionButton.prototype:ShowGrid() self.config.showGrid = true self:UpdateVisibility() end function ReActionButton.prototype:HideGrid() self.config.showGrid = false self:UpdateVisibility() end -- layout & style functions function ReActionButton.prototype:ApplyLayout() local f = self.frames if self.config.keyBindLoc then local h = f.hotkey local loc = self.config.keyBindLoc local top = string.match(loc,"TOP") local bottom = string.match(loc, "BOTTOM") h:ClearAllPoints() h:SetWidth(40) h:SetPoint(top or bottom,0,top and 2 or -2) local j if string.match(loc,"LEFT") then j = "LEFT" elseif string.match(loc,"RIGHT") then j = "RIGHT" else j = "CENTER" end h:SetJustifyH(j) end if self.config.stackCountLoc then local c = f.count local loc = self.config.stackCountLoc local top = string.match(loc,"TOP") local bottom = string.match(loc, "BOTTOM") c:ClearAllPoints() c:SetWidth(40) c:SetPoint(top or bottom,0,top and 2 or -2) local j if string.match(loc,"LEFT") then j = "LEFT" elseif string.match(loc,"RIGHT") then j = "RIGHT" else j = "CENTER" end c:SetJustifyH(j) end if self.config.showKeyBind then f.hotkey:Show() else f.hotkey:Hide() end if self.config.showStackCount then f.count:Show() else f.count:Hide() end --[[ if self.config.showNumericCooldown then f.nCooldown:Show() else f.nCooldown:Hide() end ]] if self.config.showMacroName then f.macro:Show() else f.macro:Hide() end end function ReActionButton.prototype:ApplyStyle() local f = self.frames -- for now, just a static style f.hotkey:SetFontObject(NumberFontNormal) f.count:SetFontObject(NumberFontNormalYellow) end -- start/stop flashing function ReActionButton.prototype:StartFlash() self.flashing = true self.flashtime = 0 self:UpdateCheckedState() end function ReActionButton.prototype:StopFlash() self.flashing = false self.frames.flash:Hide() self:UpdateCheckedState() end function ReActionButton.prototype:IsFlashing() return self.flashing end -- set the tooltip function ReActionButton.prototype:SetTooltip() GameTooltip_SetDefaultAnchor(GameTooltip, self.button) self:UpdateTooltip() end function ReActionButton.prototype:ClearTooltip() tooltipTime = nil GameTooltip:Hide() end -- colorize the hotkey function ReActionButton.prototype:ColorHotKey() local action = self:GetActionID() local c = hotKeyDefaultColor if action and HasAction(action) then if IsActionInRange(action) == 0 then c = hotKeyOutOfRangeColor elseif self.config.keyBindColorCode then local modKey = string.match( self.frames.hotkey:GetText() or "", "([ACS])%-") c = modKey and hotKeyModifierColors[modKey] or c end else c = hotKeyDisabledColor end self.frames.hotkey:SetTextColor(c.r, c.g, c.b) end -- display update functions function ReActionButton.prototype:UpdateDisplay() self:UpdateIcon() self:UpdateHotkey() self:UpdateCount() self:UpdateMacroText() self:UpdateUsable() self:UpdateCooldown() self:UpdateFlash() self:UpdateEvents() self:UpdateVisibility() self:UpdateTooltip() end function ReActionButton.prototype:UpdateIcon() local f = self.frames local b = self.button local action = self:GetActionID() local texture = action and GetActionTexture(action) if action and texture then f.icon:SetTexture(texture) f.icon:Show() self.rangeTimer = -1 b:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2") else f.icon:Hide() f.cooldown:Hide() self.rangeTimer = nil b:SetNormalTexture("Interface\\Buttons\\UI-Quickslot") end self:UpdateCheckedState() -- Add a green border if action is an equipped item if action and IsEquippedAction(action) then local c = equippedActionBorderColor f.border:SetVertexColor(c.r, c.g, c.b, c.a or 1) f.border:Show() else f.border:Hide() end end function ReActionButton.prototype:UpdateCheckedState() local action = self:GetActionID() if action and (IsCurrentAction(action) or IsAutoRepeatAction(action)) then self.button:SetChecked(1) else self.button:SetChecked(0) end end function ReActionButton.prototype:UpdateHotkey() local action = self:GetActionID() local b = self.button local f = self.frames local key = self:GetKeyBinding() local txt = GetBindingText(key, "KEY_",1) -- abbreviate long key names for pat, rep in pairs(keybindAbbreviations) do txt = string.gsub(txt,pat,rep) end if txt then f.hotkey:SetText(string.upper(txt)) self:ColorHotKey() else f.hotkey:SetText("") end end function ReActionButton.prototype:UpdateCount() local action = self:GetActionID() if action and (IsConsumableAction(action) or IsStackableAction(action)) then self.frames.count:SetText(GetActionCount(action)) else self.frames.count:SetText("") end end function ReActionButton.prototype:UpdateMacroText() local action = self:GetActionID() self.frames.macro:SetText(action and GetActionText(action) or "") end function ReActionButton.prototype:UpdateUsable() local f = self.frames local action = self:GetActionID() local isUsable, notEnoughMana if action then isUsable, notEnoughMana = IsUsableAction(action) end if isUsable then local c = actionUsableColor if IsActionInRange(action) == 0 then c = actionOutOfRangeColor else f.normalTexture:SetVertexColor(c.r, c.g, c.b, c.a) end f.icon:SetVertexColor(c.r, c.g, c.b, c.a) elseif notEnoughMana then local c = actionNotEnoughManaColor f.icon:SetVertexColor(c.r, c.g, c.b, c.a) f.normalTexture:SetVertexColor(c.r, c.g, c.b, c.a) else local c = actionNotUsableColor f.icon:SetVertexColor(c.r, c.g, c.b, c.a) f.normalTexture:SetVertexColor(1.0, 1.0, 1.0) end end function ReActionButton.prototype:UpdateCooldown() local action = self:GetActionID() if action then local start, duration, enable = GetActionCooldown(self:GetActionID()) CooldownFrame_SetTimer(self.frames.cooldown, start, duration, enable) -- do numeric cooldown stuff here end end function ReActionButton.prototype:UpdateFlash() local b = self.button local action = self:GetActionID() if action and ((IsAttackAction(action) and IsCurrentAction(action)) or IsAutoRepeatAction(action)) then self:StartFlash() else self:StopFlash() end end function ReActionButton.prototype:UpdateVisibility() local action = self:GetActionID() local b = self.button if b:GetAttribute("statehidden") then b:Hide() elseif action and HasAction(action) then b:GetNormalTexture():SetAlpha(1.0) b:Show() elseif self.showGridTmp_ > 0 or self.config.showGrid then b:GetNormalTexture():SetAlpha(0.5) self.frames.cooldown:Hide() b:Show() else b:Hide() end end function ReActionButton.prototype:UpdateEvents() local action = self:GetActionID() if action and HasAction(action) then self:RegisterActionEvents() elseif self.actionEventsRegistered then self:UnregisterActionEvents() end end function ReActionButton.prototype:UpdateTooltip() local action = self:GetActionID() if GameTooltip:IsOwned(self.button) and action and GameTooltip:SetAction(action) then self.tooltipTime = TOOLTIP_UPDATE_TIME else self.tooltipTime = nil end end