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