flickerstreak@7: -- flickerstreak@7: -- ReAction is a base class for action button management. It provides a flickerstreak@7: -- framework for button setup, placement, recycling, keybinding, etc. flickerstreak@7: -- It does not define any layout or actual action functionality, flickerstreak@7: -- which is deferred to derived classes. flickerstreak@7: -- flickerstreak@7: -- ReAction implements the ReBar.IButton interface. It is designed to be used with ReBar flickerstreak@7: -- for grouping and laying out buttons. flickerstreak@7: -- flickerstreak@7: -- Each instance of a ReAction-derived object is associated with a single action flickerstreak@7: -- button frame, which may or may not have a one-to-one mapping with actionID. flickerstreak@7: -- flickerstreak@7: -- ReAction makes use of a configuration structure, which can (and should) be flickerstreak@7: -- extended by implementations. A single config structure is shared amongst all flickerstreak@7: -- ReAction class instances within a single ReBar group, so the structure should flickerstreak@7: -- contain sub-tables for any property that needs to be defined on a per-button flickerstreak@7: -- basis. Each button is passed a 'barIdx' parameter to be used as an index into flickerstreak@7: -- such tables. flickerstreak@7: -- flickerstreak@7: -- The base config structure is as follows: flickerstreak@7: -- flickerstreak@7: -- config = { flickerstreak@7: -- type = "ReAction", -- static string (used by ReBar) flickerstreak@7: -- subtype = "string", -- ReAction implementation identifier (index into ReAction.buttonTypes) flickerstreak@21: -- buttons = { flickerstreak@21: -- id = n, -- global button ID number flickerstreak@21: -- actions = { {paged list}, {paged list}, ... } -- indexed by self.barIdx flickerstreak@21: -- } flickerstreak@7: -- } flickerstreak@7: -- flickerstreak@7: flickerstreak@7: flickerstreak@7: local AceOO = AceLibrary("AceOO-2.0") flickerstreak@7: local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc flickerstreak@7: flickerstreak@7: flickerstreak@1: -- private constants flickerstreak@2: -- TODO: localize these key names with GetBindingText(KEY_) flickerstreak@2: local keybindAbbreviations = { flickerstreak@2: ["Mouse Button "] = "M-", flickerstreak@2: ["Spacebar"] = "Sp", flickerstreak@2: ["Num Pad "] = "Num-", flickerstreak@2: ["Page Up"] = "PgUp", flickerstreak@2: ["Page Down"] = "PgDn", flickerstreak@2: [" Arrow"] = "", flickerstreak@2: } flickerstreak@2: flickerstreak@1: flickerstreak@7: ------------------------ flickerstreak@7: -- Interface Declarations flickerstreak@7: ------------------------ flickerstreak@1: flickerstreak@7: -- The ActionType interface defines what the button does when it is clicked. At a flickerstreak@7: -- minimum it must do the equivalent of SetAttribute("type", ...) and handle events that flickerstreak@7: -- cause the action to change. flickerstreak@7: -- ReAction implementations must provide this interface (implicitly or explicitly). flickerstreak@7: local IActionType = AceOO.Interface { flickerstreak@7: SetID = "function", -- SetID(id, [page]) optional argument indicates page #: omitting indicates default. self.config.idx[barIdx] must be updated. flickerstreak@7: GetID = "function", -- id = GetID([page]) optional argument indicates page #: omitting indicates current page flickerstreak@7: IsActionEmpty = "function", -- bool = IsActionEmpty() flickerstreak@7: SetupAction = "function", -- one-time setup flickerstreak@7: UpdateAction = "function", -- general action properties should be refreshed flickerstreak@7: PickupAction = "function", -- pick up the action on the button and put on the cursor flickerstreak@7: PlaceAction = "function", -- place the action on the cursor flickerstreak@7: UpdateTooltip = "function", -- update the tooltip with the action's info flickerstreak@7: } flickerstreak@1: flickerstreak@7: -- The Display interface defines the "look and feel" of the action button. It should define the flickerstreak@7: -- actual widgets and widget layout and provide methods to update the display. flickerstreak@7: -- ReAction implementations must provide this interface (implicitly or explicitly). flickerstreak@7: -- Note that ReAction implementations may also require additional display interfaces to be supported. flickerstreak@7: -- flickerstreak@7: -- Also note: the class 'new' method must take *only* the primary button ID as an argument. flickerstreak@7: local IDisplay = AceOO.Interface { flickerstreak@7: SetupDisplay = "function", -- SetupDisplay(buttonName), one-time setup flickerstreak@7: UpdateDisplay = "function", -- UpdateDisplay(), general display state should be refreshed flickerstreak@7: TempShow = "function", -- TempShow(visible), calls to this can be nested so keep track. flickerstreak@7: GetActionFrame = "function", -- f = GetActionFrame(), return a frame derived from SecureActionButtonTemplate (note: this is inherited unimplemented from ReBar.IButton) flickerstreak@7: GetBaseButtonSize = "function", -- sz = GetBaseButtonSize(), return size in pixels of the nominal button (square) flickerstreak@8: DisplayID = "function", -- DisplayID(show), true/false to show/hide the action ID (or equivalent) flickerstreak@10: DisplayHotkey = "function", -- DisplayHotkey(keyText, button), set the hotkey display text. 2nd argument specifies which button the hotkey is for, default is "LeftButton". flickerstreak@7: } flickerstreak@1: flickerstreak@1: flickerstreak@7: ---------------------------- flickerstreak@7: -- ReAction class definition flickerstreak@7: ---------------------------- flickerstreak@1: flickerstreak@7: ReAction = AceOO.Class("AceEvent-2.0", ReBar.IButton, IActionType, IDisplay) flickerstreak@7: ReAction.virtual = true flickerstreak@7: ReAction.IActionType = IActionType flickerstreak@7: ReAction.IDisplay = IDisplay flickerstreak@1: flickerstreak@1: flickerstreak@7: ----------------------- flickerstreak@7: -- Static class members flickerstreak@7: ----------------------- flickerstreak@7: flickerstreak@7: ReAction.recycler = CreateFrame("Frame",nil,UIParent) flickerstreak@7: ReAction.recycler:SetAllPoints(UIParent) flickerstreak@7: ReAction.recycler:Hide() flickerstreak@7: flickerstreak@7: ReAction.buttonTypes = { } flickerstreak@7: flickerstreak@7: flickerstreak@7: flickerstreak@7: ----------------------- flickerstreak@7: -- Static class methods flickerstreak@7: ----------------------- flickerstreak@7: flickerstreak@7: function ReAction:AddButtonType( name, class, maxIDs ) flickerstreak@7: self.buttonTypes[name] = { subtype = class, maxIDs = maxIDs } flickerstreak@7: end flickerstreak@7: flickerstreak@7: function ReAction:GetButtonType( name ) flickerstreak@7: if name then flickerstreak@7: local t = self.buttonTypes[name] flickerstreak@7: if t then flickerstreak@7: return t.subtype, t.maxIDs flickerstreak@7: end flickerstreak@7: end flickerstreak@7: end flickerstreak@7: flickerstreak@7: function ReAction:GetButtonTypeList() flickerstreak@7: local list = { } flickerstreak@7: for name, _ in pairs(self.buttonTypes) do flickerstreak@7: table.insert(list,name) flickerstreak@7: end flickerstreak@7: return list flickerstreak@7: end flickerstreak@7: flickerstreak@7: function ReAction:GetAvailableID( subtype, hint ) flickerstreak@7: local class, maxIDs = self:GetButtonType(subtype) flickerstreak@7: flickerstreak@7: -- store the list of action buttons in use in the button type class factory flickerstreak@7: if class._idTbl == nil then class._idTbl = { } end flickerstreak@7: local t = class._idTbl flickerstreak@1: local id = nil flickerstreak@7: flickerstreak@7: -- find lowest ID not in use flickerstreak@7: for i = 1, maxIDs do flickerstreak@7: if t[i] == nil or t[i].inUse == false then flickerstreak@1: id = i flickerstreak@1: break flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@7: if id == nil then return nil end -- all action ids are in use flickerstreak@1: flickerstreak@8: -- if a hint is given, see if that one is free instead, as long as it's < maxIDs flickerstreak@8: if hint and hint > 0 and hint <= maxIDs and (t[hint] == nil or t[hint].inUse == false) then flickerstreak@1: id = hint flickerstreak@1: end flickerstreak@1: flickerstreak@7: if t[id] == nil then flickerstreak@7: t[id] = { } flickerstreak@1: end flickerstreak@7: t[id].inUse = true flickerstreak@1: flickerstreak@7: return id, t[id] flickerstreak@7: end flickerstreak@7: flickerstreak@7: function ReAction:Acquire(config, barIdx, pages, buttonsPerPage) flickerstreak@7: local btnType = self:GetButtonType(config.subtype) flickerstreak@7: if not btnType then flickerstreak@7: error("ReAction: Unknown button type specified.") flickerstreak@7: end flickerstreak@7: pages = pages or 1 flickerstreak@7: flickerstreak@7: local ids = { } flickerstreak@7: local primary = nil flickerstreak@7: flickerstreak@7: for i = 1, pages do flickerstreak@21: local hint = config.buttons and config.buttons.actions[barIdx] flickerstreak@7: if hint == nil and i > 1 and ids[i-1] then flickerstreak@7: hint = ids[i-1] + (buttonsPerPage or 0) flickerstreak@7: end flickerstreak@7: local id, p = self:GetAvailableID(config.subtype, hint) flickerstreak@7: if id == nil then flickerstreak@7: break flickerstreak@7: end flickerstreak@7: primary = primary or p flickerstreak@7: if id then flickerstreak@7: ids[i] = id flickerstreak@7: end flickerstreak@1: end flickerstreak@1: flickerstreak@7: if primary then flickerstreak@7: if not primary.button then flickerstreak@7: primary.button = btnType:new(ids[1]) flickerstreak@7: end flickerstreak@7: if primary.button then flickerstreak@21: config.buttons.actions[barIdx] = ids flickerstreak@7: primary.button:Configure(config,barIdx) flickerstreak@7: end flickerstreak@7: end flickerstreak@7: flickerstreak@7: return primary and primary.button flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction:Release( b ) flickerstreak@1: if b then flickerstreak@21: for i = 1, #b.config.buttons.actions[b.barIdx] do flickerstreak@7: local id = b:GetID(i) flickerstreak@7: if id then flickerstreak@7: b.class._idTbl[id].inUse = false flickerstreak@7: end flickerstreak@7: end flickerstreak@7: b.config = nil flickerstreak@1: b:Recycle() flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction:ShowAllIds() flickerstreak@7: for _, t in pairs(self.buttonTypes) do flickerstreak@7: if t.subtype._idTbl then flickerstreak@7: for _, tbl in pairs(t.subtype._idTbl) do flickerstreak@8: if tbl.button then tbl.button:DisplayID(true) end flickerstreak@7: end flickerstreak@7: end flickerstreak@1: end flickerstreak@7: self.showIDs_ = true flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction:HideAllIds() flickerstreak@7: for _, t in pairs(self.buttonTypes) do flickerstreak@7: if t.subtype._idTbl then flickerstreak@7: for _, tbl in pairs(t.subtype._idTbl) do flickerstreak@8: if tbl.button then tbl.button:DisplayID(false) end flickerstreak@7: end flickerstreak@7: end flickerstreak@1: end flickerstreak@7: self.showIDs_ = false flickerstreak@1: end flickerstreak@1: flickerstreak@1: flickerstreak@1: ---------------------- flickerstreak@1: -- Instance methods flickerstreak@1: ---------------------- flickerstreak@1: flickerstreak@7: -- constructor flickerstreak@1: flickerstreak@7: function ReAction.prototype:init() flickerstreak@7: ReAction.super.prototype.init(self) flickerstreak@2: end flickerstreak@2: flickerstreak@7: flickerstreak@7: -- ReBar.IButton interface flickerstreak@7: flickerstreak@7: function ReAction.prototype:BarUnlocked() flickerstreak@7: self:TempShow(true) flickerstreak@8: self:DisplayID(true) flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:BarLocked() flickerstreak@7: self:TempShow(false) flickerstreak@8: self:DisplayID(false) flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:PlaceButton(parent, point, x, y, sz) flickerstreak@7: local b = self:GetActionFrame() flickerstreak@7: local baseSize = self:GetBaseButtonSize() flickerstreak@7: local scale = baseSize and baseSize ~= 0 and ((sz or 1) / baseSize) or 1 flickerstreak@7: if scale == 0 then scale = 1 end flickerstreak@1: b:ClearAllPoints() flickerstreak@7: b:SetParent(parent) flickerstreak@7: b:SetScale(scale) flickerstreak@1: b:SetPoint(point,x/scale,y/scale) flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:SetPages( n ) flickerstreak@7: n = tonumber(n) flickerstreak@21: local ids = self.config.buttons.actions[self.barIdx] flickerstreak@7: if n and n >= 1 then flickerstreak@7: -- note that as long as n >= 1 then id[1] will never be modified, which is what we want flickerstreak@7: -- because then the button frame ID would be out of sync with the static button name flickerstreak@7: while #ids < n do flickerstreak@21: local id = ReAction:GetAvailableID(self.config.subtype, ids[#ids] + #self.config.buttons.actions) flickerstreak@7: if id == nil then flickerstreak@7: break flickerstreak@1: end flickerstreak@7: self:SetID( id, #ids + 1 ) flickerstreak@7: table.insert(ids, id) flickerstreak@7: end flickerstreak@7: while #ids > n do flickerstreak@7: local id = table.remove(ids) flickerstreak@7: self:SetID( nil, #ids + 1 ) flickerstreak@7: self.class._idTbl[id].inUse = false flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@7: -- Event handlers flickerstreak@1: flickerstreak@7: function ReAction.prototype:UPDATE_BINDINGS() flickerstreak@10: self:DisplayHotkey(self:GetKeyBindingText("LeftButton", true),"LeftButton") flickerstreak@10: self:DisplayHotkey(self:GetKeyBindingText("RightButton",true),"RightButton") flickerstreak@7: end flickerstreak@1: flickerstreak@1: flickerstreak@7: -- Internal functions flickerstreak@7: flickerstreak@7: function ReAction.prototype:Recycle() flickerstreak@7: self:UnregisterAllEvents() flickerstreak@7: flickerstreak@7: -- tuck the frame away flickerstreak@7: local b = self:GetActionFrame() flickerstreak@7: b:SetParent(ReAction.recycler) flickerstreak@7: b:ClearAllPoints() flickerstreak@7: b:SetPoint("TOPLEFT",0,0) flickerstreak@7: b:Hide() flickerstreak@7: end flickerstreak@7: flickerstreak@7: function ReAction.prototype:Configure( config, barIdx ) flickerstreak@7: self.config = config flickerstreak@7: self.barIdx = barIdx flickerstreak@7: flickerstreak@21: local ids = config.buttons.actions[barIdx] flickerstreak@7: self:SetID(ids[1]) -- default id flickerstreak@7: for i = 1, #ids do flickerstreak@7: self:SetID(ids[i], i) -- paged ids flickerstreak@7: end flickerstreak@7: self:UpdateAction() flickerstreak@7: self:UpdateDisplay() flickerstreak@7: self:DisplayHotkey(self:GetKeyBindingText(nil, true)) flickerstreak@7: flickerstreak@7: self:RegisterEvent("UPDATE_BINDINGS") flickerstreak@7: end flickerstreak@7: flickerstreak@7: function ReAction.prototype:SetKeyBinding( k, mouseBtn ) flickerstreak@1: if k == nil or kbValidate(k) then flickerstreak@7: local current = self:GetKeyBinding(mouseBtn) flickerstreak@1: if current then flickerstreak@1: SetBinding(current,nil) flickerstreak@1: end flickerstreak@1: if k then flickerstreak@7: SetBindingClick(k, self:GetActionFrame():GetName(), mouseBtn or "LeftButton") flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:GetKeyBinding( mouseBtn ) flickerstreak@7: return GetBindingKey("CLICK "..self:GetActionFrame():GetName()..":"..(mouseBtn or "LeftButton")) flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:GetKeyBindingText( mouseBtn, abbrev ) flickerstreak@7: local key = self:GetKeyBinding(mouseBtn) flickerstreak@7: local txt = key and GetBindingText(key, "KEY_", abbrev and 1) or "" flickerstreak@7: flickerstreak@7: if txt and abbrev then flickerstreak@7: -- further abbreviate some key names flickerstreak@7: for pat, rep in pairs(keybindAbbreviations) do flickerstreak@7: txt = string.gsub(txt,pat,rep) flickerstreak@7: end flickerstreak@7: end flickerstreak@7: return txt flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:SetTooltip() flickerstreak@7: GameTooltip_SetDefaultAnchor(GameTooltip, self:GetActionFrame()) flickerstreak@1: self:UpdateTooltip() flickerstreak@1: end flickerstreak@1: flickerstreak@7: function ReAction.prototype:ClearTooltip() flickerstreak@1: tooltipTime = nil flickerstreak@1: GameTooltip:Hide() flickerstreak@1: end