comparison State.lua @ 69:a785d6708388

moved State.lua to a top level file
author Flick <flickerstreak@gmail.com>
date Tue, 03 Jun 2008 23:05:16 +0000
parents modules/ReAction_State/ReAction_State.lua@fcb5dad031f9
children 2c12e2b1752e
comparison
equal deleted inserted replaced
68:fcb5dad031f9 69:a785d6708388
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 InCombatLockdown = InCombatLockdown
11
12 -- module declaration
13 local moduleID = "State"
14 local module = ReAction:NewModule( moduleID, "AceEvent-3.0" )
15
16 -- Utility --
17
18 -- traverse a table tree by key list and fetch the result or first nil
19 local function tfetch(t, ...)
20 for i = 1, select('#', ...) do
21 t = t and t[select(i, ...)]
22 end
23 return t
24 end
25
26 -- traverse a table tree by key list and build tree as necessary
27 local function tbuild(t, ...)
28 for i = 1, select('#', ...) do
29 local key = select(i, ...)
30 if not t[key] then t[key] = { } end
31 t = t[key]
32 end
33 return t
34 end
35
36 -- PRIVATE --
37
38 local InitRules, ApplyStates, SetProperty, GetProperty
39 do
40 -- As far as I can tell the macro clauses are NOT locale-specific.
41 local ruleformats = {
42 stealth = "stealth",
43 nostealth = "nostealth",
44 shadowform = "form:1",
45 noshadowform = "noform",
46 pet = "pet",
47 nopet = "nopet",
48 harm = "target=target,harm",
49 help = "target=target,help",
50 notarget = "target=target,noexists",
51 focusharm = "target=focus,harm",
52 focushelp = "target=focus,help",
53 nofocus = "target=focus,noexists",
54 raid = "group:raid",
55 party = "group:party",
56 solo = "nogroup",
57 combat = "combat",
58 nocombat = "nocombat",
59 }
60
61 -- Have to do these shenanigans instead of hardcoding the stances/forms because
62 -- the ordering varies if the character is missing a form. For warriors
63 -- this is rarely a problem (c'mon, who actually skips the level 10 def stance quest?)
64 -- but for druids it can be. Some people never bother to do the aquatic form quest
65 -- until well past when they get cat form, and stance 5 can be flight, tree, or moonkin
66 -- depending on talents.
67 function InitRules()
68 local forms = { }
69 -- sort by icon since it's locale-independent
70 for i = 1, GetNumShapeshiftForms() do
71 local icon = GetShapeshiftFormInfo(i)
72 forms[icon] = i;
73 end
74 -- use 9 if not found since 9 is never a valid stance/form
75 local defensive = forms["Interface\\Icons\\Ability_Warrior_DefensiveStance"] or 9
76 local berserker = forms["Interface\\Icons\\Ability_Racial_Avatar"] or 9
77 local bear = forms["Interface\\Icons\\Ability_Racial_BearForm"] or 9 -- bear and dire bear share the same icon
78 local aquatic = forms["Interface\\Icons\\Ability_Druid_AquaticForm"] or 9
79 local cat = forms["Interface\\Icons\\Ability_Druid_CatForm"] or 9
80 local travel = forms["Interface\\Icons\\Ability_Druid_TravelForm"] or 9
81 local treekin = forms["Interface\\Icons\\Ability_Druid_TreeofLife"] or forms["Interface\\Icons\\Spell_Nature_ForceOfNature"] or 9
82 local flight = forms["Interface\\Icons\\Ability_Druid_FlightForm"] or 9 -- flight and swift flight share the same icon
83
84 ruleformats.battle = "stance:1"
85 ruleformats.defensive = ("stance:%d"):format(defensive)
86 ruleformats.berserker = ("stance:%d"):format(berserker)
87 ruleformats.caster = ("form:0/%d/%d/%d"):format(aquatic, travel, flight)
88 ruleformats.bear = ("form:%d"):format(bear)
89 ruleformats.cat = ("form:%d"):format(cat)
90 ruleformats.treeOrMoonkin = ("form:%d"):format(treekin)
91 end
92
93 -- return a new array of keys of table 't', sorted by comparing
94 -- sub-fields (obtained via tfetch) of the table values
95 local function fieldsort( t, ... )
96 local r = { }
97 for k in pairs(t) do
98 table.insert(r,k)
99 end
100 local dotdotdot = { ... }
101 table.sort(r, function(lhs, rhs)
102 local olhs = tfetch(t[lhs], unpack(dotdotdot)) or 0
103 local orhs = tfetch(t[rhs], unpack(dotdotdot)) or 0
104 return olhs < orhs
105 end)
106 return r
107 end
108
109 local function BuildRuleString(states)
110 local s = ""
111 local default
112 local sorted = fieldsort(states, "rule", "order")
113 for idx, name in ipairs(sorted) do
114 local state = states[name]
115 local semi = #s > 0 and "; " or ""
116 local mode = tfetch(state,"rule","type")
117 if mode == "default" then
118 default = name
119 elseif mode == "custom" then
120 if state.rule.custom then
121 -- strip out all spaces from the custom rule
122 s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name)
123 end
124 elseif mode == "any" then
125 if state.rule.values then
126 local clause = ""
127 for key, value in pairs(state.rule.values) do
128 clause = ("%s[%s]"):format(clause,ruleformats[key])
129 end
130 if #clause > 0 then
131 s = ("%s%s%s %s"):format(s, semi, clause, name)
132 end
133 end
134 elseif mode == "all" then
135 if state.rule.values then
136 local clause = ""
137 for key, value in pairs(state.rule.values) do
138 clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key])
139 end
140 if #clause > 0 then
141 s = ("%s%s[%s] %s"):format(s, semi, clause, name)
142 end
143 end
144 end
145 end
146 if default then
147 s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default)
148 end
149 return s, default
150 end
151
152 local drivers = setmetatable({},{__mode="k"})
153 local propertyFuncs = { }
154
155 function ApplyStates( bar )
156 local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
157 if states then
158 local frame = bar:GetFrame()
159 local string, default = BuildRuleString(states)
160 if string and #string > 0 then
161 drivers[bar] = true
162 -- register a map for each "statemap-reaction-XXX" to set 'state' to 'XXX'
163 -- UNLESS we're in a keybound state AND there's a default state, in which case
164 -- all keybound states go back to themselves.
165 local keybindprefix
166 if default then
167 local tmp = { }
168 for state, config in pairs(states) do
169 if tfetch(config, "rule", "type") == "keybind" then
170 bar:SetStateKeybind(tfetch(config,"rule","keybind"), state, tfetch(config,"rule","keybindreturn") or default or 0)
171 table.insert(tmp, ("%s:%s"):format(state,state))
172 end
173 end
174 if #tmp > 0 then
175 table.insert(tmp,"") -- to get a final ';'
176 end
177 keybindprefix = table.concat(tmp,";")
178 end
179 for state in pairs(states) do
180 frame:SetAttribute(("statemap-reaction-%s"):format(state), ("%s%s"):format(keybindprefix or "",state))
181 end
182 -- register a handler to set the value of attribute "state-reaction"
183 -- in response to events as per the rule string
184 RegisterStateDriver(frame, "reaction", string)
185 SecureStateHeader_Refresh(frame)
186 elseif drivers[bar] then
187 UnregisterStateDriver(frame, "reaction")
188 drivers[bar] = nil
189 end
190 for k, f in pairs(propertyFuncs) do
191 f(bar, states)
192 end
193 end
194 end
195
196 function GetProperty( bar, state, propname )
197 return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname)
198 end
199
200 function SetProperty( bar, state, propname, value )
201 local states = tbuild(module.db.profile.bars, bar:GetName(), "states")
202 tbuild(states, state)[propname] = value
203 local f = propertyFuncs[propname]
204 if f then
205 f(bar, states)
206 end
207 end
208
209 -- state property functions
210 function propertyFuncs.hide( bar, states )
211 local tmp = { }
212 for state, config in pairs(states) do
213 if config.hide then
214 table.insert(tmp, state)
215 end
216 end
217 local s = table.concat(tmp,",")
218 bar:SetHideStates(s)
219 end
220
221 function propertyFuncs.page( bar, states )
222 local map = { }
223 for state, config in pairs(states) do
224 map[state] = config.page
225 end
226 bar:SetStatePageMap(state, map)
227 end
228
229 function propertyFuncs.keybindstate( bar, states )
230 local map = { }
231 for state, config in pairs(states) do
232 if config.keybindstate then
233 table.insert(map,state)
234 end
235 end
236 bar:SetStateKeybindOverrideMap(map)
237 end
238
239 function propertyFuncs.enableanchor( bar, states )
240
241 end
242
243 function propertyFuncs.anchorPoint( bar, states )
244
245 end
246
247 function propertyFuncs.anchorRelPoint( bar, states )
248
249 end
250
251 function propertyFuncs.anchorX( bar, states )
252
253 end
254
255 function propertyFuncs.anchorY( bar, states )
256
257 end
258
259 function propertyFuncs.enablescale( bar, states )
260
261 end
262
263 function propertyFuncs.scale( bar, states )
264
265 end
266
267 end
268
269
270
271 -- module event handlers --
272
273 function module:OnInitialize()
274 self.db = ReAction.db:RegisterNamespace( moduleID,
275 {
276 profile = {
277 bars = { },
278 }
279 }
280 )
281
282 InitRules()
283 self:RegisterEvent("PLAYER_AURAS_CHANGED")
284
285 ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")
286
287 ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar")
288 ReAction.RegisterCallback(self, "OnRefreshBar")
289 ReAction.RegisterCallback(self, "OnEraseBar")
290 ReAction.RegisterCallback(self, "OnRenameBar")
291 ReAction.RegisterCallback(self, "OnConfigModeChanged")
292 end
293
294 function module:PLAYER_AURAS_CHANGED()
295 self:UnregisterEvent("PLAYER_AURAS_CHANGED")
296 -- on login the number of stances is 0 until this event fires during the init sequence.
297 -- however if you reload just the UI the number of stances is correct immediately
298 -- and this event won't fire until you gain/lose buffs/debuffs, at which point you might
299 -- be in combat.
300 if not InCombatLockdown() then
301 InitRules()
302 for name, bar in ReAction:IterateBars() do
303 self:OnRefreshBar(nil,bar,name)
304 end
305 end
306 end
307
308 function module:OnRefreshBar(event, bar, name)
309 local c = self.db.profile.bars[name]
310 if c then
311 ApplyStates(bar)
312 end
313 end
314
315 function module:OnEraseBar(event, bar, name)
316 self.db.profile.bars[name] = nil
317 end
318
319 function module:OnRenameBar(event, bar, oldname, newname)
320 local b = self.db.profile.bars
321 bars[newname], bars[oldname] = bars[oldname], nil
322 end
323
324 function module:OnConfigModeChanged(event, mode)
325 -- TODO: unregister all state drivers (temporarily) and hidestates
326 end
327
328
329
330 -- Options --
331
332 local CreateBarOptions
333 do
334 local function ClassCheck(...)
335 for i = 1, select('#',...) do
336 local _, c = UnitClass("player")
337 if c == select(i,...) then
338 return false
339 end
340 end
341 return true
342 end
343
344 -- pre-sorted by the order they should appear in
345 local rules = {
346 -- rule hidden fields
347 { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } },
348 { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {treeOrMoonkin = L["Tree/Moonkin"]} } },
349 { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } },
350 { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } },
351 { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } },
352 { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } },
353 { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } },
354 { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } },
355 { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } },
356 }
357
358 local ruleSelect = { }
359 local ruleMap = { }
360 local optionMap = setmetatable({},{__mode="k"})
361
362 local pointTable = {
363 NONE = " ",
364 CENTER = L["Center"],
365 LEFT = L["Left"],
366 RIGHT = L["Right"],
367 TOP = L["Top"],
368 BOTTOM = L["Bottom"],
369 TOPLEFT = L["Top Left"],
370 TOPRIGHT = L["Top Right"],
371 BOTTOMLEFT = L["Bottom Left"],
372 BOTTOMRIGHT = L["Bottom Right"],
373 }
374
375 -- unpack rules table into ruleSelect and ruleMap
376 for _, c in ipairs(rules) do
377 local rule, hidden, fields = unpack(c)
378 if not hidden then
379 for _, field in ipairs(fields) do
380 local key, label = next(field)
381 table.insert(ruleSelect, label)
382 table.insert(ruleMap, key)
383 end
384 end
385 end
386
387 local function CreateStateOptions(bar, name)
388 local opts = {
389 type = "group",
390 name = name,
391 childGroups = "tab",
392 }
393
394 local states = tbuild(module.db.profile.bars, bar:GetName(), "states")
395
396 local function update()
397 ApplyStates(bar)
398 end
399
400 local function setrule( key, value, ... )
401 tbuild(states, opts.name, "rule", ...)[key] = value
402 end
403
404 local function getrule( ... )
405 return tfetch(states, opts.name, "rule", ...)
406 end
407
408 local function setprop(info, value)
409 SetProperty(bar, opts.name, info[#info], value)
410 end
411
412 local function getprop(info)
413 return GetProperty(bar, opts.name, info[#info])
414 end
415
416 local function fixall(setkey)
417 -- if multiple selections in the same group are chosen when 'all' is selected,
418 -- keep only one of them. If changing the mode, the first in the fields list will
419 -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set,
420 -- it will be retained.
421 local notified = false
422 for _, c in ipairs(rules) do
423 local rule, hidden, fields = unpack(c)
424 local found = false
425 for key in ipairs(fields) do
426 if getrule("values",key) then
427 if (found or setkey) and key ~= setkey then
428 setrule(key,false,"values")
429 if not setkey and not notified then
430 ReAction:UserError(L["Warning: one or more incompatible rules were turned off"])
431 notified = true
432 end
433 end
434 found = true
435 end
436 end
437 end
438 end
439
440 local function getNeighbors()
441 local before, after
442 for k, v in pairs(states) do
443 local o = tonumber(tfetch(v, "rule", "order"))
444 if o and k ~= opts.name then
445 local obefore = tfetch(states,before,"rule","order")
446 local oafter = tfetch(states,after,"rule","order")
447 if o < opts.order and (not obefore or obefore < o) then
448 before = k
449 end
450 if o > opts.order and (not oafter or oafter > o) then
451 after = k
452 end
453 end
454 end
455 return before, after
456 end
457
458 local function swapOrder( a, b )
459 -- do options table
460 local args = optionMap[bar].args
461 args[a].order, args[b].order = args[b].order, args[a].order
462 -- do profile
463 a = tbuild(states, a, "rule")
464 b = tbuild(states, b, "rule")
465 a.order, b.order = b.order, a.order
466 end
467
468 local function anchordisable()
469 return not GetProperty(bar, opts.name, "enableanchor")
470 end
471
472 tbuild(states, name)
473
474 opts.order = getrule("order")
475 if opts.order == nil then
476 -- add after the highest
477 opts.order = 100
478 for _, state in pairs(states) do
479 local x = tonumber(tfetch(state, "rule", "order"))
480 if x and x >= opts.order then
481 opts.order = x + 1
482 end
483 end
484 setrule("order",opts.order)
485 end
486
487 opts.args = {
488 ordering = {
489 name = L["Info"],
490 order = 1,
491 type = "group",
492 args = {
493 delete = {
494 name = L["Delete this State"],
495 order = -1,
496 type = "execute",
497 func = function(info)
498 if states[opts.name] then
499 states[opts.name] = nil
500 ApplyStates(bar)
501 end
502 optionMap[bar].args[opts.name] = nil
503 end,
504 },
505 rename = {
506 name = L["Name"],
507 order = 1,
508 type = "input",
509 get = function() return opts.name end,
510 set = function(info, value)
511 -- check for existing state name
512 if states[value] then
513 L["State named '%s' already exists"]:format(value)
514 end
515 local args = optionMap[bar].args
516 states[value], args[value], states[opts.name], args[opts.name] = states[opts.name], args[opts.name], nil, nil
517 opts.name = value
518 update()
519 end,
520 pattern = "^%w*$",
521 usage = L["State names must be alphanumeric without spaces"],
522 },
523 ordering = {
524 name = L["Evaluation Order"],
525 desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"],
526 order = 2,
527 type = "group",
528 inline = true,
529 args = {
530 up = {
531 name = L["Up"],
532 order = 1,
533 type = "execute",
534 width = "half",
535 func = function()
536 local before, after = getNeighbors()
537 if before then
538 swapOrder(before, opts.name)
539 update()
540 end
541 end,
542 },
543 down = {
544 name = L["Down"],
545 order = 2,
546 type = "execute",
547 width = "half",
548 func = function()
549 local before, after = getNeighbors()
550 if after then
551 swapOrder(opts.name, after)
552 update()
553 end
554 end,
555 }
556 }
557 }
558 }
559 },
560 properties = {
561 name = L["Properties"],
562 order = 2,
563 type = "group",
564 args = {
565 desc = {
566 name = L["Set the properties for the bar when in this state"],
567 order = 1,
568 type = "description"
569 },
570 hide = {
571 name = L["Hide Bar"],
572 order = 2,
573 type = "toggle",
574 set = setprop,
575 get = getprop,
576 },
577 page = {
578 name = L["Show Page #"],
579 order = 3,
580 type = "select",
581 disabled = function()
582 return bar:GetNumPages() < 2
583 end,
584 hidden = function()
585 return bar:GetNumPages() < 2
586 end,
587 values = function()
588 local pages = { none = " " }
589 for i = 1, bar:GetNumPages() do
590 pages[i] = i
591 end
592 return pages
593 end,
594 set = function(info, value)
595 if value == "none" then
596 setprop(info, nil)
597 else
598 setprop(info, value)
599 end
600 end,
601 get = function(info)
602 return getprop(info) or "none"
603 end,
604 },
605 keybindstate = {
606 name = L["Override Keybinds"],
607 desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"],
608 order = 4,
609 type = "toggle",
610 set = setprop,
611 get = getprop,
612 },
613 position = {
614 name = L["Position"],
615 order = 5,
616 type = "group",
617 inline = true,
618 args = {
619 enableanchor = {
620 name = L["Set New Position"],
621 order = 1,
622 type = "toggle",
623 set = setprop,
624 get = getprop,
625 },
626 anchorPoint = {
627 name = L["Point"],
628 order = 2,
629 type = "select",
630 values = pointTable,
631 set = function(info, value) setprop(info, value ~= "NONE" and value or nil) end,
632 get = function(info) return getprop(info) or "NONE" end,
633 disabled = anchordisable,
634 hidden = anchordisable,
635 },
636 anchorRelPoint = {
637 name = L["Relative Point"],
638 order = 3,
639 type = "select",
640 values = pointTable,
641 set = function(info, value) setprop(info, value ~= "NONE" and value or nil) end,
642 get = function(info) return getprop(info) or "NONE" end,
643 disabled = anchordisable,
644 hidden = anchordisable,
645 },
646 anchorX = {
647 name = L["X Offset"],
648 order = 4,
649 type = "range",
650 min = -100,
651 max = 100,
652 step = 1,
653 set = setprop,
654 get = getprop,
655 disabled = anchordisable,
656 hidden = anchordisable,
657 },
658 anchorY = {
659 name = L["Y Offset"],
660 order = 5,
661 type = "range",
662 min = -100,
663 max = 100,
664 step = 1,
665 set = setprop,
666 get = getprop,
667 disabled = anchordisable,
668 hidden = anchordisable,
669 },
670 },
671 },
672 scale = {
673 name = L["Scale"],
674 order = 6,
675 type = "group",
676 inline = true,
677 args = {
678 enablescale = {
679 name = L["Set New Scale"],
680 order = 1,
681 type = "toggle",
682 set = setprop,
683 get = getprop,
684 },
685 scale = {
686 name = L["Scale"],
687 order = 2,
688 type = "range",
689 min = 0.1,
690 max = 2.5,
691 step = 0.05,
692 isPercent = true,
693 set = setprop,
694 get = function(info) return getprop(info) or 1 end,
695 disabled = function() return not GetProperty(bar, opts.name, "enablescale") end,
696 hidden = function() return not GetProperty(bar, opts.name, "enablescale") end,
697 },
698 },
699 },
700 },
701 },
702 rules = {
703 name = L["Selection Rule"],
704 order = 3,
705 type = "group",
706 args = {
707 mode = {
708 name = L["Select this state"],
709 order = 2,
710 type = "select",
711 style = "radio",
712 values = {
713 default = L["by default"],
714 any = L["when ANY of these"],
715 all = L["when ALL of these"],
716 custom = L["via custom rule"],
717 keybind = L["via keybinding"],
718 },
719 set = function( info, value )
720 setrule("type", value)
721 fixall()
722 update()
723 end,
724 get = function( info )
725 return getrule("type")
726 end,
727 },
728 clear = {
729 name = L["Clear All"],
730 order = 3,
731 type = "execute",
732 hidden = function()
733 local t = getrule("type")
734 return t ~= "any" and t ~= "all"
735 end,
736 disabled = function()
737 local t = getrule("type")
738 return t ~= "any" and t ~= "all"
739 end,
740 func = function()
741 local type = getrule("type")
742 if type == "custom" then
743 setrule("custom","")
744 elseif type == "any" or type == "all" then
745 setrule("values", {})
746 end
747 update()
748 end,
749 },
750 inputs = {
751 name = L["Conditions"],
752 order = 4,
753 type = "multiselect",
754 hidden = function()
755 local t = getrule("type")
756 return t ~= "any" and t ~= "all"
757 end,
758 disabled = function()
759 local t = getrule("type")
760 return t ~= "any" and t ~= "all"
761 end,
762 values = ruleSelect,
763 set = function(info, key, value )
764 setrule(ruleMap[key], value or nil, "values")
765 if value then
766 fixall(ruleMap[key])
767 end
768 update()
769 end,
770 get = function(info, key)
771 return getrule("values", ruleMap[key]) or false
772 end,
773 },
774 custom = {
775 name = L["Custom Rule"],
776 order = 5,
777 type = "input",
778 multiline = true,
779 hidden = function()
780 return getrule("type") ~= "custom"
781 end,
782 disabled = function()
783 return getrule("type") ~= "custom"
784 end,
785 desc = L["Syntax like macro rules: see preset rules for examples"],
786 set = function(info, value)
787 setrule("custom",value)
788 update()
789 end,
790 get = function(info)
791 return getrule("custom") or ""
792 end,
793 validate = function (info, rule)
794 local s = rule:gsub("%s","") -- remove all spaces
795 -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler
796 repeat
797 if s == "" then
798 return true
799 end
800 local c, r = s:match("(%b[])(.*)")
801 if c == nil and s and #s > 0 then
802 return L["Invalid custom rule '%s': each clause must appear within [brackets]"]:format(rule)
803 end
804 s = r
805 until c == nil
806 return true
807 end,
808 },
809 keybind = {
810 name = L["Keybinding"],
811 order = 6,
812 inline = true,
813 hidden = function() return getrule("type") ~= "keybind" end,
814 disabled = function() return getrule("type") ~= "keybind" end,
815 type = "group",
816 args = {
817 desc = {
818 name = L["Invoking a state keybind overrides all other transition rules. Toggle the keybind again to remove the override and return to the specified toggle-off state."],
819 order = 1,
820 type = "description",
821 },
822 keybind = {
823 name = L["State Hotkey"],
824 desc = L["Define an override toggle keybind"],
825 order = 2,
826 type = "keybinding",
827 set = function(info, value)
828 setrule("keybind",value)
829 update()
830 end,
831 get = function() return getrule("keybind") end,
832 },
833 default = {
834 name = L["Toggle Off State"],
835 desc = L["Select a state to return to when the keybind override is toggled off"],
836 order = 3,
837 type = "select",
838 values = function()
839 local t = { }
840 for k in pairs(states) do
841 if k ~= opts.name then
842 t[k] = k
843 end
844 end
845 return t
846 end,
847 set = function(info, value)
848 setrule("keybindreturn",value)
849 update()
850 end,
851 get = function() return getrule("keybindreturn") end,
852 },
853 },
854 },
855 },
856 },
857 }
858 return opts
859 end
860
861
862 CreateBarOptions = function(bar)
863 local private = { }
864 local options = {
865 type = "group",
866 name = L["Dynamic State"],
867 childGroups = "tree",
868 disabled = InCombatLockdown,
869 args = {
870 __desc__ = {
871 name = L["States are evaluated in the order they are listed"],
872 order = 1,
873 type = "description",
874 },
875 __new__ = {
876 name = L["New State..."],
877 order = 2,
878 type = "group",
879 args = {
880 name = {
881 name = L["State Name"],
882 desc = L["Set a name for the new state"],
883 order = 1,
884 type = "input",
885 get = function() return private.newstatename or "" end,
886 set = function(info,value) private.newstatename = value end,
887 pattern = "^%w*$",
888 usage = L["State names must be alphanumeric without spaces"],
889 },
890 create = {
891 name = L["Create State"],
892 order = 2,
893 type = "execute",
894 func = function ()
895 local name = private.newstatename
896 if states[name] then
897 ReAction:UserError(L["State named '%s' already exists"]:format(name))
898 else
899 -- TODO: select default state options and pass as final argument
900 states[name] = { }
901 optionMap[bar].args[name] = CreateStateOptions(bar,name)
902 private.newstatename = ""
903 end
904 end,
905 disabled = function()
906 local name = private.newstatename or ""
907 return #name == 0 or name:find("%W")
908 end,
909 }
910 }
911 }
912 }
913 }
914 local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
915 if states then
916 for name, config in pairs(states) do
917 options.args[name] = CreateStateOptions(bar,name)
918 end
919 end
920 optionMap[bar] = options
921 return options
922 end
923 end
924
925 function module:GetBarOptions(bar)
926 return CreateBarOptions(bar)
927 end