Mercurial > wow > reaction
comparison modules/State.lua @ 109:410d036c43b2
- reorganize modularity file structure (part 1)
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Thu, 08 Jan 2009 00:57:27 +0000 |
parents | |
children | 5c189f44e776 |
comparison
equal
deleted
inserted
replaced
108:b2fb8f7dc780 | 109:410d036c43b2 |
---|---|
1 --[[ | |
2 ReAction bar state driver interface | |
3 | |
4 --]] | |
5 | |
6 -- local imports | |
7 local ReAction = ReAction | |
8 local L = ReAction.L | |
9 local _G = _G | |
10 local format = string.format | |
11 local InCombatLockdown = InCombatLockdown | |
12 local RegisterStateDriver = RegisterStateDriver | |
13 | |
14 ReAction:UpdateRevision("$Revision$") | |
15 | |
16 -- module declaration | |
17 local moduleID = "State" | |
18 local module = ReAction:NewModule( moduleID, "AceEvent-3.0" ) | |
19 | |
20 -- Utility -- | |
21 | |
22 -- traverse a table tree by key list and fetch the result or first nil | |
23 local function tfetch(t, ...) | |
24 for i = 1, select('#', ...) do | |
25 t = t and t[select(i, ...)] | |
26 end | |
27 return t | |
28 end | |
29 | |
30 -- traverse a table tree by key list and build tree as necessary | |
31 local function tbuild(t, ...) | |
32 for i = 1, select('#', ...) do | |
33 local key = select(i, ...) | |
34 if not t[key] then t[key] = { } end | |
35 t = t[key] | |
36 end | |
37 return t | |
38 end | |
39 | |
40 -- return a new array of keys of table 't', sorted by comparing | |
41 -- sub-fields (obtained via tfetch) of the table values | |
42 local function fieldsort( t, ... ) | |
43 local r = { } | |
44 for k in pairs(t) do | |
45 table.insert(r,k) | |
46 end | |
47 local path = { ... } | |
48 table.sort(r, function(lhs, rhs) | |
49 local olhs = tfetch(t[lhs], unpack(path)) or 0 | |
50 local orhs = tfetch(t[rhs], unpack(path)) or 0 | |
51 return olhs < orhs | |
52 end) | |
53 return r | |
54 end | |
55 | |
56 -- set a frame-ref, if the frame is valid, or set nil to the | |
57 -- corresponding attribute | |
58 local function SetFrameRef(frame, name, refFrame) | |
59 if refFrame then | |
60 local _, explicit = refFrame:IsProtected() | |
61 if not explicit then | |
62 refFrame = nil | |
63 end | |
64 end | |
65 if refFrame then | |
66 frame:SetFrameRef(name,refFrame) | |
67 else | |
68 frame:SetAttribute("frameref-"..name,nil) | |
69 end | |
70 end | |
71 | |
72 | |
73 local InitRules, ApplyStates, CleanupStates, SetProperty, GetProperty, RegisterProperty, ShowAll | |
74 | |
75 -- PRIVATE -- | |
76 do | |
77 | |
78 -- the field names must match the field names of the options table, below | |
79 -- the field values are secure snippets or 'true' to skip the snippet for that property. | |
80 local properties = { | |
81 hide = | |
82 [[ | |
83 local h = hide and hide[state] and not showAll | |
84 if h ~= hidden then | |
85 if h then | |
86 self:Hide() | |
87 else | |
88 self:Show() | |
89 end | |
90 hidden = h | |
91 end | |
92 if showAll then | |
93 control:CallMethod("UpdateHiddenLabel", hide and hide[state]) | |
94 end | |
95 ]], | |
96 | |
97 --keybindState TODO: broken | |
98 | |
99 anchorEnable = | |
100 [[ | |
101 local old_anchor = anchorstate | |
102 anchorstate = (anchorEnable and anchorEnable[state]) and state | |
103 if old_anchor ~= anchorstate or not set_state then | |
104 if anchorstate and anchorPoint then | |
105 if anchorPoint[state] then | |
106 self:ClearAllPoints() | |
107 local f = self:GetAttribute("frameref-anchor-"..anchorstate) | |
108 if f then | |
109 self:SetPoint(anchorPoint[state], f, anchorRelPoint[state], anchorX[state], anchorY[state]) | |
110 end | |
111 end | |
112 elseif defaultAnchor and defaultAnchor.point then | |
113 self:ClearAllPoints() | |
114 self:SetPoint(defaultAnchor.point, defaultAnchor.frame, | |
115 defaultAnchor.relPoint, defaultAnchor.x, defaultAnchor.y) | |
116 end | |
117 end | |
118 ]], | |
119 -- anchorEnable handles all the other bits | |
120 anchorFrame = true, | |
121 anchorPoint = true, | |
122 anchorRelPoint = true, | |
123 anchorX = true, | |
124 anchorY = true, | |
125 | |
126 | |
127 enableScale = | |
128 [[ | |
129 local old_scale = scalestate | |
130 scalestate = (enableScale and enableScale[state]) and state | |
131 if old_scale ~= scalestate or not set_state then | |
132 if scalestate and scale then | |
133 if scale[state] then | |
134 self:SetScale(scale[state]) | |
135 end | |
136 else | |
137 self:SetScale(1.0) | |
138 end | |
139 end | |
140 ]], | |
141 -- enableScale handles scale | |
142 scale = true, | |
143 | |
144 enableAlpha = | |
145 [[ | |
146 local old_alpha = alphastate | |
147 alphastate = (enableAlpha and enableAlpha[state]) and state | |
148 if old_alpha ~= alphastate or not set_state then | |
149 control:CallMethod("UpdateAlpha", alphastate and alpha[state] or defaultAlpha) | |
150 end | |
151 ]], | |
152 -- enableAlpha handles alpha | |
153 alpha = true, | |
154 } | |
155 | |
156 local weak = { __mode = "k" } | |
157 local statedrivers = setmetatable( { }, weak ) | |
158 local keybinds = setmetatable( { }, weak ) | |
159 | |
160 -- | |
161 -- Secure Handler Snippets | |
162 -- | |
163 local SetHandlerData, SetStateDriver, SetStateKeybind, RefreshState | |
164 do | |
165 local stateHandler_propInit = | |
166 [[ | |
167 propfuncs = table.new() | |
168 local proplist = self:GetAttribute("prop-func-list") | |
169 for s in string.gmatch(proplist, "(%w+)") do | |
170 table.insert(propfuncs, s) | |
171 end | |
172 ]] | |
173 | |
174 local onStateHandler = | |
175 -- function _onstate-reaction( self, stateid, newstate ) | |
176 [[ | |
177 set_state = newstate | |
178 | |
179 local oldState = state | |
180 state = state_override or set_state or state | |
181 for i = 1, #propfuncs do | |
182 control:RunAttribute("func-"..propfuncs[i]) | |
183 end | |
184 | |
185 control:ChildUpdate() | |
186 | |
187 if oldState ~= state then | |
188 control:CallMethod("StateRefresh", state) | |
189 end | |
190 ]] | |
191 | |
192 local onClickHandler = | |
193 -- function OnClick( self, button, down ) | |
194 [[ | |
195 if state_override == button then | |
196 state_override = nil -- toggle | |
197 else | |
198 state_override = button | |
199 end | |
200 ]] .. onStateHandler | |
201 | |
202 local function UpdateAlpha( frame, alpha ) | |
203 if alpha then | |
204 frame:SetAlpha(alpha) | |
205 end | |
206 end | |
207 | |
208 -- Construct a lua assignment as a code string and execute it within the header | |
209 -- frame's sandbox. 'value' must be a string, boolean, number, or nil. If called | |
210 -- with four arguments, then it treats 'varname' as an existing global table and | |
211 -- sets a key-value pair. For a slight efficiency boost, pass the values in as | |
212 -- attributes and fetch them as attributes from the snippet code, to leverage snippet | |
213 -- caching. | |
214 function SetHandlerData( bar, varname, value, key ) | |
215 local f = bar:GetFrame() | |
216 f:SetAttribute("data-varname",varname) | |
217 f:SetAttribute("data-value", value) | |
218 f:SetAttribute("data-key", key) | |
219 f:Execute( | |
220 [[ | |
221 local name = self:GetAttribute("data-varname") | |
222 local value = self:GetAttribute("data-value") | |
223 local key = self:GetAttribute("data-key") | |
224 if name then | |
225 if key then | |
226 if not _G[name] then | |
227 _G[name] = table.new() | |
228 end | |
229 _G[name][key] = value | |
230 else | |
231 _G[name] = value | |
232 end | |
233 end | |
234 ]]) | |
235 end | |
236 | |
237 function SetDefaultAnchor( bar ) | |
238 local point, frame, relPoint, x, y = bar:GetAnchor() | |
239 SetHandlerData(bar, "defaultAnchor", point, "point") | |
240 SetHandlerData(bar, "defaultAnchor", relPoint, "relPoint") | |
241 SetHandlerData(bar, "defaultAnchor", x, "x") | |
242 SetHandlerData(bar, "defaultAnchor", y, "y") | |
243 SetHandlerData(bar, "defaultAlpha", bar:GetAlpha()) | |
244 | |
245 local f = bar:GetFrame() | |
246 f.UpdateAlpha = UpdateAlpha | |
247 SetFrameRef(f, "defaultAnchor", _G[frame or "UIParent"]) | |
248 f:Execute( | |
249 [[ | |
250 defaultAnchor.frame = self:GetAttribute("frameref-defaultAnchor") | |
251 ]]) | |
252 end | |
253 | |
254 function RefreshState( bar ) | |
255 SetDefaultAnchor(bar) | |
256 bar:GetFrame():Execute( | |
257 [[ | |
258 if self:GetAttribute("reaction-refresh") then | |
259 control:RunAttribute("reaction-refresh") | |
260 end | |
261 ]]) | |
262 end | |
263 | |
264 function SetStateDriver( bar, rule ) | |
265 local f = bar:GetFrame() | |
266 | |
267 if not f.UpdateHiddenLabel then | |
268 function f:UpdateHiddenLabel(hide) | |
269 bar:SetLabelSubtext( hide and L["Hidden"] ) | |
270 end | |
271 end | |
272 | |
273 function f:StateRefresh( state ) | |
274 bar:RefreshControls() | |
275 end | |
276 | |
277 local props = { } | |
278 for p, h in pairs(properties) do | |
279 if type(h) == "string" then | |
280 table.insert(props,p) | |
281 f:SetAttribute("func-"..p, h) | |
282 end | |
283 end | |
284 f:SetAttribute("prop-func-list", table.concat(props," ")) | |
285 f:Execute(stateHandler_propInit) | |
286 f:SetAttribute("reaction-refresh", onStateHandler) | |
287 | |
288 if rule and #rule > 0 then | |
289 f:SetAttribute( "_onstate-reaction", onStateHandler ) | |
290 RegisterStateDriver(f, "reaction", rule) | |
291 statedrivers[bar] = rule | |
292 elseif statedrivers[bar] then | |
293 UnregisterStateDriver(f, "reaction") | |
294 f:SetAttribute( "_onstate-reaction", nil ) | |
295 statedrivers[bar] = nil | |
296 end | |
297 end | |
298 | |
299 function SetStateKeybind( bar, key, state ) | |
300 local f = bar:GetFrame() | |
301 | |
302 local kb = keybinds[bar] | |
303 if kb == nil then | |
304 if key == nil then | |
305 -- nothing to do | |
306 return | |
307 end | |
308 kb = { } | |
309 keybinds[bar] = kb | |
310 end | |
311 | |
312 -- clear the old binding, if any | |
313 if kb[state] then | |
314 SetOverrideBinding(f, false, kb[state], nil) | |
315 end | |
316 kb[state] = key | |
317 | |
318 if key then | |
319 f:SetAttribute("_onclick", onClickHandler) | |
320 SetOverrideBindingClick(f, false, key, state, nil) -- state name is the virtual mouse button | |
321 end | |
322 end | |
323 end | |
324 | |
325 -- As far as I can tell the macro clauses are NOT locale-specific. | |
326 local ruleformats = { | |
327 stealth = "stealth", | |
328 nostealth = "nostealth", | |
329 shadowform = "form:1", | |
330 noshadowform = "noform", | |
331 pet = "pet", | |
332 nopet = "nopet", | |
333 harm = "target=target,harm", | |
334 help = "target=target,help", | |
335 notarget = "target=target,noexists", | |
336 focusharm = "target=focus,harm", | |
337 focushelp = "target=focus,help", | |
338 nofocus = "target=focus,noexists", | |
339 raid = "group:raid", | |
340 party = "group:party", | |
341 solo = "nogroup", | |
342 combat = "combat", | |
343 nocombat = "nocombat", | |
344 possess = "bonusbar:5", | |
345 } | |
346 | |
347 -- Have to do these shenanigans instead of hardcoding the stances/forms because the | |
348 -- ordering varies if the character is missing a form. For warriors this is rarely | |
349 -- a problem (c'mon, who actually skips the level 10 def stance quest?) but for druids | |
350 -- it can be. Some people never bother to do the aquatic form quest until well past | |
351 -- when they get cat form, and stance 5/6 can be flight, tree, or moonkin depending | |
352 -- on talents. | |
353 function InitRules() | |
354 local forms = { } | |
355 -- sort by icon since it's locale-independent | |
356 for i = 1, GetNumShapeshiftForms() do | |
357 local icon, name, active = GetShapeshiftFormInfo(i) | |
358 -- if it's the current form, the icon is wrong (Ability_Spell_WispSplode) | |
359 -- so capture it from the spell info directly | |
360 if active then | |
361 local _1, _2 | |
362 _1, _2, icon = GetSpellInfo(name) | |
363 end | |
364 forms[icon] = i; | |
365 end | |
366 -- use 9 if not found since 9 is never a valid stance/form | |
367 local defensive = forms["Interface\\Icons\\Ability_Warrior_DefensiveStance"] or 9 | |
368 local berserker = forms["Interface\\Icons\\Ability_Racial_Avatar"] or 9 | |
369 local bear = forms["Interface\\Icons\\Ability_Racial_BearForm"] or 9 -- bear and dire bear share the same icon | |
370 local aquatic = forms["Interface\\Icons\\Ability_Druid_AquaticForm"] or 9 | |
371 local cat = forms["Interface\\Icons\\Ability_Druid_CatForm"] or 9 | |
372 local travel = forms["Interface\\Icons\\Ability_Druid_TravelForm"] or 9 | |
373 local tree = forms["Interface\\Icons\\Ability_Druid_TreeofLife"] or 9 | |
374 local moonkin = forms["Interface\\Icons\\Spell_Nature_ForceOfNature"] or 9 | |
375 local flight = forms["Interface\\Icons\\Ability_Druid_FlightForm"] or 9 -- flight and swift flight share the same icon | |
376 | |
377 ruleformats.battle = "stance:1" | |
378 ruleformats.defensive = format("stance:%d",defensive) | |
379 ruleformats.berserker = format("stance:%d",berserker) | |
380 ruleformats.caster = format("form:0/%d/%d/%d",aquatic, travel, flight) | |
381 ruleformats.bear = format("form:%d",bear) | |
382 ruleformats.cat = format("form:%d",cat) | |
383 ruleformats.tree = format("form:%d",tree) | |
384 ruleformats.moonkin = format("form:%d",moonkin) | |
385 end | |
386 | |
387 local function BuildRule(states) | |
388 local rules = { } | |
389 local default | |
390 | |
391 for idx, state in ipairs(fieldsort(states, "rule", "order")) do | |
392 local c = states[state].rule | |
393 local type = c.type | |
394 if type == "default" then | |
395 default = default or state | |
396 elseif type == "custom" then | |
397 if c.custom then | |
398 -- strip out all spaces from the custom rule | |
399 table.insert(rules, format("%s %s", c.custom:gsub("%s",""), state)) | |
400 end | |
401 elseif type == "any" or type == "all" then | |
402 if c.values then | |
403 local clauses = { } | |
404 for key, value in pairs(c.values) do | |
405 table.insert(clauses, ruleformats[key]) | |
406 end | |
407 if #clauses > 0 then | |
408 local sep = (type == "any") and "][" or "," | |
409 table.insert(rules, format("[%s] %s", table.concat(clauses,sep), state)) | |
410 end | |
411 end | |
412 end | |
413 end | |
414 -- make sure that the default, if any, is last | |
415 if default then | |
416 table.insert(rules, default) | |
417 end | |
418 return table.concat(rules,";") | |
419 end | |
420 | |
421 local function BuildKeybinds( bar, states ) | |
422 for name, state in pairs(states) do | |
423 local type = tfetch(state, "rule", "type") | |
424 if type == "keybind" then | |
425 local key = tfetch(state, "rule", "keybind") | |
426 SetStateKeybind(bar, key, name) | |
427 else | |
428 SetStateKeybind(bar, nil, name) -- this clears an existing keybind | |
429 end | |
430 end | |
431 end | |
432 | |
433 function GetProperty( bar, state, propname ) | |
434 return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname) | |
435 end | |
436 | |
437 function SetProperty( bar, state, propname, value ) | |
438 local s = tbuild(module.db.profile.bars, bar:GetName(), "states", state) | |
439 s[propname] = value | |
440 SetHandlerData(bar, propname, value, state) | |
441 RefreshState(bar) | |
442 end | |
443 | |
444 function RegisterProperty( propname, snippet ) | |
445 properties[propname] = snippet or true | |
446 for _, bar in ReAction:IterateBars() do | |
447 local states = tfetch(module.db.profile.bars, bar:GetName(), "states") | |
448 if states then | |
449 for name, s in pairs(states) do | |
450 SetHandlerData(bar, propname, s[propname], name) | |
451 end | |
452 SetStateDriver(bar, BuildRule(states)) | |
453 RefreshState(bar) | |
454 end | |
455 end | |
456 end | |
457 | |
458 function UnregisterProperty( propname ) | |
459 properties[propname] = nil | |
460 for _, bar in ReAction:IterateBars() do | |
461 SetHandlerData(bar, propname, nil) | |
462 SetStateDriver(bar, BuildRule(states)) | |
463 RefreshState(bar) | |
464 end | |
465 end | |
466 | |
467 function ApplyStates( bar ) | |
468 local states = tfetch(module.db.profile.bars, bar:GetName(), "states") | |
469 if states then | |
470 for propname in pairs(properties) do | |
471 for name, s in pairs(states) do | |
472 if propname == "anchorFrame" then | |
473 SetFrameRef(bar:GetFrame(), "anchor-"..name, _G[s.anchorFrame]) | |
474 else | |
475 SetHandlerData(bar, propname, s[propname], name) | |
476 end | |
477 end | |
478 end | |
479 BuildKeybinds(bar, states) | |
480 SetHandlerData(bar, "showAll", ReAction:GetConfigMode()) | |
481 SetStateDriver(bar, BuildRule(states)) | |
482 RefreshState(bar) | |
483 end | |
484 end | |
485 | |
486 function CleanupStates( bar ) | |
487 SetStateDriver(bar, nil) | |
488 end | |
489 | |
490 function ShowAll( bar, show ) | |
491 if statedrivers[bar] then | |
492 SetHandlerData(bar, "showAll", show) | |
493 RefreshState(bar) | |
494 end | |
495 end | |
496 end | |
497 | |
498 | |
499 | |
500 -- module event handlers -- | |
501 | |
502 function module:OnInitialize() | |
503 self.db = ReAction.db:RegisterNamespace( moduleID, | |
504 { | |
505 profile = { | |
506 bars = { }, | |
507 } | |
508 } | |
509 ) | |
510 | |
511 self:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") | |
512 | |
513 ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") | |
514 | |
515 ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") | |
516 ReAction.RegisterCallback(self, "OnDestroyBar") | |
517 ReAction.RegisterCallback(self, "OnRefreshBar") | |
518 ReAction.RegisterCallback(self, "OnEraseBar") | |
519 ReAction.RegisterCallback(self, "OnRenameBar") | |
520 ReAction.RegisterCallback(self, "OnConfigModeChanged") | |
521 end | |
522 | |
523 function module:OnEnable() | |
524 self:UPDATE_SHAPESHIFT_FORMS() -- it doesn't fire on a /reloadui | |
525 end | |
526 | |
527 function module:UPDATE_SHAPESHIFT_FORMS() | |
528 -- Re-parse the rules table according to the new form list. | |
529 -- This happens both at initial login (after PLAYER_ENTERING_WORLD) | |
530 -- as well as when gaining new abilities. | |
531 InitRules() | |
532 for name, bar in ReAction:IterateBars() do | |
533 self:OnRefreshBar(nil,bar,name) | |
534 end | |
535 end | |
536 | |
537 function module:OnRefreshBar(event, bar, name) | |
538 local c = self.db.profile.bars[name] | |
539 if c then | |
540 ApplyStates(bar) | |
541 end | |
542 end | |
543 | |
544 function module:OnDestroyBar(event, bar, name) | |
545 CleanupStates(bar) | |
546 end | |
547 | |
548 function module:OnEraseBar(event, bar, name) | |
549 self.db.profile.bars[name] = nil | |
550 end | |
551 | |
552 function module:OnRenameBar(event, bar, oldname, newname) | |
553 local bars = self.db.profile.bars | |
554 bars[newname], bars[oldname] = bars[oldname], nil | |
555 end | |
556 | |
557 function module:OnConfigModeChanged(event, mode) | |
558 for name, bar in ReAction:IterateBars() do | |
559 if self.db.profile.bars[name] then | |
560 ShowAll(bar, mode) | |
561 end | |
562 end | |
563 end | |
564 | |
565 | |
566 | |
567 -- Options -- | |
568 | |
569 local CreateBarOptions, RegisterPropertyOptions | |
570 do | |
571 local playerClass = select(2, UnitClass("player")) | |
572 local function ClassCheck(...) | |
573 for i = 1, select('#',...) do | |
574 if playerClass == select(i,...) then | |
575 return false | |
576 end | |
577 end | |
578 return true | |
579 end | |
580 | |
581 -- pre-sorted by the order they should appear in | |
582 local rules = { | |
583 -- rule hidden fields | |
584 { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, | |
585 { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {tree = L["Tree of Life"]}, {moonkin = L["Moonkin Form"]} } }, | |
586 { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } }, | |
587 { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, | |
588 { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, | |
589 { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, | |
590 { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, | |
591 { "possess", false, { {possess = L["Mind Control"]} } }, | |
592 { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, | |
593 { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, | |
594 } | |
595 | |
596 local ruleSelect = { } | |
597 local ruleMap = { } | |
598 local optionMap = setmetatable({},{__mode="k"}) | |
599 | |
600 local pointTable = { | |
601 NONE = " ", | |
602 CENTER = L["Center"], | |
603 LEFT = L["Left"], | |
604 RIGHT = L["Right"], | |
605 TOP = L["Top"], | |
606 BOTTOM = L["Bottom"], | |
607 TOPLEFT = L["Top Left"], | |
608 TOPRIGHT = L["Top Right"], | |
609 BOTTOMLEFT = L["Bottom Left"], | |
610 BOTTOMRIGHT = L["Bottom Right"], | |
611 } | |
612 | |
613 -- unpack rules table into ruleSelect and ruleMap | |
614 for _, c in ipairs(rules) do | |
615 local rule, hidden, fields = unpack(c) | |
616 if not hidden then | |
617 for _, field in ipairs(fields) do | |
618 local key, label = next(field) | |
619 table.insert(ruleSelect, label) | |
620 table.insert(ruleMap, key) | |
621 end | |
622 end | |
623 end | |
624 | |
625 local stateOptions = { | |
626 ordering = { | |
627 name = L["Info"], | |
628 order = 1, | |
629 type = "group", | |
630 args = { | |
631 delete = { | |
632 name = L["Delete this State"], | |
633 order = -1, | |
634 type = "execute", | |
635 func = "DeleteState", | |
636 }, | |
637 rename = { | |
638 name = L["Name"], | |
639 order = 1, | |
640 type = "input", | |
641 get = "GetName", | |
642 set = "SetStateName", | |
643 pattern = "^%w*$", | |
644 usage = L["State names must be alphanumeric without spaces"], | |
645 }, | |
646 ordering = { | |
647 name = L["Evaluation Order"], | |
648 desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"], | |
649 order = 2, | |
650 type = "group", | |
651 inline = true, | |
652 args = { | |
653 up = { | |
654 name = L["Up"], | |
655 order = 1, | |
656 type = "execute", | |
657 width = "half", | |
658 func = "MoveStateUp", | |
659 }, | |
660 down = { | |
661 name = L["Down"], | |
662 order = 2, | |
663 type = "execute", | |
664 width = "half", | |
665 func = "MoveStateDown", | |
666 } | |
667 } | |
668 } | |
669 } | |
670 }, | |
671 properties = { | |
672 name = L["Properties"], | |
673 order = 2, | |
674 type = "group", | |
675 args = { | |
676 desc = { | |
677 name = L["Set the properties for the bar when in this state"], | |
678 order = 1, | |
679 type = "description" | |
680 }, | |
681 hide = { | |
682 name = L["Hide Bar"], | |
683 order = 90, | |
684 type = "toggle", | |
685 set = "SetProp", | |
686 get = "GetProp", | |
687 }, | |
688 --[[ BROKEN | |
689 keybindState = { | |
690 name = L["Override Keybinds"], | |
691 desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"], | |
692 order = 91, | |
693 type = "toggle", | |
694 set = "SetProp", | |
695 get = "GetProp", | |
696 }, ]] | |
697 position = { | |
698 name = L["Position"], | |
699 order = 92, | |
700 type = "group", | |
701 inline = true, | |
702 args = { | |
703 anchorEnable = { | |
704 name = L["Reposition"], | |
705 order = 1, | |
706 type = "toggle", | |
707 set = "SetProp", | |
708 get = "GetProp", | |
709 }, | |
710 anchorFrame = { | |
711 name = L["Anchor Frame"], | |
712 order = 2, | |
713 type = "select", | |
714 values = "GetAnchorFrames", | |
715 set = "SetAnchorFrame", | |
716 get = "GetAnchorFrame", | |
717 disabled = "GetAnchorDisabled", | |
718 hidden = "GetAnchorDisabled", | |
719 }, | |
720 anchorPoint = { | |
721 name = L["Point"], | |
722 order = 3, | |
723 type = "select", | |
724 values = pointTable, | |
725 set = "SetAnchorPointProp", | |
726 get = "GetAnchorPointProp", | |
727 disabled = "GetAnchorDisabled", | |
728 hidden = "GetAnchorDisabled", | |
729 }, | |
730 anchorRelPoint = { | |
731 name = L["Relative Point"], | |
732 order = 4, | |
733 type = "select", | |
734 values = pointTable, | |
735 set = "SetAnchorPointProp", | |
736 get = "GetAnchorPointProp", | |
737 disabled = "GetAnchorDisabled", | |
738 hidden = "GetAnchorDisabled", | |
739 }, | |
740 anchorX = { | |
741 name = L["X Offset"], | |
742 order = 5, | |
743 type = "range", | |
744 min = -100, | |
745 max = 100, | |
746 step = 1, | |
747 set = "SetProp", | |
748 get = "GetProp", | |
749 disabled = "GetAnchorDisabled", | |
750 hidden = "GetAnchorDisabled", | |
751 }, | |
752 anchorY = { | |
753 name = L["Y Offset"], | |
754 order = 6, | |
755 type = "range", | |
756 min = -100, | |
757 max = 100, | |
758 step = 1, | |
759 set = "SetProp", | |
760 get = "GetProp", | |
761 disabled = "GetAnchorDisabled", | |
762 hidden = "GetAnchorDisabled", | |
763 }, | |
764 }, | |
765 }, | |
766 scale = { | |
767 name = L["Scale"], | |
768 order = 93, | |
769 type = "group", | |
770 inline = true, | |
771 args = { | |
772 enableScale = { | |
773 name = L["Set New Scale"], | |
774 order = 1, | |
775 type = "toggle", | |
776 set = "SetProp", | |
777 get = "GetProp", | |
778 }, | |
779 scale = { | |
780 name = L["Scale"], | |
781 order = 2, | |
782 type = "range", | |
783 min = 0.25, | |
784 max = 2.5, | |
785 step = 0.05, | |
786 isPercent = true, | |
787 set = "SetProp", | |
788 get = "GetScale", | |
789 disabled = "GetScaleDisabled", | |
790 hidden = "GetScaleDisabled", | |
791 }, | |
792 }, | |
793 }, | |
794 alpha = { | |
795 name = L["Transparency"], | |
796 order = 94, | |
797 type = "group", | |
798 inline = true, | |
799 args = { | |
800 enableAlpha = { | |
801 name = L["Set Transparency"], | |
802 order = 1, | |
803 type = "toggle", | |
804 set = "SetProp", | |
805 get = "GetProp", | |
806 }, | |
807 alpha = { | |
808 name = L["Transparency"], | |
809 order = 2, | |
810 type = "range", | |
811 min = 0, | |
812 max = 1, | |
813 step = 0.01, | |
814 bigStep = 0.05, | |
815 isPercent = true, | |
816 set = "SetProp", | |
817 get = "GetAlpha", | |
818 disabled = "GetAlphaDisabled", | |
819 hidden = "GetAlphaDisabled", | |
820 }, | |
821 }, | |
822 }, | |
823 }, | |
824 plugins = { } | |
825 }, | |
826 rules = { | |
827 name = L["Rule"], | |
828 order = 3, | |
829 type = "group", | |
830 args = { | |
831 mode = { | |
832 name = L["Select this state"], | |
833 order = 2, | |
834 type = "select", | |
835 style = "radio", | |
836 values = { | |
837 default = L["by default"], | |
838 any = L["when ANY of these"], | |
839 all = L["when ALL of these"], | |
840 custom = L["via custom rule"], | |
841 keybind = L["via keybinding"], | |
842 }, | |
843 set = "SetType", | |
844 get = "GetType", | |
845 }, | |
846 clear = { | |
847 name = L["Clear All"], | |
848 order = 3, | |
849 type = "execute", | |
850 hidden = "GetClearAllDisabled", | |
851 disabled = "GetClearAllDisabled", | |
852 func = "ClearAllConditions", | |
853 }, | |
854 inputs = { | |
855 name = L["Conditions"], | |
856 order = 4, | |
857 type = "multiselect", | |
858 hidden = "GetConditionsDisabled", | |
859 disabled = "GetConditionsDisabled", | |
860 values = ruleSelect, | |
861 set = "SetCondition", | |
862 get = "GetCondition", | |
863 }, | |
864 custom = { | |
865 name = L["Custom Rule"], | |
866 order = 5, | |
867 type = "input", | |
868 multiline = true, | |
869 hidden = "GetCustomDisabled", | |
870 disabled = "GetCustomDisabled", | |
871 desc = L["Syntax like macro rules: see preset rules for examples"], | |
872 set = "SetCustomRule", | |
873 get = "GetCustomRule", | |
874 validate = "ValidateCustomRule", | |
875 }, | |
876 keybind = { | |
877 name = L["Keybinding"], | |
878 order = 6, | |
879 inline = true, | |
880 hidden = "GetKeybindDisabled", | |
881 disabled = "GetKeybindDisabled", | |
882 type = "group", | |
883 args = { | |
884 desc = { | |
885 name = L["Invoking a state keybind toggles an override of all other transition rules."], | |
886 order = 1, | |
887 type = "description", | |
888 }, | |
889 keybind = { | |
890 name = L["State Hotkey"], | |
891 desc = L["Define an override toggle keybind"], | |
892 order = 2, | |
893 type = "keybinding", | |
894 set = "SetKeybind", | |
895 get = "GetKeybind", | |
896 }, | |
897 }, | |
898 }, | |
899 }, | |
900 }, | |
901 } | |
902 | |
903 local handlers = { } | |
904 local meta = { | |
905 __index = function(self, key) | |
906 for _, h in pairs(handlers) do | |
907 if h[key] then | |
908 return h[key] | |
909 end | |
910 end | |
911 end, | |
912 } | |
913 local StateHandler = setmetatable({ }, meta) | |
914 local proto = { __index = StateHandler } | |
915 | |
916 function RegisterPropertyOptions( field, options, handler ) | |
917 stateOptions.properties.plugins[field] = options | |
918 handlers[field] = handler | |
919 end | |
920 | |
921 function UnregisterPropertyOptions( field ) | |
922 stateOptions.properties.plugins[field] = nil | |
923 handlers[field] = nil | |
924 end | |
925 | |
926 function StateHandler:New( bar, opts ) | |
927 local self = setmetatable( | |
928 { | |
929 bar = bar | |
930 }, | |
931 proto ) | |
932 | |
933 function self:GetName() | |
934 return opts.name | |
935 end | |
936 | |
937 function self:SetName(name) | |
938 opts.name = name | |
939 end | |
940 | |
941 function self:GetOrder() | |
942 return opts.order | |
943 end | |
944 | |
945 -- get reference to states table: even if the bar | |
946 -- name changes the states table ref won't | |
947 self.states = tbuild(module.db.profile.bars, bar:GetName(), "states") | |
948 self.state = tbuild(self.states, opts.name) | |
949 | |
950 opts.order = self:GetRuleField("order") | |
951 if opts.order == nil then | |
952 -- add after the highest | |
953 opts.order = 100 | |
954 for _, state in pairs(self.states) do | |
955 local x = tonumber(tfetch(state, "rule", "order")) | |
956 if x and x >= opts.order then | |
957 opts.order = x + 1 | |
958 end | |
959 end | |
960 self:SetRuleField("order",opts.order) | |
961 end | |
962 | |
963 return self | |
964 end | |
965 | |
966 -- helper methods | |
967 | |
968 function StateHandler:SetRuleField( key, value, ... ) | |
969 tbuild(self.state, "rule", ...)[key] = value | |
970 end | |
971 | |
972 function StateHandler:GetRuleField( ... ) | |
973 return tfetch(self.state, "rule", ...) | |
974 end | |
975 | |
976 function StateHandler:FixAll( setkey ) | |
977 -- if multiple selections in the same group are chosen when 'all' is selected, | |
978 -- keep only one of them. If changing the mode, the first in the fields list will | |
979 -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, | |
980 -- it will be retained. | |
981 local notified = false | |
982 if self:GetRuleField("type") == "all" then | |
983 for _, c in ipairs(rules) do | |
984 local rule, hidden, fields = unpack(c) | |
985 local once = false | |
986 if setkey then | |
987 for idx, field in ipairs(fields) do | |
988 if next(field) == setkey then | |
989 once = true | |
990 end | |
991 end | |
992 end | |
993 for idx, field in ipairs(fields) do | |
994 local key = next(field) | |
995 if self:GetRuleField("values",key) then | |
996 if once and key ~= setkey then | |
997 self:SetRuleField(key,false,"values") | |
998 if not setkey and not notified then | |
999 ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) | |
1000 notified = true | |
1001 end | |
1002 end | |
1003 once = true | |
1004 end | |
1005 end | |
1006 end | |
1007 end | |
1008 end | |
1009 | |
1010 function StateHandler:GetNeighbors() | |
1011 local before, after | |
1012 for k, v in pairs(self.states) do | |
1013 local o = tonumber(tfetch(v, "rule", "order")) | |
1014 if o and k ~= self:GetName() then | |
1015 local obefore = tfetch(self.states,before,"rule","order") | |
1016 local oafter = tfetch(self.states,after,"rule","order") | |
1017 if o < self:GetOrder() and (not obefore or obefore < o) then | |
1018 before = k | |
1019 end | |
1020 if o > self:GetOrder() and (not oafter or oafter > o) then | |
1021 after = k | |
1022 end | |
1023 end | |
1024 end | |
1025 return before, after | |
1026 end | |
1027 | |
1028 function StateHandler:SwapOrder( a, b ) | |
1029 -- do options table | |
1030 local args = optionMap[self.bar].args | |
1031 args[a].order, args[b].order = args[b].order, args[a].order | |
1032 -- do profile | |
1033 a = tbuild(self.states, a, "rule") | |
1034 b = tbuild(self.states, b, "rule") | |
1035 a.order, b.order = b.order, a.order | |
1036 end | |
1037 | |
1038 -- handler methods | |
1039 | |
1040 function StateHandler:GetProp( info ) | |
1041 -- gets property of the same name as the options arg | |
1042 return GetProperty(self.bar, self:GetName(), info[#info]) | |
1043 end | |
1044 | |
1045 function StateHandler:SetProp( info, value ) | |
1046 -- sets property of the same name as the options arg | |
1047 SetProperty(self.bar, self:GetName(), info[#info], value) | |
1048 end | |
1049 | |
1050 function StateHandler:DeleteState() | |
1051 if self.states[self:GetName()] then | |
1052 self.states[self:GetName()] = nil | |
1053 ApplyStates(self.bar) | |
1054 end | |
1055 optionMap[self.bar].args[self:GetName()] = nil | |
1056 end | |
1057 | |
1058 function StateHandler:SetStateName(info, value) | |
1059 -- check for existing state name | |
1060 if self.states[value] then | |
1061 ReAction:UserError(format(L["State named '%s' already exists"],value)) | |
1062 return | |
1063 end | |
1064 local args = optionMap[self.bar].args | |
1065 local name = self:GetName() | |
1066 self.states[value], args[value], self.states[name], args[name] = self.states[name], args[name], nil, nil | |
1067 self:SetName(value) | |
1068 ApplyStates(self.bar) | |
1069 ReAction:ShowEditor(self.bar, moduleID, value) | |
1070 end | |
1071 | |
1072 function StateHandler:MoveStateUp() | |
1073 local before, after = self:GetNeighbors() | |
1074 if before then | |
1075 self:SwapOrder(before, self:GetName()) | |
1076 ApplyStates(self.bar) | |
1077 end | |
1078 end | |
1079 | |
1080 function StateHandler:MoveStateDown() | |
1081 local before, after = self:GetNeighbors() | |
1082 if after then | |
1083 self:SwapOrder(self:GetName(), after) | |
1084 ApplyStates(self.bar) | |
1085 end | |
1086 end | |
1087 | |
1088 function StateHandler:GetAnchorDisabled() | |
1089 return not GetProperty(self.bar, self:GetName(), "anchorEnable") | |
1090 end | |
1091 | |
1092 function StateHandler:GetAnchorFrames(info) | |
1093 self._anchorframes = self._anchorframes or { } | |
1094 table.wipe(self._anchorframes) | |
1095 | |
1096 table.insert(self._anchorframes, "UIParent") | |
1097 for name, bar in ReAction:IterateBars() do | |
1098 table.insert(self._anchorframes, bar:GetFrame():GetName()) | |
1099 end | |
1100 return self._anchorframes | |
1101 end | |
1102 | |
1103 function StateHandler:GetAnchorFrame(info) | |
1104 local value = self:GetProp(info) | |
1105 for k,v in pairs(self._anchorframes) do | |
1106 if v == value then | |
1107 return k | |
1108 end | |
1109 end | |
1110 end | |
1111 | |
1112 function StateHandler:SetAnchorFrame(info, value) | |
1113 local f = _G[self._anchorframes[value]] | |
1114 if f then | |
1115 SetFrameRef(self.bar:GetFrame(), "anchor-"..self:GetName(), f) | |
1116 self:SetProp(info, f:GetName()) | |
1117 end | |
1118 end | |
1119 | |
1120 function StateHandler:SetAnchorPointProp(info, value) | |
1121 self:SetProp(info, value ~= "NONE" and value or nil) | |
1122 end | |
1123 | |
1124 function StateHandler:GetAnchorPointProp(info) | |
1125 return self:GetProp(info) or "NONE" | |
1126 end | |
1127 | |
1128 function StateHandler:GetScale(info) | |
1129 return self:GetProp(info) or 1.0 | |
1130 end | |
1131 | |
1132 function StateHandler:GetScaleDisabled() | |
1133 return not GetProperty(self.bar, self:GetName(), "enableScale") | |
1134 end | |
1135 | |
1136 function StateHandler:GetAlpha(info) | |
1137 return self:GetProp(info) or 1.0 | |
1138 end | |
1139 | |
1140 function StateHandler:GetAlphaDisabled() | |
1141 return not GetProperty(self.bar, self:GetName(), "enableAlpha") | |
1142 end | |
1143 | |
1144 function StateHandler:SetType(info, value) | |
1145 self:SetRuleField("type", value) | |
1146 self:FixAll() | |
1147 ApplyStates(self.bar) | |
1148 end | |
1149 | |
1150 function StateHandler:GetType() | |
1151 return self:GetRuleField("type") | |
1152 end | |
1153 | |
1154 function StateHandler:GetClearAllDisabled() | |
1155 local t = self:GetRuleField("type") | |
1156 return not( t == "any" or t == "all" or t == "custom") | |
1157 end | |
1158 | |
1159 function StateHandler:ClearAllConditions() | |
1160 local t = self:GetRuleField("type") | |
1161 if t == "custom" then | |
1162 self:SetRuleField("custom","") | |
1163 elseif t == "any" or t == "all" then | |
1164 self:SetRuleField("values", {}) | |
1165 end | |
1166 ApplyStates(self.bar) | |
1167 end | |
1168 | |
1169 function StateHandler:GetConditionsDisabled() | |
1170 local t = self:GetRuleField("type") | |
1171 return not( t == "any" or t == "all") | |
1172 end | |
1173 | |
1174 function StateHandler:SetCondition(info, key, value) | |
1175 self:SetRuleField(ruleMap[key], value or nil, "values") | |
1176 if value then | |
1177 self:FixAll(ruleMap[key]) | |
1178 end | |
1179 ApplyStates(self.bar) | |
1180 end | |
1181 | |
1182 function StateHandler:GetCondition(info, key) | |
1183 return self:GetRuleField("values", ruleMap[key]) or false | |
1184 end | |
1185 | |
1186 function StateHandler:GetCustomDisabled() | |
1187 return self:GetRuleField("type") ~= "custom" | |
1188 end | |
1189 | |
1190 function StateHandler:SetCustomRule(info, value) | |
1191 self:SetRuleField("custom",value) | |
1192 ApplyStates(self.bar) | |
1193 end | |
1194 | |
1195 function StateHandler:GetCustomRule() | |
1196 return self:GetRuleField("custom") or "" | |
1197 end | |
1198 | |
1199 function StateHandler:ValidateCustomRule(info, value) | |
1200 local s = value:gsub("%s","") -- remove all spaces | |
1201 -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler | |
1202 repeat | |
1203 if s == "" then | |
1204 return true | |
1205 end | |
1206 local c, r = s:match("(%b[])(.*)") | |
1207 if c == nil and s and #s > 0 then | |
1208 return format(L["Invalid custom rule '%s': each clause must appear within [brackets]"],value or "") | |
1209 end | |
1210 s = r | |
1211 until c == nil | |
1212 return true | |
1213 end | |
1214 | |
1215 function StateHandler:GetKeybindDisabled() | |
1216 return self:GetRuleField("type") ~= "keybind" | |
1217 end | |
1218 | |
1219 function StateHandler:GetKeybind() | |
1220 return self:GetRuleField("keybind") | |
1221 end | |
1222 | |
1223 function StateHandler:SetKeybind(info, value) | |
1224 if value and #value == 0 then | |
1225 value = nil | |
1226 end | |
1227 self:SetRuleField("keybind",value) | |
1228 ApplyStates(self.bar) | |
1229 end | |
1230 | |
1231 local function CreateStateOptions(bar, name) | |
1232 local opts = { | |
1233 type = "group", | |
1234 name = name, | |
1235 childGroups = "tab", | |
1236 args = stateOptions | |
1237 } | |
1238 | |
1239 opts.handler = StateHandler:New(bar,opts) | |
1240 | |
1241 return opts | |
1242 end | |
1243 | |
1244 function module:GetBarOptions(bar) | |
1245 local private = { } | |
1246 local states = tbuild(module.db.profile.bars, bar:GetName(), "states") | |
1247 local options = { | |
1248 name = L["Dynamic State"], | |
1249 type = "group", | |
1250 order = -1, | |
1251 childGroups = "tree", | |
1252 disabled = InCombatLockdown, | |
1253 args = { | |
1254 __desc__ = { | |
1255 name = L["States are evaluated in the order they are listed"], | |
1256 order = 1, | |
1257 type = "description", | |
1258 }, | |
1259 __new__ = { | |
1260 name = L["New State..."], | |
1261 order = 2, | |
1262 type = "group", | |
1263 args = { | |
1264 name = { | |
1265 name = L["State Name"], | |
1266 desc = L["Set a name for the new state"], | |
1267 order = 1, | |
1268 type = "input", | |
1269 get = function() return private.newstatename or "" end, | |
1270 set = function(info,value) private.newstatename = value end, | |
1271 pattern = "^%w*$", | |
1272 usage = L["State names must be alphanumeric without spaces"], | |
1273 }, | |
1274 create = { | |
1275 name = L["Create State"], | |
1276 order = 2, | |
1277 type = "execute", | |
1278 func = function () | |
1279 local name = private.newstatename | |
1280 if states[name] then | |
1281 ReAction:UserError(format(L["State named '%s' already exists"],name)) | |
1282 else | |
1283 -- TODO: select default state options and pass as final argument | |
1284 states[name] = { } | |
1285 optionMap[bar].args[name] = CreateStateOptions(bar,name) | |
1286 ReAction:ShowEditor(bar, moduleID, name) | |
1287 private.newstatename = "" | |
1288 end | |
1289 end, | |
1290 disabled = function() | |
1291 local name = private.newstatename or "" | |
1292 return #name == 0 or name:find("%W") | |
1293 end, | |
1294 } | |
1295 } | |
1296 } | |
1297 } | |
1298 } | |
1299 for name, config in pairs(states) do | |
1300 options.args[name] = CreateStateOptions(bar,name) | |
1301 end | |
1302 optionMap[bar] = options | |
1303 return options | |
1304 end | |
1305 end | |
1306 | |
1307 -- Module API -- | |
1308 | |
1309 -- Pass in a property field-name, an implementation secure snippet, a static options table, and an | |
1310 -- optional options handler method-table | |
1311 -- | |
1312 -- The options table is static, i.e. not bar-specific and should only reference handler method | |
1313 -- strings (either existing ones or those added via optHandler). The existing options are ordered | |
1314 -- 90-99. Order #1 is reserved for the heading. | |
1315 -- | |
1316 -- The contents of optHandler, if provided, will be added to the existing StateHandler options metatable. | |
1317 -- See above, for existing API. In particular see the properties set up in the New method: self.bar, | |
1318 -- self.states, and self:GetName(), and the generic property handlers self:GetProp() and self:SetProp(). | |
1319 -- | |
1320 function module:RegisterStateProperty( field, snippetHandler, options, optHandler ) | |
1321 RegisterProperty(field, snippetHandler) | |
1322 RegisterPropertyOptions(field, options, optHandler) | |
1323 end | |
1324 | |
1325 function module:UnregisterStateProperty( field ) | |
1326 UnregisterProperty(field) | |
1327 UnregisterPropertyOptions(field) | |
1328 end | |
1329 | |
1330 | |
1331 -- Export methods to Bar class -- | |
1332 | |
1333 function ReAction.Bar:GetState() | |
1334 return GetManagedEnvironment(self:GetFrame()).state | |
1335 end | |
1336 | |
1337 ReAction.Bar.GetStateProperty = GetProperty | |
1338 ReAction.Bar.SetStateProperty = SetProperty |