flickerstreak@25
|
1 local ReAction = ReAction
|
flickerstreak@25
|
2 local L = ReAction.L
|
flickerstreak@25
|
3 local _G = _G
|
flickerstreak@25
|
4 local CreateFrame = CreateFrame
|
flickerstreak@33
|
5 local floor = math.floor
|
flickerstreak@71
|
6 local SecureStateHeader_Refresh = SecureStateHeader_Refresh
|
flickerstreak@33
|
7
|
flickerstreak@33
|
8
|
flickerstreak@25
|
9
|
flickerstreak@25
|
10 -- update ReAction revision if this file is newer
|
flickerstreak@33
|
11 local revision = tonumber(("$Revision$"):match("%d+"))
|
flickerstreak@25
|
12 if revision > ReAction.revision then
|
flickerstreak@52
|
13 ReAction.revision = revision
|
flickerstreak@25
|
14 end
|
flickerstreak@25
|
15
|
flickerstreak@28
|
16 ------ BAR CLASS ------
|
flickerstreak@28
|
17 local Bar = { _classID = {} }
|
flickerstreak@25
|
18
|
flickerstreak@28
|
19 local function Constructor( self, name, config )
|
flickerstreak@25
|
20 self.name, self.config = name, config
|
flickerstreak@68
|
21 self.buttons = setmetatable({},{__mode="k"})
|
flickerstreak@25
|
22
|
flickerstreak@25
|
23 if type(config) ~= "table" then
|
flickerstreak@28
|
24 error("ReAction.Bar: config table required")
|
flickerstreak@25
|
25 end
|
flickerstreak@25
|
26
|
flickerstreak@54
|
27 local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent
|
flickerstreak@68
|
28 local f = CreateFrame("Frame",nil,parent,"SecureStateHeaderTemplate")
|
flickerstreak@25
|
29 f:SetFrameStrata("MEDIUM")
|
flickerstreak@30
|
30 config.width = config.width or 480
|
flickerstreak@30
|
31 config.height = config.height or 40
|
flickerstreak@25
|
32 f:SetWidth(config.width)
|
flickerstreak@25
|
33 f:SetWidth(config.height)
|
flickerstreak@25
|
34
|
flickerstreak@63
|
35 ReAction.RegisterCallback(self, "OnConfigModeChanged")
|
flickerstreak@63
|
36
|
flickerstreak@25
|
37 self.frame = f
|
flickerstreak@25
|
38 self:ApplyAnchor()
|
flickerstreak@25
|
39 f:Show()
|
flickerstreak@68
|
40 self:RefreshLayout()
|
flickerstreak@25
|
41 end
|
flickerstreak@25
|
42
|
flickerstreak@25
|
43 function Bar:Destroy()
|
flickerstreak@25
|
44 local f = self.frame
|
flickerstreak@25
|
45 f:UnregisterAllEvents()
|
flickerstreak@25
|
46 f:Hide()
|
flickerstreak@25
|
47 f:SetParent(UIParent)
|
flickerstreak@25
|
48 f:ClearAllPoints()
|
flickerstreak@63
|
49 ReAction.UnregisterAllCallbacks(self)
|
flickerstreak@72
|
50 if self.statedriver then
|
flickerstreak@72
|
51 UnregisterStateDriver(f, "reaction")
|
flickerstreak@72
|
52 end
|
flickerstreak@25
|
53 self.labelString = nil
|
flickerstreak@25
|
54 self.controlFrame = nil
|
flickerstreak@25
|
55 self.frame = nil
|
flickerstreak@25
|
56 self.config = nil
|
flickerstreak@25
|
57 end
|
flickerstreak@25
|
58
|
flickerstreak@63
|
59 function Bar:OnConfigModeChanged(event, mode)
|
flickerstreak@73
|
60 self:ShowControls(mode) -- ShowControls() defined in Overlay.lua
|
flickerstreak@63
|
61 end
|
flickerstreak@63
|
62
|
flickerstreak@25
|
63 function Bar:RefreshLayout()
|
flickerstreak@63
|
64 ReAction:RefreshBar(self)
|
flickerstreak@25
|
65 end
|
flickerstreak@25
|
66
|
flickerstreak@25
|
67 function Bar:ApplyAnchor()
|
flickerstreak@25
|
68 local f, config = self.frame, self.config
|
flickerstreak@25
|
69 f:SetWidth(config.width)
|
flickerstreak@25
|
70 f:SetHeight(config.height)
|
flickerstreak@25
|
71 local anchor = config.anchor
|
flickerstreak@51
|
72 f:ClearAllPoints()
|
flickerstreak@25
|
73 if anchor then
|
flickerstreak@52
|
74 local anchorTo = f:GetParent()
|
flickerstreak@25
|
75 if config.anchorTo then
|
flickerstreak@52
|
76 local bar = ReAction:GetBar(config.anchorTo)
|
flickerstreak@52
|
77 if bar then
|
flickerstreak@52
|
78 anchorTo = bar:GetFrame()
|
flickerstreak@52
|
79 else
|
flickerstreak@52
|
80 anchorTo = _G[config.anchorTo]
|
flickerstreak@52
|
81 end
|
flickerstreak@25
|
82 end
|
flickerstreak@52
|
83 f:SetPoint(anchor, anchorTo or f:GetParent(), config.relativePoint, config.x or 0, config.y or 0)
|
flickerstreak@25
|
84 else
|
flickerstreak@25
|
85 f:SetPoint("CENTER")
|
flickerstreak@25
|
86 end
|
flickerstreak@25
|
87 end
|
flickerstreak@25
|
88
|
flickerstreak@51
|
89 function Bar:SetAnchor(point, frame, relativePoint, x, y)
|
flickerstreak@51
|
90 local c = self.config
|
flickerstreak@51
|
91 c.anchor = point or c.anchor
|
flickerstreak@51
|
92 c.anchorTo = frame and frame:GetName() or c.anchorTo
|
flickerstreak@51
|
93 c.relativePoint = relativePoint or c.relativePoint
|
flickerstreak@51
|
94 c.x = x or c.x
|
flickerstreak@51
|
95 c.y = y or c.y
|
flickerstreak@51
|
96 self:ApplyAnchor()
|
flickerstreak@51
|
97 end
|
flickerstreak@51
|
98
|
flickerstreak@51
|
99 function Bar:GetAnchor()
|
flickerstreak@51
|
100 local c = self.config
|
flickerstreak@51
|
101 return (c.anchor or "CENTER"), (c.anchorTo or self.frame:GetParent():GetName()), (c.relativePoint or c.anchor or "CENTER"), (c.x or 0), (c.y or 0)
|
flickerstreak@51
|
102 end
|
flickerstreak@51
|
103
|
flickerstreak@25
|
104 function Bar:GetFrame()
|
flickerstreak@25
|
105 return self.frame
|
flickerstreak@25
|
106 end
|
flickerstreak@25
|
107
|
flickerstreak@25
|
108 function Bar:GetSize()
|
flickerstreak@25
|
109 return self.frame:GetWidth() or 200, self.frame:GetHeight() or 200
|
flickerstreak@25
|
110 end
|
flickerstreak@25
|
111
|
flickerstreak@25
|
112 function Bar:SetSize(w,h)
|
flickerstreak@25
|
113 self.config.width = w
|
flickerstreak@25
|
114 self.config.height = h
|
flickerstreak@25
|
115 end
|
flickerstreak@25
|
116
|
flickerstreak@25
|
117 function Bar:GetButtonSize()
|
flickerstreak@25
|
118 local w = self.config.btnWidth or 32
|
flickerstreak@25
|
119 local h = self.config.btnHeight or 32
|
flickerstreak@25
|
120 -- TODO: get from modules?
|
flickerstreak@25
|
121 return w,h
|
flickerstreak@25
|
122 end
|
flickerstreak@25
|
123
|
flickerstreak@25
|
124 function Bar:SetButtonSize(w,h)
|
flickerstreak@25
|
125 if w > 0 and h > 0 then
|
flickerstreak@25
|
126 self.config.btnWidth = w
|
flickerstreak@25
|
127 self.config.btnHeight = h
|
flickerstreak@25
|
128 end
|
flickerstreak@25
|
129 end
|
flickerstreak@25
|
130
|
flickerstreak@25
|
131 function Bar:GetButtonGrid()
|
flickerstreak@25
|
132 local cfg = self.config
|
flickerstreak@25
|
133 local r = cfg.btnRows or 1
|
flickerstreak@25
|
134 local c = cfg.btnColumns or 1
|
flickerstreak@25
|
135 local s = cfg.spacing or 4
|
flickerstreak@25
|
136 return r,c,s
|
flickerstreak@25
|
137 end
|
flickerstreak@25
|
138
|
flickerstreak@25
|
139 function Bar:SetButtonGrid(r,c,s)
|
flickerstreak@25
|
140 if r > 0 and c > 0 and s > 0 then
|
flickerstreak@25
|
141 local cfg = self.config
|
flickerstreak@25
|
142 cfg.btnRows = r
|
flickerstreak@25
|
143 cfg.btnColumns = c
|
flickerstreak@25
|
144 cfg.spacing = s
|
flickerstreak@25
|
145 end
|
flickerstreak@25
|
146 end
|
flickerstreak@25
|
147
|
flickerstreak@25
|
148 function Bar:GetName()
|
flickerstreak@25
|
149 return self.name
|
flickerstreak@25
|
150 end
|
flickerstreak@25
|
151
|
flickerstreak@33
|
152 function Bar:SetName(name)
|
flickerstreak@33
|
153 self.name = name
|
flickerstreak@33
|
154 if self.controlLabelString then
|
flickerstreak@33
|
155 self.controlLabelString:SetText(self.name)
|
flickerstreak@33
|
156 end
|
flickerstreak@33
|
157 end
|
flickerstreak@33
|
158
|
flickerstreak@25
|
159 function Bar:PlaceButton(f, idx, baseW, baseH)
|
flickerstreak@25
|
160 local r, c, s = self:GetButtonGrid()
|
flickerstreak@25
|
161 local bh, bw = self:GetButtonSize()
|
flickerstreak@25
|
162 local row, col = floor((idx-1)/c), mod((idx-1),c) -- zero-based
|
flickerstreak@25
|
163 local x, y = col*bw + (col+0.5)*s, row*bh + (row+0.5)*s
|
flickerstreak@25
|
164 local scale = bw/baseW
|
flickerstreak@25
|
165
|
flickerstreak@25
|
166 f:ClearAllPoints()
|
flickerstreak@25
|
167 f:SetPoint("TOPLEFT",x/scale,-y/scale)
|
flickerstreak@25
|
168 f:SetScale(scale)
|
flickerstreak@68
|
169 self.buttons[f] = true
|
flickerstreak@25
|
170 end
|
flickerstreak@25
|
171
|
flickerstreak@71
|
172
|
flickerstreak@71
|
173 -- multi-state functions --
|
flickerstreak@68
|
174 function Bar:GetNumPages()
|
flickerstreak@68
|
175 return self.config.nPages or 1
|
flickerstreak@68
|
176 end
|
flickerstreak@28
|
177
|
flickerstreak@72
|
178 --
|
flickerstreak@72
|
179 -- 'rule' is a rule-string to pass to RegisterStateDriver
|
flickerstreak@72
|
180 -- 'states' is a { ["statename"] = <don't care> } table of all state names
|
flickerstreak@72
|
181 -- 'keybinds' is a { ["statename"] = keybind } table of all keybound states
|
flickerstreak@72
|
182 --
|
flickerstreak@72
|
183 function Bar:SetStateDriver( rule, states, keybinds )
|
flickerstreak@72
|
184 local f = self.frame
|
flickerstreak@72
|
185 local kbprefix = ""
|
flickerstreak@72
|
186 do
|
flickerstreak@72
|
187 local tmp = { }
|
flickerstreak@72
|
188 for s, k in pairs(keybinds) do
|
flickerstreak@72
|
189 if k and #k > 0 then -- filter out false table entries
|
flickerstreak@72
|
190 -- if in a keybound state, set the stack to the new state but stay in the keybound state.
|
flickerstreak@72
|
191 -- use $s as a placeholder for the current state, it will be gsub()'d in later
|
flickerstreak@72
|
192 table.insert(tmp,("%s:$s set() %s"):format(s,s))
|
flickerstreak@72
|
193 end
|
flickerstreak@72
|
194 end
|
flickerstreak@72
|
195 table.insert(tmp,kbprefix) -- to get a trailing ';' if the table is not empty
|
flickerstreak@72
|
196 kbprefix = table.concat(tmp,";")
|
flickerstreak@72
|
197 end
|
flickerstreak@72
|
198 for state in pairs(states) do
|
flickerstreak@72
|
199 -- For all states: if in a keybound state, stay there (with stack manipulation, see above).
|
flickerstreak@72
|
200 -- Otherwise, go to the state
|
flickerstreak@72
|
201 f:SetAttribute(("statemap-reaction-%s"):format(state),("%s%s"):format(kbprefix:gsub("%$s",state),state))
|
flickerstreak@72
|
202
|
flickerstreak@72
|
203 local binding = keybinds[state]
|
flickerstreak@72
|
204 self:SetStateKeybind(binding, state) -- set the binding even if nil, to clear it unconditionally
|
flickerstreak@72
|
205 if binding then
|
flickerstreak@72
|
206 -- for key bindings, use the state-stack to toggle between the last state and the keybound state
|
flickerstreak@72
|
207 -- use a different 'virtual state' passed to attribute 'reaction-state' for key bindings, "<state>_binding"
|
flickerstreak@72
|
208 f:SetAttribute(("statemap-reaction-%s_binding"):format(state), ("%s:pop();*:set(%s)"):format(state,state))
|
flickerstreak@72
|
209 end
|
flickerstreak@72
|
210 end
|
flickerstreak@72
|
211
|
flickerstreak@72
|
212 if rule and #rule > 0 then
|
flickerstreak@72
|
213 self.stateDriver = true
|
flickerstreak@72
|
214 RegisterStateDriver(f, "reaction", rule)
|
flickerstreak@72
|
215 elseif self.statedriver then
|
flickerstreak@72
|
216 self.statedriver = false
|
flickerstreak@72
|
217 UnregisterStateDriver(f, "reaction")
|
flickerstreak@72
|
218 end
|
flickerstreak@72
|
219 end
|
flickerstreak@72
|
220
|
flickerstreak@68
|
221 function Bar:SetHideStates(s)
|
flickerstreak@68
|
222 for f in pairs(self.buttons) do
|
flickerstreak@68
|
223 if f:GetParent() == self.frame then
|
flickerstreak@68
|
224 f:SetAttribute("hidestates",s)
|
flickerstreak@68
|
225 end
|
flickerstreak@68
|
226 end
|
flickerstreak@68
|
227 SecureStateHeader_Refresh(self.frame)
|
flickerstreak@68
|
228 end
|
flickerstreak@28
|
229
|
flickerstreak@70
|
230 function Bar:SetStateKeybind(key, state, defaultstate)
|
flickerstreak@72
|
231 -- Lazily create a tiny offscreen button which sends "<state>_binding" values to the
|
flickerstreak@72
|
232 -- bar frame's state-reaction attribute, by using an override binding to generate a
|
flickerstreak@72
|
233 -- click on the button with a virtual mouse button "state".
|
flickerstreak@72
|
234 -- This gets around making the bar itself a clickable button, which is not desirable
|
flickerstreak@68
|
235 local f = self.statebuttonframe
|
flickerstreak@70
|
236 if key then
|
flickerstreak@68
|
237 if not f then
|
flickerstreak@72
|
238 f = CreateFrame("Button",self:GetName().."_statebutton",self.frame,"SecureActionButtonTemplate")
|
flickerstreak@68
|
239 f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT")
|
flickerstreak@68
|
240 f:SetWidth(1)
|
flickerstreak@68
|
241 f:SetHeight(1)
|
flickerstreak@72
|
242 f:SetAttribute("type*","attribute")
|
flickerstreak@72
|
243 f:SetAttribute("attribute-name*","state-reaction")
|
flickerstreak@72
|
244 f:SetAttribute("attribute-frame*",self.frame)
|
flickerstreak@68
|
245 f:Show()
|
flickerstreak@72
|
246 f.bindings = { }
|
flickerstreak@68
|
247 self.statebuttonframe = f
|
flickerstreak@68
|
248 end
|
flickerstreak@72
|
249 f:SetAttribute(("attribute-value-%s"):format(state),("%s_binding"):format(state))
|
flickerstreak@72
|
250 -- clear the old binding, if any, for this state
|
flickerstreak@72
|
251 if f.bindings[state] then
|
flickerstreak@72
|
252 SetOverrideBinding(self.frame, false, f.bindings[state], nil)
|
flickerstreak@72
|
253 end
|
flickerstreak@72
|
254 SetOverrideBindingClick(self.frame, false, key, f:GetName(), state) -- the state name is used as the virtual button
|
flickerstreak@72
|
255 f.bindings[state] = key
|
flickerstreak@68
|
256 elseif f then
|
flickerstreak@72
|
257 key = f.bindings[state]
|
flickerstreak@70
|
258 if key then
|
flickerstreak@72
|
259 SetOverrideBinding(self.frame, false, key, nil)
|
flickerstreak@72
|
260 f.bindings[state] = nil
|
flickerstreak@70
|
261 end
|
flickerstreak@68
|
262 end
|
flickerstreak@68
|
263 end
|
flickerstreak@33
|
264
|
flickerstreak@68
|
265 function Bar:SetStatePageMap(state, map) -- map is a { ["statename"] = pagenumber } table
|
flickerstreak@68
|
266 local f = self.frame
|
flickerstreak@68
|
267 local tmp = { }
|
flickerstreak@68
|
268 for s, p in pairs(map) do
|
flickerstreak@71
|
269 table.insert(tmp, ("%s:page%d"):format(s,p))
|
flickerstreak@68
|
270 end
|
flickerstreak@68
|
271 local spec = table.concat(tmp,";")
|
flickerstreak@72
|
272 local current = f:GetAttribute("statebutton")
|
flickerstreak@72
|
273 if spec ~= f:GetAttribute("statebutton") then
|
flickerstreak@72
|
274 f:SetAttribute("statebutton", spec)
|
flickerstreak@72
|
275 end
|
flickerstreak@71
|
276 SecureStateHeader_Refresh(f)
|
flickerstreak@68
|
277 end
|
flickerstreak@33
|
278
|
flickerstreak@68
|
279 function Bar:SetStateKeybindOverrideMap(states) -- 'states' is an array of state-names that should have keybind overrides enabled
|
flickerstreak@68
|
280 local f = self.frame
|
flickerstreak@68
|
281 for i = 1, #states do
|
flickerstreak@68
|
282 local s = states[i]
|
flickerstreak@68
|
283 states[i] = ("%s:%s"):format(s,s)
|
flickerstreak@68
|
284 end
|
flickerstreak@71
|
285 table.insert(states,"_defaultbindings")
|
flickerstreak@68
|
286 f:SetAttribute("statebindings",table.concat(states,";"))
|
flickerstreak@71
|
287 SecureStateHeader_Refresh(f)
|
flickerstreak@68
|
288 for b in pairs(self.buttons) do
|
flickerstreak@71
|
289 -- TODO: signal child frames that they should maintain multiple bindings
|
flickerstreak@68
|
290 end
|
flickerstreak@68
|
291 end
|
flickerstreak@33
|
292
|
flickerstreak@71
|
293 local _ofskeys = { "point", "relpoint", "x", "y" }
|
flickerstreak@71
|
294 function Bar:SetStateAnchorMap( map ) -- 'map' is a { ["statename"] = { point=point, relpoint=relpoint, x=x, y=y } } table
|
flickerstreak@71
|
295 local f = self.frame
|
flickerstreak@71
|
296 local c = self.config
|
flickerstreak@71
|
297 local default = { point = c.anchor, relpoint = c.relativePoint, x = c.x, y = c.y }
|
flickerstreak@71
|
298 for _, key in pairs(_ofskeys) do
|
flickerstreak@71
|
299 local t = { }
|
flickerstreak@71
|
300 for state, info in pairs(map) do
|
flickerstreak@71
|
301 if info[key] then
|
flickerstreak@71
|
302 table.insert(t, ("%s:%s"):format(state, info[key]))
|
flickerstreak@71
|
303 end
|
flickerstreak@71
|
304 end
|
flickerstreak@71
|
305 if #t > 0 and default[key] then table.insert(t, tostring(default[key])) end
|
flickerstreak@71
|
306 f:SetAttribute(("headofs%s"):format(key), table.concat(t,";") or "")
|
flickerstreak@71
|
307 end
|
flickerstreak@71
|
308 SecureStateHeader_Refresh(f)
|
flickerstreak@71
|
309 end
|
flickerstreak@71
|
310
|
flickerstreak@71
|
311 function Bar:SetStateScaleMap( map ) -- 'map' is a { ["statename"] = scalevalue } table
|
flickerstreak@71
|
312 local f = self.frame
|
flickerstreak@71
|
313 local t = { }
|
flickerstreak@71
|
314 for state, scale in pairs(map) do
|
flickerstreak@71
|
315 table.insert( t, ("%s:%s"):format(state,scale) )
|
flickerstreak@71
|
316 end
|
flickerstreak@71
|
317 if #t > 0 then table.insert(t, "1.0") end
|
flickerstreak@71
|
318 f:SetAttribute("headscale",table.concat(t,";") or "")
|
flickerstreak@71
|
319 SecureStateHeader_Refresh(f)
|
flickerstreak@71
|
320 end
|
flickerstreak@71
|
321
|
flickerstreak@71
|
322
|
flickerstreak@33
|
323
|
flickerstreak@28
|
324 ------ Export as a class-factory ------
|
flickerstreak@28
|
325 ReAction.Bar = {
|
flickerstreak@73
|
326 prototype = Bar,
|
flickerstreak@28
|
327 new = function(self, ...)
|
flickerstreak@28
|
328 local x = { }
|
flickerstreak@28
|
329 for k,v in pairs(Bar) do
|
flickerstreak@28
|
330 x[k] = v
|
flickerstreak@28
|
331 end
|
flickerstreak@28
|
332 Constructor(x, ...)
|
flickerstreak@28
|
333 return x
|
flickerstreak@28
|
334 end
|
flickerstreak@28
|
335 }
|