flickerstreak@25: --[[ flickerstreak@62: ReAction bar state driver interface flickerstreak@25: flickerstreak@25: --]] flickerstreak@25: flickerstreak@25: -- local imports flickerstreak@25: local ReAction = ReAction flickerstreak@25: local L = ReAction.L flickerstreak@25: local _G = _G flickerstreak@25: local InCombatLockdown = InCombatLockdown flickerstreak@25: flickerstreak@25: -- module declaration flickerstreak@25: local moduleID = "State" flickerstreak@65: local module = ReAction:NewModule( moduleID, "AceEvent-3.0" ) flickerstreak@62: flickerstreak@64: -- Utility -- flickerstreak@62: flickerstreak@62: -- traverse a table tree by key list and fetch the result or first nil flickerstreak@62: local function tfetch(t, ...) flickerstreak@62: for i = 1, select('#', ...) do flickerstreak@62: t = t and t[select(i, ...)] flickerstreak@62: end flickerstreak@62: return t flickerstreak@62: end flickerstreak@62: flickerstreak@62: -- traverse a table tree by key list and build tree as necessary flickerstreak@62: local function tbuild(t, ...) flickerstreak@62: for i = 1, select('#', ...) do flickerstreak@62: local key = select(i, ...) flickerstreak@62: if not t[key] then t[key] = { } end flickerstreak@62: t = t[key] flickerstreak@62: end flickerstreak@62: return t flickerstreak@62: end flickerstreak@62: flickerstreak@64: -- PRIVATE -- flickerstreak@68: flickerstreak@68: local InitRules, ApplyStates, SetProperty, GetProperty flickerstreak@64: do flickerstreak@67: -- As far as I can tell the macro clauses are NOT locale-specific. flickerstreak@67: local ruleformats = { flickerstreak@67: stealth = "stealth", flickerstreak@67: nostealth = "nostealth", flickerstreak@67: shadowform = "form:1", flickerstreak@67: noshadowform = "noform", flickerstreak@67: pet = "pet", flickerstreak@67: nopet = "nopet", flickerstreak@67: harm = "target=target,harm", flickerstreak@67: help = "target=target,help", flickerstreak@67: notarget = "target=target,noexists", flickerstreak@67: focusharm = "target=focus,harm", flickerstreak@67: focushelp = "target=focus,help", flickerstreak@67: nofocus = "target=focus,noexists", flickerstreak@67: raid = "group:raid", flickerstreak@67: party = "group:party", flickerstreak@67: solo = "nogroup", flickerstreak@67: combat = "combat", flickerstreak@67: nocombat = "nocombat", flickerstreak@67: } flickerstreak@65: flickerstreak@67: -- Have to do these shenanigans instead of hardcoding the stances/forms because flickerstreak@67: -- the ordering varies if the character is missing a form. For warriors flickerstreak@67: -- this is rarely a problem (c'mon, who actually skips the level 10 def stance quest?) flickerstreak@67: -- but for druids it can be. Some people never bother to do the aquatic form quest flickerstreak@67: -- until well past when they get cat form, and stance 5 can be flight, tree, or moonkin flickerstreak@67: -- depending on talents. flickerstreak@65: function InitRules() flickerstreak@65: local forms = { } flickerstreak@67: -- sort by icon since it's locale-independent flickerstreak@65: for i = 1, GetNumShapeshiftForms() do flickerstreak@65: local icon = GetShapeshiftFormInfo(i) flickerstreak@65: forms[icon] = i; flickerstreak@65: end flickerstreak@65: -- use 9 if not found since 9 is never a valid stance/form flickerstreak@65: local defensive = forms["Interface\\Icons\\Ability_Warrior_DefensiveStance"] or 9 flickerstreak@65: local berserker = forms["Interface\\Icons\\Ability_Racial_Avatar"] or 9 flickerstreak@65: local bear = forms["Interface\\Icons\\Ability_Racial_BearForm"] or 9 -- bear and dire bear share the same icon flickerstreak@65: local aquatic = forms["Interface\\Icons\\Ability_Druid_AquaticForm"] or 9 flickerstreak@65: local cat = forms["Interface\\Icons\\Ability_Druid_CatForm"] or 9 flickerstreak@65: local travel = forms["Interface\\Icons\\Ability_Druid_TravelForm"] or 9 flickerstreak@65: local treekin = forms["Interface\\Icons\\Ability_Druid_TreeofLife"] or forms["Interface\\Icons\\Spell_Nature_ForceOfNature"] or 9 flickerstreak@67: local flight = forms["Interface\\Icons\\Ability_Druid_FlightForm"] or 9 -- flight and swift flight share the same icon flickerstreak@65: flickerstreak@65: ruleformats.battle = "stance:1" flickerstreak@65: ruleformats.defensive = ("stance:%d"):format(defensive) flickerstreak@65: ruleformats.berserker = ("stance:%d"):format(berserker) flickerstreak@65: ruleformats.caster = ("form:0/%d/%d/%d"):format(aquatic, travel, flight) flickerstreak@65: ruleformats.bear = ("form:%d"):format(bear) flickerstreak@65: ruleformats.cat = ("form:%d"):format(cat) flickerstreak@65: ruleformats.treeOrMoonkin = ("form:%d"):format(treekin) flickerstreak@64: end flickerstreak@62: flickerstreak@68: -- return a new array of keys of table 't', sorted by comparing flickerstreak@68: -- sub-fields (obtained via tfetch) of the table values flickerstreak@68: local function fieldsort( t, ... ) flickerstreak@68: local r = { } flickerstreak@68: for k in pairs(t) do flickerstreak@68: table.insert(r,k) flickerstreak@68: end flickerstreak@68: local dotdotdot = { ... } flickerstreak@68: table.sort(r, function(lhs, rhs) flickerstreak@68: local olhs = tfetch(t[lhs], unpack(dotdotdot)) or 0 flickerstreak@68: local orhs = tfetch(t[rhs], unpack(dotdotdot)) or 0 flickerstreak@68: return olhs < orhs flickerstreak@68: end) flickerstreak@68: return r flickerstreak@68: end flickerstreak@68: flickerstreak@65: local function BuildRuleString(states) flickerstreak@64: local s = "" flickerstreak@64: local default flickerstreak@68: local sorted = fieldsort(states, "rule", "order") flickerstreak@64: for idx, name in ipairs(sorted) do flickerstreak@64: local state = states[name] flickerstreak@64: local semi = #s > 0 and "; " or "" flickerstreak@64: local mode = tfetch(state,"rule","type") flickerstreak@64: if mode == "default" then flickerstreak@64: default = name flickerstreak@64: elseif mode == "custom" then flickerstreak@64: if state.rule.custom then flickerstreak@64: -- strip out all spaces from the custom rule flickerstreak@64: s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name) flickerstreak@64: end flickerstreak@64: elseif mode == "any" then flickerstreak@64: if state.rule.values then flickerstreak@64: local clause = "" flickerstreak@64: for key, value in pairs(state.rule.values) do flickerstreak@64: clause = ("%s[%s]"):format(clause,ruleformats[key]) flickerstreak@64: end flickerstreak@64: if #clause > 0 then flickerstreak@64: s = ("%s%s%s %s"):format(s, semi, clause, name) flickerstreak@64: end flickerstreak@64: end flickerstreak@64: elseif mode == "all" then flickerstreak@64: if state.rule.values then flickerstreak@64: local clause = "" flickerstreak@64: for key, value in pairs(state.rule.values) do flickerstreak@64: clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key]) flickerstreak@64: end flickerstreak@64: if #clause > 0 then flickerstreak@64: s = ("%s%s[%s] %s"):format(s, semi, clause, name) flickerstreak@64: end flickerstreak@64: end flickerstreak@64: end flickerstreak@64: end flickerstreak@64: if default then flickerstreak@64: s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default) flickerstreak@64: end flickerstreak@68: return s, default flickerstreak@64: end flickerstreak@64: flickerstreak@64: local drivers = setmetatable({},{__mode="k"}) flickerstreak@68: local propertyFuncs = { } flickerstreak@64: flickerstreak@64: function ApplyStates( bar ) flickerstreak@64: local states = tfetch(module.db.profile.bars, bar:GetName(), "states") flickerstreak@64: if states then flickerstreak@64: local frame = bar:GetFrame() flickerstreak@68: local string, default = BuildRuleString(states) flickerstreak@64: if string and #string > 0 then flickerstreak@68: drivers[bar] = true flickerstreak@68: -- register a map for each "statemap-reaction-XXX" to set 'state' to 'XXX' flickerstreak@68: -- UNLESS we're in a keybound state AND there's a default state, in which case flickerstreak@68: -- all keybound states go back to themselves. flickerstreak@68: local keybindprefix flickerstreak@68: if default then flickerstreak@68: local tmp = { } flickerstreak@68: for state, config in pairs(states) do flickerstreak@68: if tfetch(config, "rule", "type") == "keybind" then flickerstreak@68: bar:SetStateKeybind(tfetch(config,"rule","keybind"), state, tfetch(config,"rule","keybindreturn") or default or 0) flickerstreak@68: table.insert(tmp, ("%s:%s"):format(state,state)) flickerstreak@68: end flickerstreak@68: end flickerstreak@68: if #tmp > 0 then flickerstreak@68: table.insert(tmp,"") -- to get a final ';' flickerstreak@68: end flickerstreak@68: keybindprefix = table.concat(tmp,";") flickerstreak@68: end flickerstreak@68: for state in pairs(states) do flickerstreak@68: frame:SetAttribute(("statemap-reaction-%s"):format(state), ("%s%s"):format(keybindprefix or "",state)) flickerstreak@68: end flickerstreak@64: -- register a handler to set the value of attribute "state-reaction" flickerstreak@64: -- in response to events as per the rule string flickerstreak@64: RegisterStateDriver(frame, "reaction", string) flickerstreak@68: SecureStateHeader_Refresh(frame) flickerstreak@64: elseif drivers[bar] then flickerstreak@64: UnregisterStateDriver(frame, "reaction") flickerstreak@64: drivers[bar] = nil flickerstreak@64: end flickerstreak@68: for k, f in pairs(propertyFuncs) do flickerstreak@68: f(bar, states) flickerstreak@68: end flickerstreak@64: end flickerstreak@64: end flickerstreak@68: flickerstreak@68: function GetProperty( bar, state, propname ) flickerstreak@68: return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname) flickerstreak@68: end flickerstreak@68: flickerstreak@68: function SetProperty( bar, state, propname, value ) flickerstreak@68: local states = tbuild(module.db.profile.bars, bar:GetName(), "states") flickerstreak@68: tbuild(states, state)[propname] = value flickerstreak@68: local f = propertyFuncs[propname] flickerstreak@68: if f then flickerstreak@68: f(bar, states) flickerstreak@68: end flickerstreak@68: end flickerstreak@68: flickerstreak@68: -- state property functions flickerstreak@68: function propertyFuncs.hide( bar, states ) flickerstreak@68: local tmp = { } flickerstreak@68: for state, config in pairs(states) do flickerstreak@68: if config.hide then flickerstreak@68: table.insert(tmp, state) flickerstreak@68: end flickerstreak@68: end flickerstreak@68: local s = table.concat(tmp,",") flickerstreak@68: bar:SetHideStates(s) flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.page( bar, states ) flickerstreak@68: local map = { } flickerstreak@68: for state, config in pairs(states) do flickerstreak@68: map[state] = config.page flickerstreak@68: end flickerstreak@68: bar:SetStatePageMap(state, map) flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.keybindstate( bar, states ) flickerstreak@68: local map = { } flickerstreak@68: for state, config in pairs(states) do flickerstreak@68: if config.keybindstate then flickerstreak@68: table.insert(map,state) flickerstreak@68: end flickerstreak@68: end flickerstreak@68: bar:SetStateKeybindOverrideMap(map) flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.enableanchor( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.anchorPoint( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.anchorRelPoint( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.anchorX( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.anchorY( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.enablescale( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@68: function propertyFuncs.scale( bar, states ) flickerstreak@68: flickerstreak@68: end flickerstreak@68: flickerstreak@64: end flickerstreak@64: flickerstreak@64: flickerstreak@68: flickerstreak@68: -- module event handlers -- flickerstreak@68: flickerstreak@65: function module:OnInitialize() flickerstreak@65: self.db = ReAction.db:RegisterNamespace( moduleID, flickerstreak@65: { flickerstreak@65: profile = { flickerstreak@65: bars = { }, flickerstreak@65: } flickerstreak@65: } flickerstreak@65: ) flickerstreak@65: flickerstreak@65: InitRules() flickerstreak@65: self:RegisterEvent("PLAYER_AURAS_CHANGED") flickerstreak@65: flickerstreak@65: ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") flickerstreak@65: flickerstreak@65: ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") flickerstreak@65: ReAction.RegisterCallback(self, "OnRefreshBar") flickerstreak@65: ReAction.RegisterCallback(self, "OnEraseBar") flickerstreak@65: ReAction.RegisterCallback(self, "OnRenameBar") flickerstreak@65: ReAction.RegisterCallback(self, "OnConfigModeChanged") flickerstreak@65: end flickerstreak@65: flickerstreak@65: function module:PLAYER_AURAS_CHANGED() flickerstreak@65: self:UnregisterEvent("PLAYER_AURAS_CHANGED") flickerstreak@66: -- on login the number of stances is 0 until this event fires during the init sequence. flickerstreak@66: -- however if you reload just the UI the number of stances is correct immediately flickerstreak@66: -- and this event won't fire until you gain/lose buffs/debuffs, at which point you might flickerstreak@66: -- be in combat. flickerstreak@66: if not InCombatLockdown() then flickerstreak@66: InitRules() flickerstreak@66: for name, bar in ReAction:IterateBars() do flickerstreak@67: self:OnRefreshBar(nil,bar,name) flickerstreak@66: end flickerstreak@66: end flickerstreak@65: end flickerstreak@65: flickerstreak@65: function module:OnRefreshBar(event, bar, name) flickerstreak@65: local c = self.db.profile.bars[name] flickerstreak@65: if c then flickerstreak@68: ApplyStates(bar) flickerstreak@65: end flickerstreak@65: end flickerstreak@65: flickerstreak@65: function module:OnEraseBar(event, bar, name) flickerstreak@65: self.db.profile.bars[name] = nil flickerstreak@65: end flickerstreak@65: flickerstreak@65: function module:OnRenameBar(event, bar, oldname, newname) flickerstreak@65: local b = self.db.profile.bars flickerstreak@65: bars[newname], bars[oldname] = bars[oldname], nil flickerstreak@65: end flickerstreak@65: flickerstreak@65: function module:OnConfigModeChanged(event, mode) flickerstreak@65: -- TODO: unregister all state drivers (temporarily) and hidestates flickerstreak@65: end flickerstreak@65: flickerstreak@64: flickerstreak@64: flickerstreak@64: -- Options -- flickerstreak@64: flickerstreak@64: local CreateBarOptions flickerstreak@62: do flickerstreak@62: local function ClassCheck(...) flickerstreak@62: for i = 1, select('#',...) do flickerstreak@62: local _, c = UnitClass("player") flickerstreak@62: if c == select(i,...) then flickerstreak@62: return false flickerstreak@62: end flickerstreak@62: end flickerstreak@62: return true flickerstreak@62: end flickerstreak@62: flickerstreak@64: -- pre-sorted by the order they should appear in flickerstreak@64: local rules = { flickerstreak@64: -- rule hidden fields flickerstreak@64: { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, flickerstreak@64: { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {treeOrMoonkin = L["Tree/Moonkin"]} } }, flickerstreak@64: { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } }, flickerstreak@64: { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, flickerstreak@64: { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, flickerstreak@64: { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, flickerstreak@64: { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, flickerstreak@64: { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, flickerstreak@64: { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, flickerstreak@62: } flickerstreak@62: flickerstreak@64: local ruleSelect = { } flickerstreak@64: local ruleMap = { } flickerstreak@64: local optionMap = setmetatable({},{__mode="k"}) flickerstreak@62: flickerstreak@68: local pointTable = { flickerstreak@68: NONE = " ", flickerstreak@68: CENTER = L["Center"], flickerstreak@68: LEFT = L["Left"], flickerstreak@68: RIGHT = L["Right"], flickerstreak@68: TOP = L["Top"], flickerstreak@68: BOTTOM = L["Bottom"], flickerstreak@68: TOPLEFT = L["Top Left"], flickerstreak@68: TOPRIGHT = L["Top Right"], flickerstreak@68: BOTTOMLEFT = L["Bottom Left"], flickerstreak@68: BOTTOMRIGHT = L["Bottom Right"], flickerstreak@68: } flickerstreak@68: flickerstreak@64: -- unpack rules table into ruleSelect and ruleMap flickerstreak@64: for _, c in ipairs(rules) do flickerstreak@64: local rule, hidden, fields = unpack(c) flickerstreak@64: if not hidden then flickerstreak@64: for _, field in ipairs(fields) do flickerstreak@64: local key, label = next(field) flickerstreak@64: table.insert(ruleSelect, label) flickerstreak@64: table.insert(ruleMap, key) flickerstreak@62: end flickerstreak@62: end flickerstreak@62: end flickerstreak@62: flickerstreak@62: local function CreateStateOptions(bar, name) flickerstreak@62: local opts = { flickerstreak@62: type = "group", flickerstreak@62: name = name, flickerstreak@64: childGroups = "tab", flickerstreak@25: } flickerstreak@62: flickerstreak@64: local states = tbuild(module.db.profile.bars, bar:GetName(), "states") flickerstreak@64: flickerstreak@68: local function update() flickerstreak@68: ApplyStates(bar) flickerstreak@68: end flickerstreak@68: flickerstreak@68: local function setrule( key, value, ... ) flickerstreak@64: tbuild(states, opts.name, "rule", ...)[key] = value flickerstreak@64: end flickerstreak@64: flickerstreak@68: local function getrule( ... ) flickerstreak@64: return tfetch(states, opts.name, "rule", ...) flickerstreak@64: end flickerstreak@64: flickerstreak@68: local function setprop(info, value) flickerstreak@68: SetProperty(bar, opts.name, info[#info], value) flickerstreak@68: end flickerstreak@68: flickerstreak@68: local function getprop(info) flickerstreak@68: return GetProperty(bar, opts.name, info[#info]) flickerstreak@68: end flickerstreak@68: flickerstreak@64: local function fixall(setkey) flickerstreak@64: -- if multiple selections in the same group are chosen when 'all' is selected, flickerstreak@64: -- keep only one of them. If changing the mode, the first in the fields list will flickerstreak@64: -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, flickerstreak@64: -- it will be retained. flickerstreak@64: local notified = false flickerstreak@64: for _, c in ipairs(rules) do flickerstreak@64: local rule, hidden, fields = unpack(c) flickerstreak@64: local found = false flickerstreak@64: for key in ipairs(fields) do flickerstreak@68: if getrule("values",key) then flickerstreak@64: if (found or setkey) and key ~= setkey then flickerstreak@68: setrule(key,false,"values") flickerstreak@64: if not setkey and not notified then flickerstreak@64: ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) flickerstreak@64: notified = true flickerstreak@64: end flickerstreak@64: end flickerstreak@64: found = true flickerstreak@64: end flickerstreak@62: end flickerstreak@62: end flickerstreak@62: end flickerstreak@62: flickerstreak@64: local function getNeighbors() flickerstreak@64: local before, after flickerstreak@64: for k, v in pairs(states) do flickerstreak@64: local o = tonumber(tfetch(v, "rule", "order")) flickerstreak@64: if o and k ~= opts.name then flickerstreak@64: local obefore = tfetch(states,before,"rule","order") flickerstreak@64: local oafter = tfetch(states,after,"rule","order") flickerstreak@64: if o < opts.order and (not obefore or obefore < o) then flickerstreak@64: before = k flickerstreak@64: end flickerstreak@64: if o > opts.order and (not oafter or oafter > o) then flickerstreak@64: after = k flickerstreak@64: end flickerstreak@64: end flickerstreak@64: end flickerstreak@64: return before, after flickerstreak@64: end flickerstreak@64: flickerstreak@64: local function swapOrder( a, b ) flickerstreak@64: -- do options table flickerstreak@64: local args = optionMap[bar].args flickerstreak@64: args[a].order, args[b].order = args[b].order, args[a].order flickerstreak@64: -- do profile flickerstreak@64: a = tbuild(states, a, "rule") flickerstreak@64: b = tbuild(states, b, "rule") flickerstreak@64: a.order, b.order = b.order, a.order flickerstreak@64: end flickerstreak@64: flickerstreak@68: local function anchordisable() flickerstreak@68: return not GetProperty(bar, opts.name, "enableanchor") flickerstreak@64: end flickerstreak@64: flickerstreak@68: tbuild(states, name) flickerstreak@64: flickerstreak@68: opts.order = getrule("order") flickerstreak@64: if opts.order == nil then flickerstreak@64: -- add after the highest flickerstreak@64: opts.order = 100 flickerstreak@64: for _, state in pairs(states) do flickerstreak@64: local x = tonumber(tfetch(state, "rule", "order")) flickerstreak@64: if x and x >= opts.order then flickerstreak@64: opts.order = x + 1 flickerstreak@64: end flickerstreak@64: end flickerstreak@68: setrule("order",opts.order) flickerstreak@64: end flickerstreak@64: flickerstreak@64: opts.args = { flickerstreak@68: ordering = { flickerstreak@68: name = L["Info"], flickerstreak@68: order = 1, flickerstreak@64: type = "group", flickerstreak@64: args = { flickerstreak@64: delete = { flickerstreak@68: name = L["Delete this State"], flickerstreak@68: order = -1, flickerstreak@64: type = "execute", flickerstreak@64: func = function(info) flickerstreak@68: if states[opts.name] then flickerstreak@68: states[opts.name] = nil flickerstreak@68: ApplyStates(bar) flickerstreak@68: end flickerstreak@64: optionMap[bar].args[opts.name] = nil flickerstreak@64: end, flickerstreak@64: }, flickerstreak@64: rename = { flickerstreak@64: name = L["Name"], flickerstreak@64: order = 1, flickerstreak@68: type = "input", flickerstreak@64: get = function() return opts.name end, flickerstreak@64: set = function(info, value) flickerstreak@64: -- check for existing state name flickerstreak@64: if states[value] then flickerstreak@64: L["State named '%s' already exists"]:format(value) flickerstreak@64: end flickerstreak@64: local args = optionMap[bar].args flickerstreak@64: states[value], args[value], states[opts.name], args[opts.name] = states[opts.name], args[opts.name], nil, nil flickerstreak@64: opts.name = value flickerstreak@64: update() flickerstreak@64: end, flickerstreak@64: pattern = "^%w*$", flickerstreak@64: usage = L["State names must be alphanumeric without spaces"], flickerstreak@64: }, flickerstreak@64: ordering = { flickerstreak@64: name = L["Evaluation Order"], flickerstreak@64: desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"], flickerstreak@64: order = 2, flickerstreak@68: type = "group", flickerstreak@68: inline = true, flickerstreak@64: args = { flickerstreak@64: up = { flickerstreak@68: name = L["Up"], flickerstreak@68: order = 1, flickerstreak@64: type = "execute", flickerstreak@64: width = "half", flickerstreak@64: func = function() flickerstreak@64: local before, after = getNeighbors() flickerstreak@64: if before then flickerstreak@64: swapOrder(before, opts.name) flickerstreak@64: update() flickerstreak@64: end flickerstreak@64: end, flickerstreak@64: }, flickerstreak@64: down = { flickerstreak@68: name = L["Down"], flickerstreak@68: order = 2, flickerstreak@64: type = "execute", flickerstreak@64: width = "half", flickerstreak@64: func = function() flickerstreak@64: local before, after = getNeighbors() flickerstreak@64: if after then flickerstreak@64: swapOrder(opts.name, after) flickerstreak@64: update() flickerstreak@64: end flickerstreak@64: end, flickerstreak@64: } flickerstreak@64: } flickerstreak@68: } flickerstreak@64: } flickerstreak@64: }, flickerstreak@68: properties = { flickerstreak@68: name = L["Properties"], flickerstreak@68: order = 2, flickerstreak@68: type = "group", flickerstreak@68: args = { flickerstreak@68: desc = { flickerstreak@68: name = L["Set the properties for the bar when in this state"], flickerstreak@68: order = 1, flickerstreak@68: type = "description" flickerstreak@68: }, flickerstreak@68: hide = { flickerstreak@68: name = L["Hide Bar"], flickerstreak@68: order = 2, flickerstreak@68: type = "toggle", flickerstreak@68: set = setprop, flickerstreak@68: get = getprop, flickerstreak@68: }, flickerstreak@68: page = { flickerstreak@68: name = L["Show Page #"], flickerstreak@68: order = 3, flickerstreak@68: type = "select", flickerstreak@68: disabled = function() flickerstreak@68: return bar:GetNumPages() < 2 flickerstreak@68: end, flickerstreak@68: hidden = function() flickerstreak@68: return bar:GetNumPages() < 2 flickerstreak@68: end, flickerstreak@68: values = function() flickerstreak@68: local pages = { none = " " } flickerstreak@68: for i = 1, bar:GetNumPages() do flickerstreak@68: pages[i] = i flickerstreak@68: end flickerstreak@68: return pages flickerstreak@68: end, flickerstreak@68: set = function(info, value) flickerstreak@68: if value == "none" then flickerstreak@68: setprop(info, nil) flickerstreak@68: else flickerstreak@68: setprop(info, value) flickerstreak@68: end flickerstreak@68: end, flickerstreak@68: get = function(info) flickerstreak@68: return getprop(info) or "none" flickerstreak@68: end, flickerstreak@68: }, flickerstreak@68: keybindstate = { flickerstreak@68: name = L["Override Keybinds"], flickerstreak@68: desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"], flickerstreak@68: order = 4, flickerstreak@68: type = "toggle", flickerstreak@68: set = setprop, flickerstreak@68: get = getprop, flickerstreak@68: }, flickerstreak@68: position = { flickerstreak@68: name = L["Position"], flickerstreak@68: order = 5, flickerstreak@68: type = "group", flickerstreak@68: inline = true, flickerstreak@68: args = { flickerstreak@68: enableanchor = { flickerstreak@68: name = L["Set New Position"], flickerstreak@68: order = 1, flickerstreak@68: type = "toggle", flickerstreak@68: set = setprop, flickerstreak@68: get = getprop, flickerstreak@68: }, flickerstreak@68: anchorPoint = { flickerstreak@68: name = L["Point"], flickerstreak@68: order = 2, flickerstreak@68: type = "select", flickerstreak@68: values = pointTable, flickerstreak@68: set = function(info, value) setprop(info, value ~= "NONE" and value or nil) end, flickerstreak@68: get = function(info) return getprop(info) or "NONE" end, flickerstreak@68: disabled = anchordisable, flickerstreak@68: hidden = anchordisable, flickerstreak@68: }, flickerstreak@68: anchorRelPoint = { flickerstreak@68: name = L["Relative Point"], flickerstreak@68: order = 3, flickerstreak@68: type = "select", flickerstreak@68: values = pointTable, flickerstreak@68: set = function(info, value) setprop(info, value ~= "NONE" and value or nil) end, flickerstreak@68: get = function(info) return getprop(info) or "NONE" end, flickerstreak@68: disabled = anchordisable, flickerstreak@68: hidden = anchordisable, flickerstreak@68: }, flickerstreak@68: anchorX = { flickerstreak@68: name = L["X Offset"], flickerstreak@68: order = 4, flickerstreak@68: type = "range", flickerstreak@68: min = -100, flickerstreak@68: max = 100, flickerstreak@68: step = 1, flickerstreak@68: set = setprop, flickerstreak@68: get = getprop, flickerstreak@68: disabled = anchordisable, flickerstreak@68: hidden = anchordisable, flickerstreak@68: }, flickerstreak@68: anchorY = { flickerstreak@68: name = L["Y Offset"], flickerstreak@68: order = 5, flickerstreak@68: type = "range", flickerstreak@68: min = -100, flickerstreak@68: max = 100, flickerstreak@68: step = 1, flickerstreak@68: set = setprop, flickerstreak@68: get = getprop, flickerstreak@68: disabled = anchordisable, flickerstreak@68: hidden = anchordisable, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: scale = { flickerstreak@68: name = L["Scale"], flickerstreak@68: order = 6, flickerstreak@68: type = "group", flickerstreak@68: inline = true, flickerstreak@68: args = { flickerstreak@68: enablescale = { flickerstreak@68: name = L["Set New Scale"], flickerstreak@68: order = 1, flickerstreak@68: type = "toggle", flickerstreak@68: set = setprop, flickerstreak@68: get = getprop, flickerstreak@68: }, flickerstreak@68: scale = { flickerstreak@68: name = L["Scale"], flickerstreak@68: order = 2, flickerstreak@68: type = "range", flickerstreak@68: min = 0.1, flickerstreak@68: max = 2.5, flickerstreak@68: step = 0.05, flickerstreak@68: isPercent = true, flickerstreak@68: set = setprop, flickerstreak@68: get = function(info) return getprop(info) or 1 end, flickerstreak@68: disabled = function() return not GetProperty(bar, opts.name, "enablescale") end, flickerstreak@68: hidden = function() return not GetProperty(bar, opts.name, "enablescale") end, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@64: rules = { flickerstreak@68: name = L["Selection Rule"], flickerstreak@68: order = 3, flickerstreak@64: type = "group", flickerstreak@64: args = { flickerstreak@64: mode = { flickerstreak@68: name = L["Select this state"], flickerstreak@68: order = 2, flickerstreak@64: type = "select", flickerstreak@64: style = "radio", flickerstreak@64: values = { flickerstreak@64: default = L["by default"], flickerstreak@64: any = L["when ANY of these"], flickerstreak@64: all = L["when ALL of these"], flickerstreak@68: custom = L["via custom rule"], flickerstreak@68: keybind = L["via keybinding"], flickerstreak@64: }, flickerstreak@64: set = function( info, value ) flickerstreak@68: setrule("type", value) flickerstreak@64: fixall() flickerstreak@64: update() flickerstreak@64: end, flickerstreak@64: get = function( info ) flickerstreak@68: return getrule("type") flickerstreak@64: end, flickerstreak@64: }, flickerstreak@64: clear = { flickerstreak@68: name = L["Clear All"], flickerstreak@68: order = 3, flickerstreak@64: type = "execute", flickerstreak@68: hidden = function() flickerstreak@68: local t = getrule("type") flickerstreak@68: return t ~= "any" and t ~= "all" flickerstreak@68: end, flickerstreak@68: disabled = function() flickerstreak@68: local t = getrule("type") flickerstreak@68: return t ~= "any" and t ~= "all" flickerstreak@68: end, flickerstreak@64: func = function() flickerstreak@68: local type = getrule("type") flickerstreak@64: if type == "custom" then flickerstreak@68: setrule("custom","") flickerstreak@64: elseif type == "any" or type == "all" then flickerstreak@68: setrule("values", {}) flickerstreak@64: end flickerstreak@64: update() flickerstreak@64: end, flickerstreak@64: }, flickerstreak@64: inputs = { flickerstreak@68: name = L["Conditions"], flickerstreak@68: order = 4, flickerstreak@64: type = "multiselect", flickerstreak@64: hidden = function() flickerstreak@68: local t = getrule("type") flickerstreak@68: return t ~= "any" and t ~= "all" flickerstreak@64: end, flickerstreak@64: disabled = function() flickerstreak@68: local t = getrule("type") flickerstreak@68: return t ~= "any" and t ~= "all" flickerstreak@64: end, flickerstreak@64: values = ruleSelect, flickerstreak@64: set = function(info, key, value ) flickerstreak@68: setrule(ruleMap[key], value or nil, "values") flickerstreak@64: if value then flickerstreak@64: fixall(ruleMap[key]) flickerstreak@64: end flickerstreak@64: update() flickerstreak@64: end, flickerstreak@64: get = function(info, key) flickerstreak@68: return getrule("values", ruleMap[key]) or false flickerstreak@64: end, flickerstreak@64: }, flickerstreak@64: custom = { flickerstreak@68: name = L["Custom Rule"], flickerstreak@68: order = 5, flickerstreak@64: type = "input", flickerstreak@64: multiline = true, flickerstreak@64: hidden = function() flickerstreak@68: return getrule("type") ~= "custom" flickerstreak@64: end, flickerstreak@64: disabled = function() flickerstreak@68: return getrule("type") ~= "custom" flickerstreak@64: end, flickerstreak@64: desc = L["Syntax like macro rules: see preset rules for examples"], flickerstreak@64: set = function(info, value) flickerstreak@68: setrule("custom",value) flickerstreak@64: update() flickerstreak@64: end, flickerstreak@64: get = function(info) flickerstreak@68: return getrule("custom") or "" flickerstreak@64: end, flickerstreak@64: validate = function (info, rule) flickerstreak@64: local s = rule:gsub("%s","") -- remove all spaces flickerstreak@64: -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler flickerstreak@64: repeat flickerstreak@64: if s == "" then flickerstreak@64: return true flickerstreak@64: end flickerstreak@64: local c, r = s:match("(%b[])(.*)") flickerstreak@64: if c == nil and s and #s > 0 then flickerstreak@64: return L["Invalid custom rule '%s': each clause must appear within [brackets]"]:format(rule) flickerstreak@64: end flickerstreak@64: s = r flickerstreak@64: until c == nil flickerstreak@64: return true flickerstreak@64: end, flickerstreak@68: }, flickerstreak@68: keybind = { flickerstreak@68: name = L["Keybinding"], flickerstreak@68: order = 6, flickerstreak@68: inline = true, flickerstreak@68: hidden = function() return getrule("type") ~= "keybind" end, flickerstreak@68: disabled = function() return getrule("type") ~= "keybind" end, flickerstreak@68: type = "group", flickerstreak@68: args = { flickerstreak@68: desc = { flickerstreak@68: 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."], flickerstreak@68: order = 1, flickerstreak@68: type = "description", flickerstreak@68: }, flickerstreak@68: keybind = { flickerstreak@68: name = L["State Hotkey"], flickerstreak@68: desc = L["Define an override toggle keybind"], flickerstreak@68: order = 2, flickerstreak@68: type = "keybinding", flickerstreak@68: set = function(info, value) flickerstreak@68: setrule("keybind",value) flickerstreak@68: update() flickerstreak@68: end, flickerstreak@68: get = function() return getrule("keybind") end, flickerstreak@68: }, flickerstreak@68: default = { flickerstreak@68: name = L["Toggle Off State"], flickerstreak@68: desc = L["Select a state to return to when the keybind override is toggled off"], flickerstreak@68: order = 3, flickerstreak@68: type = "select", flickerstreak@68: values = function() flickerstreak@68: local t = { } flickerstreak@68: for k in pairs(states) do flickerstreak@68: if k ~= opts.name then flickerstreak@68: t[k] = k flickerstreak@68: end flickerstreak@68: end flickerstreak@68: return t flickerstreak@68: end, flickerstreak@68: set = function(info, value) flickerstreak@68: setrule("keybindreturn",value) flickerstreak@68: update() flickerstreak@68: end, flickerstreak@68: get = function() return getrule("keybindreturn") end, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@68: }, flickerstreak@62: } flickerstreak@62: return opts flickerstreak@25: end flickerstreak@62: flickerstreak@64: flickerstreak@62: CreateBarOptions = function(bar) flickerstreak@62: local private = { } flickerstreak@62: local options = { flickerstreak@62: type = "group", flickerstreak@62: name = L["Dynamic State"], flickerstreak@64: childGroups = "tree", flickerstreak@62: disabled = InCombatLockdown, flickerstreak@62: args = { flickerstreak@64: __desc__ = { flickerstreak@68: name = L["States are evaluated in the order they are listed"], flickerstreak@68: order = 1, flickerstreak@64: type = "description", flickerstreak@64: }, flickerstreak@64: __new__ = { flickerstreak@64: name = L["New State..."], flickerstreak@64: order = 2, flickerstreak@68: type = "group", flickerstreak@62: args = { flickerstreak@64: name = { flickerstreak@64: name = L["State Name"], flickerstreak@64: desc = L["Set a name for the new state"], flickerstreak@68: order = 1, flickerstreak@68: type = "input", flickerstreak@64: get = function() return private.newstatename or "" end, flickerstreak@64: set = function(info,value) private.newstatename = value end, flickerstreak@64: pattern = "^%w*$", flickerstreak@64: usage = L["State names must be alphanumeric without spaces"], flickerstreak@64: }, flickerstreak@64: create = { flickerstreak@68: name = L["Create State"], flickerstreak@68: order = 2, flickerstreak@64: type = "execute", flickerstreak@64: func = function () flickerstreak@64: local name = private.newstatename flickerstreak@68: if states[name] then flickerstreak@68: ReAction:UserError(L["State named '%s' already exists"]:format(name)) flickerstreak@68: else flickerstreak@68: -- TODO: select default state options and pass as final argument flickerstreak@68: states[name] = { } flickerstreak@68: optionMap[bar].args[name] = CreateStateOptions(bar,name) flickerstreak@68: private.newstatename = "" flickerstreak@68: end flickerstreak@64: end, flickerstreak@64: disabled = function() flickerstreak@64: local name = private.newstatename or "" flickerstreak@64: return #name == 0 or name:find("%W") flickerstreak@64: end, flickerstreak@62: } flickerstreak@62: } flickerstreak@64: } flickerstreak@62: } flickerstreak@62: } flickerstreak@62: local states = tfetch(module.db.profile.bars, bar:GetName(), "states") flickerstreak@62: if states then flickerstreak@62: for name, config in pairs(states) do flickerstreak@64: options.args[name] = CreateStateOptions(bar,name) flickerstreak@62: end flickerstreak@62: end flickerstreak@64: optionMap[bar] = options flickerstreak@62: return options flickerstreak@62: end flickerstreak@25: end flickerstreak@25: flickerstreak@62: function module:GetBarOptions(bar) flickerstreak@64: return CreateBarOptions(bar) flickerstreak@25: end