flickerstreak@7
|
1 --
|
flickerstreak@7
|
2 -- ReAction is a base class for action button management. It provides a
|
flickerstreak@7
|
3 -- framework for button setup, placement, recycling, keybinding, etc.
|
flickerstreak@7
|
4 -- It does not define any layout or actual action functionality,
|
flickerstreak@7
|
5 -- which is deferred to derived classes.
|
flickerstreak@7
|
6 --
|
flickerstreak@7
|
7 -- ReAction implements the ReBar.IButton interface. It is designed to be used with ReBar
|
flickerstreak@7
|
8 -- for grouping and laying out buttons.
|
flickerstreak@7
|
9 --
|
flickerstreak@7
|
10 -- ReAction supports the ReBound keybinding interface (only).
|
flickerstreak@7
|
11 --
|
flickerstreak@7
|
12 -- Each instance of a ReAction-derived object is associated with a single action
|
flickerstreak@7
|
13 -- button frame, which may or may not have a one-to-one mapping with actionID.
|
flickerstreak@7
|
14 --
|
flickerstreak@7
|
15 -- ReAction makes use of a configuration structure, which can (and should) be
|
flickerstreak@7
|
16 -- extended by implementations. A single config structure is shared amongst all
|
flickerstreak@7
|
17 -- ReAction class instances within a single ReBar group, so the structure should
|
flickerstreak@7
|
18 -- contain sub-tables for any property that needs to be defined on a per-button
|
flickerstreak@7
|
19 -- basis. Each button is passed a 'barIdx' parameter to be used as an index into
|
flickerstreak@7
|
20 -- such tables.
|
flickerstreak@7
|
21 --
|
flickerstreak@7
|
22 -- The base config structure is as follows:
|
flickerstreak@7
|
23 --
|
flickerstreak@7
|
24 -- config = {
|
flickerstreak@7
|
25 -- type = "ReAction", -- static string (used by ReBar)
|
flickerstreak@7
|
26 -- subtype = "string", -- ReAction implementation identifier (index into ReAction.buttonTypes)
|
flickerstreak@7
|
27 -- ids = { {paged list}, {paged list}, ... } -- indexed by self.barIdx
|
flickerstreak@7
|
28 -- }
|
flickerstreak@7
|
29 --
|
flickerstreak@7
|
30
|
flickerstreak@7
|
31
|
flickerstreak@7
|
32 local AceOO = AceLibrary("AceOO-2.0")
|
flickerstreak@7
|
33 local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc
|
flickerstreak@7
|
34
|
flickerstreak@7
|
35
|
flickerstreak@1
|
36 -- private constants
|
flickerstreak@2
|
37 -- TODO: localize these key names with GetBindingText(KEY_)
|
flickerstreak@2
|
38 local keybindAbbreviations = {
|
flickerstreak@2
|
39 ["Mouse Button "] = "M-",
|
flickerstreak@2
|
40 ["Spacebar"] = "Sp",
|
flickerstreak@2
|
41 ["Num Pad "] = "Num-",
|
flickerstreak@2
|
42 ["Page Up"] = "PgUp",
|
flickerstreak@2
|
43 ["Page Down"] = "PgDn",
|
flickerstreak@2
|
44 [" Arrow"] = "",
|
flickerstreak@2
|
45 }
|
flickerstreak@2
|
46
|
flickerstreak@1
|
47
|
flickerstreak@7
|
48 ------------------------
|
flickerstreak@7
|
49 -- Interface Declarations
|
flickerstreak@7
|
50 ------------------------
|
flickerstreak@1
|
51
|
flickerstreak@7
|
52 -- The ActionType interface defines what the button does when it is clicked. At a
|
flickerstreak@7
|
53 -- minimum it must do the equivalent of SetAttribute("type", ...) and handle events that
|
flickerstreak@7
|
54 -- cause the action to change.
|
flickerstreak@7
|
55 -- ReAction implementations must provide this interface (implicitly or explicitly).
|
flickerstreak@7
|
56 local IActionType = AceOO.Interface {
|
flickerstreak@7
|
57 SetID = "function", -- SetID(id, [page]) optional argument indicates page #: omitting indicates default. self.config.idx[barIdx] must be updated.
|
flickerstreak@7
|
58 GetID = "function", -- id = GetID([page]) optional argument indicates page #: omitting indicates current page
|
flickerstreak@7
|
59 IsActionEmpty = "function", -- bool = IsActionEmpty()
|
flickerstreak@7
|
60 SetupAction = "function", -- one-time setup
|
flickerstreak@7
|
61 UpdateAction = "function", -- general action properties should be refreshed
|
flickerstreak@7
|
62 PickupAction = "function", -- pick up the action on the button and put on the cursor
|
flickerstreak@7
|
63 PlaceAction = "function", -- place the action on the cursor
|
flickerstreak@7
|
64 UpdateTooltip = "function", -- update the tooltip with the action's info
|
flickerstreak@7
|
65 }
|
flickerstreak@1
|
66
|
flickerstreak@7
|
67 -- The Display interface defines the "look and feel" of the action button. It should define the
|
flickerstreak@7
|
68 -- actual widgets and widget layout and provide methods to update the display.
|
flickerstreak@7
|
69 -- ReAction implementations must provide this interface (implicitly or explicitly).
|
flickerstreak@7
|
70 -- Note that ReAction implementations may also require additional display interfaces to be supported.
|
flickerstreak@7
|
71 --
|
flickerstreak@7
|
72 -- Also note: the class 'new' method must take *only* the primary button ID as an argument.
|
flickerstreak@7
|
73 local IDisplay = AceOO.Interface {
|
flickerstreak@7
|
74 SetupDisplay = "function", -- SetupDisplay(buttonName), one-time setup
|
flickerstreak@7
|
75 UpdateDisplay = "function", -- UpdateDisplay(), general display state should be refreshed
|
flickerstreak@7
|
76 TempShow = "function", -- TempShow(visible), calls to this can be nested so keep track.
|
flickerstreak@7
|
77 GetActionFrame = "function", -- f = GetActionFrame(), return a frame derived from SecureActionButtonTemplate (note: this is inherited unimplemented from ReBar.IButton)
|
flickerstreak@7
|
78 GetBaseButtonSize = "function", -- sz = GetBaseButtonSize(), return size in pixels of the nominal button (square)
|
flickerstreak@8
|
79 DisplayID = "function", -- DisplayID(show), true/false to show/hide the action ID (or equivalent)
|
flickerstreak@7
|
80 DisplayHotkey = "function", -- DisplayHotkey(keyText), set the hotkey display text
|
flickerstreak@7
|
81 }
|
flickerstreak@1
|
82
|
flickerstreak@1
|
83
|
flickerstreak@7
|
84 ----------------------------
|
flickerstreak@7
|
85 -- ReAction class definition
|
flickerstreak@7
|
86 ----------------------------
|
flickerstreak@1
|
87
|
flickerstreak@7
|
88 ReAction = AceOO.Class("AceEvent-2.0", ReBar.IButton, IActionType, IDisplay)
|
flickerstreak@7
|
89 ReAction.virtual = true
|
flickerstreak@7
|
90 ReAction.IActionType = IActionType
|
flickerstreak@7
|
91 ReAction.IDisplay = IDisplay
|
flickerstreak@1
|
92
|
flickerstreak@1
|
93
|
flickerstreak@7
|
94 -----------------------
|
flickerstreak@7
|
95 -- Static class members
|
flickerstreak@7
|
96 -----------------------
|
flickerstreak@7
|
97
|
flickerstreak@7
|
98 ReAction.recycler = CreateFrame("Frame",nil,UIParent)
|
flickerstreak@7
|
99 ReAction.recycler:SetAllPoints(UIParent)
|
flickerstreak@7
|
100 ReAction.recycler:Hide()
|
flickerstreak@7
|
101
|
flickerstreak@7
|
102 ReAction.buttonTypes = { }
|
flickerstreak@7
|
103
|
flickerstreak@7
|
104
|
flickerstreak@7
|
105
|
flickerstreak@7
|
106 -----------------------
|
flickerstreak@7
|
107 -- Static class methods
|
flickerstreak@7
|
108 -----------------------
|
flickerstreak@7
|
109
|
flickerstreak@7
|
110 function ReAction:AddButtonType( name, class, maxIDs )
|
flickerstreak@7
|
111 self.buttonTypes[name] = { subtype = class, maxIDs = maxIDs }
|
flickerstreak@7
|
112 end
|
flickerstreak@7
|
113
|
flickerstreak@7
|
114 function ReAction:GetButtonType( name )
|
flickerstreak@7
|
115 if name then
|
flickerstreak@7
|
116 local t = self.buttonTypes[name]
|
flickerstreak@7
|
117 if t then
|
flickerstreak@7
|
118 return t.subtype, t.maxIDs
|
flickerstreak@7
|
119 end
|
flickerstreak@7
|
120 end
|
flickerstreak@7
|
121 end
|
flickerstreak@7
|
122
|
flickerstreak@7
|
123 function ReAction:GetButtonTypeList()
|
flickerstreak@7
|
124 local list = { }
|
flickerstreak@7
|
125 for name, _ in pairs(self.buttonTypes) do
|
flickerstreak@7
|
126 table.insert(list,name)
|
flickerstreak@7
|
127 end
|
flickerstreak@7
|
128 return list
|
flickerstreak@7
|
129 end
|
flickerstreak@7
|
130
|
flickerstreak@7
|
131 function ReAction:GetAvailableID( subtype, hint )
|
flickerstreak@7
|
132 local class, maxIDs = self:GetButtonType(subtype)
|
flickerstreak@7
|
133
|
flickerstreak@7
|
134 -- store the list of action buttons in use in the button type class factory
|
flickerstreak@7
|
135 if class._idTbl == nil then class._idTbl = { } end
|
flickerstreak@7
|
136 local t = class._idTbl
|
flickerstreak@1
|
137 local id = nil
|
flickerstreak@7
|
138
|
flickerstreak@7
|
139 -- find lowest ID not in use
|
flickerstreak@7
|
140 for i = 1, maxIDs do
|
flickerstreak@7
|
141 if t[i] == nil or t[i].inUse == false then
|
flickerstreak@1
|
142 id = i
|
flickerstreak@1
|
143 break
|
flickerstreak@1
|
144 end
|
flickerstreak@1
|
145 end
|
flickerstreak@1
|
146
|
flickerstreak@7
|
147 if id == nil then return nil end -- all action ids are in use
|
flickerstreak@1
|
148
|
flickerstreak@8
|
149 -- if a hint is given, see if that one is free instead, as long as it's < maxIDs
|
flickerstreak@8
|
150 if hint and hint > 0 and hint <= maxIDs and (t[hint] == nil or t[hint].inUse == false) then
|
flickerstreak@1
|
151 id = hint
|
flickerstreak@1
|
152 end
|
flickerstreak@1
|
153
|
flickerstreak@7
|
154 if t[id] == nil then
|
flickerstreak@7
|
155 t[id] = { }
|
flickerstreak@1
|
156 end
|
flickerstreak@7
|
157 t[id].inUse = true
|
flickerstreak@1
|
158
|
flickerstreak@7
|
159 return id, t[id]
|
flickerstreak@7
|
160 end
|
flickerstreak@7
|
161
|
flickerstreak@7
|
162 function ReAction:Acquire(config, barIdx, pages, buttonsPerPage)
|
flickerstreak@7
|
163 local btnType = self:GetButtonType(config.subtype)
|
flickerstreak@7
|
164 if not btnType then
|
flickerstreak@7
|
165 error("ReAction: Unknown button type specified.")
|
flickerstreak@7
|
166 end
|
flickerstreak@7
|
167 pages = pages or 1
|
flickerstreak@7
|
168
|
flickerstreak@7
|
169 local ids = { }
|
flickerstreak@7
|
170 local primary = nil
|
flickerstreak@7
|
171
|
flickerstreak@7
|
172 for i = 1, pages do
|
flickerstreak@7
|
173 local hint = config.ids[barIdx] and config.ids[barIdx][i]
|
flickerstreak@7
|
174 if hint == nil and i > 1 and ids[i-1] then
|
flickerstreak@7
|
175 hint = ids[i-1] + (buttonsPerPage or 0)
|
flickerstreak@7
|
176 end
|
flickerstreak@7
|
177 local id, p = self:GetAvailableID(config.subtype, hint)
|
flickerstreak@7
|
178 if id == nil then
|
flickerstreak@7
|
179 break
|
flickerstreak@7
|
180 end
|
flickerstreak@7
|
181 primary = primary or p
|
flickerstreak@7
|
182 if id then
|
flickerstreak@7
|
183 ids[i] = id
|
flickerstreak@7
|
184 end
|
flickerstreak@1
|
185 end
|
flickerstreak@1
|
186
|
flickerstreak@7
|
187 if primary then
|
flickerstreak@7
|
188 if not primary.button then
|
flickerstreak@7
|
189 primary.button = btnType:new(ids[1])
|
flickerstreak@7
|
190 end
|
flickerstreak@7
|
191 if primary.button then
|
flickerstreak@7
|
192 config.ids[barIdx] = ids
|
flickerstreak@7
|
193 primary.button:Configure(config,barIdx)
|
flickerstreak@7
|
194 end
|
flickerstreak@7
|
195 end
|
flickerstreak@7
|
196
|
flickerstreak@7
|
197 return primary and primary.button
|
flickerstreak@1
|
198 end
|
flickerstreak@1
|
199
|
flickerstreak@7
|
200 function ReAction:Release( b )
|
flickerstreak@1
|
201 if b then
|
flickerstreak@7
|
202 for i = 1, #b.config.ids[b.barIdx] do
|
flickerstreak@7
|
203 local id = b:GetID(i)
|
flickerstreak@7
|
204 if id then
|
flickerstreak@7
|
205 b.class._idTbl[id].inUse = false
|
flickerstreak@7
|
206 end
|
flickerstreak@7
|
207 end
|
flickerstreak@7
|
208 b.config = nil
|
flickerstreak@1
|
209 b:Recycle()
|
flickerstreak@1
|
210 end
|
flickerstreak@1
|
211 end
|
flickerstreak@1
|
212
|
flickerstreak@7
|
213 function ReAction:ShowAllIds()
|
flickerstreak@7
|
214 for _, t in pairs(self.buttonTypes) do
|
flickerstreak@7
|
215 if t.subtype._idTbl then
|
flickerstreak@7
|
216 for _, tbl in pairs(t.subtype._idTbl) do
|
flickerstreak@8
|
217 if tbl.button then tbl.button:DisplayID(true) end
|
flickerstreak@7
|
218 end
|
flickerstreak@7
|
219 end
|
flickerstreak@1
|
220 end
|
flickerstreak@7
|
221 self.showIDs_ = true
|
flickerstreak@1
|
222 end
|
flickerstreak@1
|
223
|
flickerstreak@7
|
224 function ReAction:HideAllIds()
|
flickerstreak@7
|
225 for _, t in pairs(self.buttonTypes) do
|
flickerstreak@7
|
226 if t.subtype._idTbl then
|
flickerstreak@7
|
227 for _, tbl in pairs(t.subtype._idTbl) do
|
flickerstreak@8
|
228 if tbl.button then tbl.button:DisplayID(false) end
|
flickerstreak@7
|
229 end
|
flickerstreak@7
|
230 end
|
flickerstreak@1
|
231 end
|
flickerstreak@7
|
232 self.showIDs_ = false
|
flickerstreak@1
|
233 end
|
flickerstreak@1
|
234
|
flickerstreak@1
|
235
|
flickerstreak@1
|
236 ----------------------
|
flickerstreak@1
|
237 -- Instance methods
|
flickerstreak@1
|
238 ----------------------
|
flickerstreak@1
|
239
|
flickerstreak@7
|
240 -- constructor
|
flickerstreak@1
|
241
|
flickerstreak@7
|
242 function ReAction.prototype:init()
|
flickerstreak@7
|
243 ReAction.super.prototype.init(self)
|
flickerstreak@2
|
244 end
|
flickerstreak@2
|
245
|
flickerstreak@7
|
246
|
flickerstreak@7
|
247 -- ReBar.IButton interface
|
flickerstreak@7
|
248
|
flickerstreak@7
|
249 function ReAction.prototype:BarUnlocked()
|
flickerstreak@7
|
250 self:TempShow(true)
|
flickerstreak@8
|
251 self:DisplayID(true)
|
flickerstreak@1
|
252 end
|
flickerstreak@1
|
253
|
flickerstreak@7
|
254 function ReAction.prototype:BarLocked()
|
flickerstreak@7
|
255 self:TempShow(false)
|
flickerstreak@8
|
256 self:DisplayID(false)
|
flickerstreak@1
|
257 end
|
flickerstreak@1
|
258
|
flickerstreak@7
|
259 function ReAction.prototype:PlaceButton(parent, point, x, y, sz)
|
flickerstreak@7
|
260 local b = self:GetActionFrame()
|
flickerstreak@7
|
261 local baseSize = self:GetBaseButtonSize()
|
flickerstreak@7
|
262 local scale = baseSize and baseSize ~= 0 and ((sz or 1) / baseSize) or 1
|
flickerstreak@7
|
263 if scale == 0 then scale = 1 end
|
flickerstreak@1
|
264 b:ClearAllPoints()
|
flickerstreak@7
|
265 b:SetParent(parent)
|
flickerstreak@7
|
266 b:SetScale(scale)
|
flickerstreak@1
|
267 b:SetPoint(point,x/scale,y/scale)
|
flickerstreak@1
|
268 end
|
flickerstreak@1
|
269
|
flickerstreak@7
|
270 function ReAction.prototype:SetPages( n )
|
flickerstreak@7
|
271 n = tonumber(n)
|
flickerstreak@7
|
272 local ids = self.config.ids[self.barIdx]
|
flickerstreak@7
|
273 if n and n >= 1 then
|
flickerstreak@7
|
274 -- note that as long as n >= 1 then id[1] will never be modified, which is what we want
|
flickerstreak@7
|
275 -- because then the button frame ID would be out of sync with the static button name
|
flickerstreak@7
|
276 while #ids < n do
|
flickerstreak@7
|
277 local id = ReAction:GetAvailableID(self.config.subtype, ids[#ids] + #self.config.ids)
|
flickerstreak@7
|
278 if id == nil then
|
flickerstreak@7
|
279 break
|
flickerstreak@1
|
280 end
|
flickerstreak@7
|
281 self:SetID( id, #ids + 1 )
|
flickerstreak@7
|
282 table.insert(ids, id)
|
flickerstreak@7
|
283 end
|
flickerstreak@7
|
284 while #ids > n do
|
flickerstreak@7
|
285 local id = table.remove(ids)
|
flickerstreak@7
|
286 self:SetID( nil, #ids + 1 )
|
flickerstreak@7
|
287 self.class._idTbl[id].inUse = false
|
flickerstreak@1
|
288 end
|
flickerstreak@1
|
289 end
|
flickerstreak@1
|
290 end
|
flickerstreak@1
|
291
|
flickerstreak@7
|
292 -- Event handlers
|
flickerstreak@1
|
293
|
flickerstreak@7
|
294 function ReAction.prototype:UPDATE_BINDINGS()
|
flickerstreak@7
|
295 self:DisplayHotkey(self:GetKeyBindingText(nil, true))
|
flickerstreak@7
|
296 end
|
flickerstreak@1
|
297
|
flickerstreak@1
|
298
|
flickerstreak@7
|
299 -- Internal functions
|
flickerstreak@7
|
300
|
flickerstreak@7
|
301 function ReAction.prototype:Recycle()
|
flickerstreak@7
|
302 --self:SetKeyBinding(nil) -- TODO: only if we're using override bindings
|
flickerstreak@7
|
303 self:UnregisterAllEvents()
|
flickerstreak@7
|
304
|
flickerstreak@7
|
305 -- tuck the frame away
|
flickerstreak@7
|
306 local b = self:GetActionFrame()
|
flickerstreak@7
|
307 b:SetParent(ReAction.recycler)
|
flickerstreak@7
|
308 b:ClearAllPoints()
|
flickerstreak@7
|
309 b:SetPoint("TOPLEFT",0,0)
|
flickerstreak@7
|
310 b:Hide()
|
flickerstreak@7
|
311 end
|
flickerstreak@7
|
312
|
flickerstreak@7
|
313 function ReAction.prototype:Configure( config, barIdx )
|
flickerstreak@7
|
314 self.config = config
|
flickerstreak@7
|
315 self.barIdx = barIdx
|
flickerstreak@7
|
316
|
flickerstreak@7
|
317 local ids = config.ids[barIdx]
|
flickerstreak@7
|
318 self:SetID(ids[1]) -- default id
|
flickerstreak@7
|
319 for i = 1, #ids do
|
flickerstreak@7
|
320 self:SetID(ids[i], i) -- paged ids
|
flickerstreak@7
|
321 end
|
flickerstreak@7
|
322 self:UpdateAction()
|
flickerstreak@7
|
323 self:UpdateDisplay()
|
flickerstreak@7
|
324 self:DisplayHotkey(self:GetKeyBindingText(nil, true))
|
flickerstreak@7
|
325
|
flickerstreak@7
|
326 self:RegisterEvent("UPDATE_BINDINGS")
|
flickerstreak@7
|
327 end
|
flickerstreak@7
|
328
|
flickerstreak@7
|
329 function ReAction.prototype:SetKeyBinding( k, mouseBtn )
|
flickerstreak@1
|
330 if k == nil or kbValidate(k) then
|
flickerstreak@7
|
331 local current = self:GetKeyBinding(mouseBtn)
|
flickerstreak@7
|
332 -- !!!TODO: do we need this?
|
flickerstreak@7
|
333 -- ClearOverrideBindings(self:GetActionFrame())
|
flickerstreak@1
|
334 if current then
|
flickerstreak@1
|
335 SetBinding(current,nil)
|
flickerstreak@1
|
336 end
|
flickerstreak@1
|
337 if k then
|
flickerstreak@7
|
338 -- TODO: use OverrideBinding and store the keybinding in the profile.
|
flickerstreak@7
|
339 SetBindingClick(k, self:GetActionFrame():GetName(), mouseBtn or "LeftButton")
|
flickerstreak@1
|
340 end
|
flickerstreak@1
|
341 end
|
flickerstreak@1
|
342 end
|
flickerstreak@1
|
343
|
flickerstreak@7
|
344 function ReAction.prototype:GetKeyBinding( mouseBtn )
|
flickerstreak@7
|
345 return GetBindingKey("CLICK "..self:GetActionFrame():GetName()..":"..(mouseBtn or "LeftButton"))
|
flickerstreak@1
|
346 end
|
flickerstreak@1
|
347
|
flickerstreak@7
|
348 function ReAction.prototype:GetKeyBindingText( mouseBtn, abbrev )
|
flickerstreak@7
|
349 local key = self:GetKeyBinding(mouseBtn)
|
flickerstreak@7
|
350 local txt = key and GetBindingText(key, "KEY_", abbrev and 1) or ""
|
flickerstreak@7
|
351
|
flickerstreak@7
|
352 if txt and abbrev then
|
flickerstreak@7
|
353 -- further abbreviate some key names
|
flickerstreak@7
|
354 for pat, rep in pairs(keybindAbbreviations) do
|
flickerstreak@7
|
355 txt = string.gsub(txt,pat,rep)
|
flickerstreak@7
|
356 end
|
flickerstreak@7
|
357 end
|
flickerstreak@7
|
358 return txt
|
flickerstreak@1
|
359 end
|
flickerstreak@1
|
360
|
flickerstreak@7
|
361 function ReAction.prototype:SetTooltip()
|
flickerstreak@7
|
362 GameTooltip_SetDefaultAnchor(GameTooltip, self:GetActionFrame())
|
flickerstreak@1
|
363 self:UpdateTooltip()
|
flickerstreak@1
|
364 end
|
flickerstreak@1
|
365
|
flickerstreak@7
|
366 function ReAction.prototype:ClearTooltip()
|
flickerstreak@1
|
367 tooltipTime = nil
|
flickerstreak@1
|
368 GameTooltip:Hide()
|
flickerstreak@1
|
369 end
|