Mercurial > wow > reaction
view modules/State.lua @ 145:42cade25d40d
Fixed display bugs with vehicle support
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Mon, 27 Apr 2009 18:10:04 +0000 |
parents | d186e041ca14 |
children | 806a61b331a0 |
line wrap: on
line source
--[[ ReAction bar state driver interface --]] -- local imports local ReAction = ReAction local L = ReAction.L local _G = _G local format = string.format local InCombatLockdown = InCombatLockdown local RegisterStateDriver = RegisterStateDriver ReAction:UpdateRevision("$Revision$") -- module declaration local moduleID = "State" local module = ReAction:NewModule( moduleID, "AceEvent-3.0" ) -- Utility -- -- traverse a table tree by key list and fetch the result or first nil local function tfetch(t, ...) for i = 1, select('#', ...) do t = t and t[select(i, ...)] end return t end -- traverse a table tree by key list and build tree as necessary local function tbuild(t, ...) for i = 1, select('#', ...) do local key = select(i, ...) if not t[key] then t[key] = { } end t = t[key] end return t end -- return a new array of keys of table 't', sorted by comparing -- sub-fields (obtained via tfetch) of the table values local function fieldsort( t, ... ) local r = { } for k in pairs(t) do table.insert(r,k) end local path = { ... } table.sort(r, function(lhs, rhs) local olhs = tfetch(t[lhs], unpack(path)) or 0 local orhs = tfetch(t[rhs], unpack(path)) or 0 return olhs < orhs end) return r end -- set a frame-ref, if the frame is valid, or set nil to the -- corresponding attribute local function SetFrameRef(frame, name, refFrame) if refFrame then local _, explicit = refFrame:IsProtected() if not explicit then refFrame = nil end end if refFrame then frame:SetFrameRef(name,refFrame) else frame:SetAttribute("frameref-"..name,nil) end end local InitRules, ApplyStates, CleanupStates, SetProperty, GetProperty, RegisterProperty, ShowAll -- PRIVATE -- do -- the field names must match the field names of the options table, below -- the field values are secure snippets or 'true' to skip the snippet for that property. local properties = { hide = [[ local h = hide and hide[state] and not showAll if h ~= hidden then if h then self:Hide() else self:Show() end hidden = h end if showAll then control:CallMethod("UpdateHiddenLabel", hide and hide[state]) end ]], --keybindState TODO: broken anchorEnable = [[ local old_anchor = anchorstate anchorstate = (anchorEnable and anchorEnable[state]) and state if old_anchor ~= anchorstate or not set_state then if anchorstate and anchorPoint then if anchorPoint[state] then self:ClearAllPoints() local f = self:GetAttribute("frameref-anchor-"..anchorstate) if f then self:SetPoint(anchorPoint[state], f, anchorRelPoint[state], anchorX[state], anchorY[state]) end end elseif defaultAnchor and defaultAnchor.point then self:ClearAllPoints() self:SetPoint(defaultAnchor.point, defaultAnchor.frame, defaultAnchor.relPoint, defaultAnchor.x, defaultAnchor.y) end end ]], -- anchorEnable handles all the other bits anchorFrame = true, anchorPoint = true, anchorRelPoint = true, anchorX = true, anchorY = true, enableScale = [[ local old_scale = scalestate scalestate = (enableScale and enableScale[state]) and state if old_scale ~= scalestate or not set_state then if scalestate and scale then if scale[state] then self:SetScale(scale[state]) end else self:SetScale(1.0) end end ]], -- enableScale handles scale scale = true, enableAlpha = [[ local old_alpha = alphastate alphastate = (enableAlpha and enableAlpha[state]) and state if old_alpha ~= alphastate or not set_state then control:CallMethod("UpdateAlpha", alphastate and alpha[state] or defaultAlpha) end ]], -- enableAlpha handles alpha alpha = true, } local weak = { __mode = "k" } local statedrivers = setmetatable( { }, weak ) local keybinds = setmetatable( { }, weak ) -- -- Secure Handler Snippets -- local SetHandlerData, SetStateDriver, SetStateKeybind, RefreshState do local stateHandler_propInit = [[ propfuncs = table.new() local proplist = self:GetAttribute("prop-func-list") for s in string.gmatch(proplist, "(%w+)") do table.insert(propfuncs, s) end ]] local onStateHandler = -- function _onstate-reaction( self, stateid, newstate ) [[ set_state = newstate local oldState = state state = state_override or set_state or state for i = 1, #propfuncs do control:RunAttribute("func-"..propfuncs[i]) end control:ChildUpdate() if oldState ~= state then control:CallMethod("StateRefresh", state) end ]] local onClickHandler = -- function OnClick( self, button, down ) [[ if state_override == button then state_override = nil -- toggle else state_override = button end ]] .. onStateHandler local function UpdateAlpha( frame, alpha ) if alpha then frame:SetAlpha(alpha) end end -- Construct a lua assignment as a code string and execute it within the header -- frame's sandbox. 'value' must be a string, boolean, number, or nil. If called -- with four arguments, then it treats 'varname' as an existing global table and -- sets a key-value pair. For a slight efficiency boost, pass the values in as -- attributes and fetch them as attributes from the snippet code, to leverage snippet -- caching. function SetHandlerData( bar, varname, value, key ) local f = bar:GetFrame() f:SetAttribute("data-varname",varname) f:SetAttribute("data-value", value) f:SetAttribute("data-key", key) f:Execute( [[ local name = self:GetAttribute("data-varname") local value = self:GetAttribute("data-value") local key = self:GetAttribute("data-key") if name then if key then if not _G[name] then _G[name] = table.new() end _G[name][key] = value else _G[name] = value end end ]]) end function SetDefaultAnchor( bar ) local point, frame, relPoint, x, y = bar:GetAnchor() SetHandlerData(bar, "defaultAnchor", point, "point") SetHandlerData(bar, "defaultAnchor", relPoint, "relPoint") SetHandlerData(bar, "defaultAnchor", x, "x") SetHandlerData(bar, "defaultAnchor", y, "y") SetHandlerData(bar, "defaultAlpha", bar:GetAlpha()) local f = bar:GetFrame() f.UpdateAlpha = UpdateAlpha SetFrameRef(f, "defaultAnchor", _G[frame or "UIParent"]) f:Execute( [[ defaultAnchor.frame = self:GetAttribute("frameref-defaultAnchor") ]]) end function RefreshState( bar ) SetDefaultAnchor(bar) bar:GetFrame():Execute( [[ if self:GetAttribute("reaction-refresh") then control:RunAttribute("reaction-refresh") end ]]) end function SetStateDriver( bar, rule ) local f = bar:GetFrame() if not f.UpdateHiddenLabel then function f:UpdateHiddenLabel(hide) bar:SetLabelSubtext( hide and L["Hidden"] ) end end function f:StateRefresh( state ) bar:RefreshControls() end local props = { } for p, h in pairs(properties) do if type(h) == "string" then table.insert(props,p) f:SetAttribute("func-"..p, h) end end f:SetAttribute("prop-func-list", table.concat(props," ")) f:Execute(stateHandler_propInit) f:SetAttribute("reaction-refresh", onStateHandler) if rule and #rule > 0 then f:SetAttribute( "_onstate-reaction", onStateHandler ) RegisterStateDriver(f, "reaction", rule) statedrivers[bar] = rule elseif statedrivers[bar] then UnregisterStateDriver(f, "reaction") f:SetAttribute( "_onstate-reaction", nil ) statedrivers[bar] = nil end end function SetStateKeybind( bar, key, state ) local f = bar:GetFrame() local kb = keybinds[bar] if kb == nil then if key == nil then -- nothing to do return end kb = { } keybinds[bar] = kb end -- clear the old binding, if any if kb[state] then SetOverrideBinding(f, false, kb[state], nil) end kb[state] = key if key then f:SetAttribute("_onclick", onClickHandler) SetOverrideBindingClick(f, false, key, state, nil) -- state name is the virtual mouse button end end end local playerClass = select(2, UnitClass("player")) local function ClassFilter(...) for i = 1, select('#',...) do if playerClass == select(i,...) then return false end end return true end -- As far as I can tell the macro clauses are NOT locale-specific. -- 'filter' specifies whether rules should be omitted from execution. -- 'true' indicates they should be filtered out. local ruleformats = { stealth = { format = "stealth", filter = ClassFilter("ROGUE","DRUID") }, nostealth = { format = "nostealth", filter = ClassFilter("ROGUE","DRUID") }, shadowform = { format = "form:1", filter = ClassFilter("PRIEST") }, noshadowform = { format = "noform", filter = ClassFilter("PRIEST") }, battle = { format = "stance:1", filter = ClassFilter("WARRIOR") }, defensive = { format = "stance:2", filter = ClassFilter("WARRIOR") }, berserker = { format = "stance:3", filter = ClassFilter("WARRIOR") }, caster = { format = "form:0/2/4/5/6", filter = ClassFilter("DRUID") }, bear = { format = "form:1", filter = ClassFilter("DRUID") }, cat = { format = "form:1", filter = ClassFilter("DRUID") }, tree = { format = "form:5", filter = ClassFilter("DRUID") }, moonkin = { format = "form:5", filter = ClassFilter("DRUID") }, pet = { format = "pet" }, nopet = { format = "nopet" }, harm = { format = "target=target,harm" }, help = { format = "target=target,help" }, notarget = { format = "target=target,noexists" }, focusharm = { format = "target=focus,harm" }, focushelp = { format = "target=focus,help" }, nofocus = { format = "target=focus,noexists" }, raid = { format = "group:raid" }, party = { format = "group:party" }, solo = { format = "nogroup" }, combat = { format = "combat" }, nocombat = { format = "nocombat" }, possess = { format = "bonusbar:5" }, vehicle = { format = "target=vehicle,exists,bonusbar:5" }, } -- Have to do these shenanigans instead of hardcoding the stances/forms because the -- ordering varies if the character is missing a form. For warriors this is rarely -- a problem (c'mon, who actually skips the level 10 def stance quest?) but for druids -- it can be. Some people never bother to do the aquatic form quest until well past -- when they get cat form, and stance 5/6 can be flight, tree, or moonkin depending -- on talents. function InitRules() local forms = { } -- sort by icon since it's locale-independent for i = 1, GetNumShapeshiftForms() do local icon, name, active = GetShapeshiftFormInfo(i) -- if it's the current form, the icon is wrong (Spell_Nature_WispSplode) -- so capture it from the spell info directly if active then local _1, _2 _1, _2, icon = GetSpellInfo(name) end forms[icon] = i; end -- use 9 if not found since 9 is never a valid stance/form local defensive = forms["Interface\\Icons\\Ability_Warrior_DefensiveStance"] or 9 local berserker = forms["Interface\\Icons\\Ability_Racial_Avatar"] or 9 local bear = forms["Interface\\Icons\\Ability_Racial_BearForm"] or 9 -- bear and dire bear share the same icon local aquatic = forms["Interface\\Icons\\Ability_Druid_AquaticForm"] or 9 local cat = forms["Interface\\Icons\\Ability_Druid_CatForm"] or 9 local travel = forms["Interface\\Icons\\Ability_Druid_TravelForm"] or 9 local tree = forms["Interface\\Icons\\Ability_Druid_TreeofLife"] or 9 local moonkin = forms["Interface\\Icons\\Spell_Nature_ForceOfNature"] or 9 local flight = forms["Interface\\Icons\\Ability_Druid_FlightForm"] or 9 -- flight and swift flight share the same icon ruleformats.defensive.format = format("stance:%d",defensive) ruleformats.berserker.format = format("stance:%d",berserker) ruleformats.caster.format = format("form:0/%d/%d/%d", aquatic, travel, flight) ruleformats.bear.format = format("form:%d",bear) ruleformats.cat.format = format("form:%d",cat) ruleformats.tree.format = format("form:%d",tree) ruleformats.moonkin.format = format("form:%d",moonkin) end local function BuildRule(states) local rules = { } local default for idx, state in ipairs(fieldsort(states, "rule", "order")) do local c = states[state].rule local type = c.type if type == "default" then default = default or state elseif type == "custom" then if c.custom then -- strip out all spaces from the custom rule table.insert(rules, format("%s %s", c.custom:gsub("%s",""), state)) end elseif type == "any" or type == "all" then if c.values then local clauses = { } for key, value in pairs(c.values) do if ruleformats[key] and not ruleformats[key].filter then table.insert(clauses, ruleformats[key].format) end end if #clauses > 0 then local sep = (type == "any") and "][" or "," table.insert(rules, format("[%s] %s", table.concat(clauses,sep), state)) end end end end -- make sure that the default, if any, is last if default then table.insert(rules, default) end return table.concat(rules,";") end local function BuildKeybinds( bar, states ) for name, state in pairs(states) do local type = tfetch(state, "rule", "type") if type == "keybind" then local key = tfetch(state, "rule", "keybind") SetStateKeybind(bar, key, name) else SetStateKeybind(bar, nil, name) -- this clears an existing keybind end end end function GetProperty( bar, state, propname ) return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname) end function SetProperty( bar, state, propname, value ) local s = tbuild(module.db.profile.bars, bar:GetName(), "states", state) s[propname] = value SetHandlerData(bar, propname, value, state) RefreshState(bar) end function RegisterProperty( propname, snippet ) properties[propname] = snippet or true for _, bar in ReAction:IterateBars() do local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then for name, s in pairs(states) do SetHandlerData(bar, propname, s[propname], name) end SetStateDriver(bar, BuildRule(states)) RefreshState(bar) end end end function UnregisterProperty( propname ) properties[propname] = nil for _, bar in ReAction:IterateBars() do SetHandlerData(bar, propname, nil) SetStateDriver(bar, BuildRule(states)) RefreshState(bar) end end function ApplyStates( bar ) local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then for propname in pairs(properties) do for name, s in pairs(states) do if propname == "anchorFrame" then SetFrameRef(bar:GetFrame(), "anchor-"..name, _G[s.anchorFrame]) else SetHandlerData(bar, propname, s[propname], name) end end end BuildKeybinds(bar, states) SetHandlerData(bar, "showAll", ReAction:GetConfigMode()) SetStateDriver(bar, BuildRule(states)) RefreshState(bar) end end function CleanupStates( bar ) SetStateDriver(bar, nil) end function ShowAll( bar, show ) if statedrivers[bar] then SetHandlerData(bar, "showAll", show) RefreshState(bar) end end end -- module event handlers -- function module:OnInitialize() self.db = ReAction.db:RegisterNamespace( moduleID, { profile = { bars = { }, } } ) self:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") ReAction.RegisterCallback(self, "OnDestroyBar") ReAction.RegisterCallback(self, "OnRefreshBar") ReAction.RegisterCallback(self, "OnEraseBar") ReAction.RegisterCallback(self, "OnRenameBar") ReAction.RegisterCallback(self, "OnConfigModeChanged") end function module:OnEnable() self:UPDATE_SHAPESHIFT_FORMS() -- it doesn't fire on a /reloadui end function module:UPDATE_SHAPESHIFT_FORMS() -- Re-parse the rules table according to the new form list. -- This happens both at initial login (after PLAYER_ENTERING_WORLD) -- as well as when gaining new abilities. InitRules() for name, bar in ReAction:IterateBars() do self:OnRefreshBar(nil,bar,name) end end function module:OnRefreshBar(event, bar, name) local c = self.db.profile.bars[name] if c then ApplyStates(bar) end end function module:OnDestroyBar(event, bar, name) CleanupStates(bar) end function module:OnEraseBar(event, bar, name) self.db.profile.bars[name] = nil end function module:OnRenameBar(event, bar, oldname, newname) local bars = self.db.profile.bars bars[newname], bars[oldname] = bars[oldname], nil end function module:OnConfigModeChanged(event, mode) for name, bar in ReAction:IterateBars() do if self.db.profile.bars[name] then ShowAll(bar, mode) end end end -- Options -- local CreateBarOptions, RegisterPropertyOptions do -- pre-sorted by the order they should appear in local rules = { -- rule fields { "stance", { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, { "form", { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {tree = L["Tree of Life"]}, {moonkin = L["Moonkin Form"]} } }, { "stealth", { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } }, { "shadow", { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, { "pet", { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, { "target", { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, { "focus", { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, { "possess", { {possess = L["Mind Control"]} } }, { "vehicle", { {vehicle = L["In a Vehicle"]} } }, { "group", { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, { "combat", { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, } local ruleSelect = { } local ruleMap = { } local optionMap = setmetatable({},{__mode="k"}) local pointTable = { NONE = " ", CENTER = L["Center"], LEFT = L["Left"], RIGHT = L["Right"], TOP = L["Top"], BOTTOM = L["Bottom"], TOPLEFT = L["Top Left"], TOPRIGHT = L["Top Right"], BOTTOMLEFT = L["Bottom Left"], BOTTOMRIGHT = L["Bottom Right"], } -- unpack rules table into ruleSelect and ruleMap for _, c in ipairs(rules) do local rule, fields = unpack(c) for _, field in ipairs(fields) do local key, label = next(field) table.insert(ruleSelect, label) table.insert(ruleMap, key) end end local stateOptions = { ordering = { name = L["Info"], order = 1, type = "group", args = { delete = { name = L["Delete this State"], order = -1, type = "execute", func = "DeleteState", }, rename = { name = L["Name"], order = 1, type = "input", get = "GetName", set = "SetStateName", pattern = "^%w*$", usage = L["State names must be alphanumeric without spaces"], }, ordering = { name = L["Evaluation Order"], desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"], order = 2, type = "group", inline = true, args = { up = { name = L["Up"], order = 1, type = "execute", width = "half", func = "MoveStateUp", }, down = { name = L["Down"], order = 2, type = "execute", width = "half", func = "MoveStateDown", } } } } }, properties = { name = L["Properties"], order = 2, type = "group", args = { desc = { name = L["Set the properties for the bar when in this state"], order = 1, type = "description" }, hide = { name = L["Hide Bar"], order = 90, type = "toggle", set = "SetProp", get = "GetProp", }, --[[ BROKEN keybindState = { name = L["Override Keybinds"], desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"], order = 91, type = "toggle", set = "SetProp", get = "GetProp", }, ]] position = { name = L["Position"], order = 92, type = "group", inline = true, args = { anchorEnable = { name = L["Reposition"], order = 1, type = "toggle", set = "SetProp", get = "GetProp", }, anchorFrame = { name = L["Anchor Frame"], order = 2, type = "select", values = "GetAnchorFrames", set = "SetAnchorFrame", get = "GetAnchorFrame", disabled = "GetAnchorDisabled", hidden = "GetAnchorDisabled", }, anchorPoint = { name = L["Point"], order = 3, type = "select", values = pointTable, set = "SetAnchorPointProp", get = "GetAnchorPointProp", disabled = "GetAnchorDisabled", hidden = "GetAnchorDisabled", }, anchorRelPoint = { name = L["Relative Point"], order = 4, type = "select", values = pointTable, set = "SetAnchorPointProp", get = "GetAnchorPointProp", disabled = "GetAnchorDisabled", hidden = "GetAnchorDisabled", }, anchorX = { name = L["X Offset"], order = 5, type = "range", min = -100, max = 100, step = 1, set = "SetProp", get = "GetProp", disabled = "GetAnchorDisabled", hidden = "GetAnchorDisabled", }, anchorY = { name = L["Y Offset"], order = 6, type = "range", min = -100, max = 100, step = 1, set = "SetProp", get = "GetProp", disabled = "GetAnchorDisabled", hidden = "GetAnchorDisabled", }, }, }, scale = { name = L["Scale"], order = 93, type = "group", inline = true, args = { enableScale = { name = L["Set New Scale"], order = 1, type = "toggle", set = "SetProp", get = "GetProp", }, scale = { name = L["Scale"], order = 2, type = "range", min = 0.25, max = 2.5, step = 0.05, isPercent = true, set = "SetProp", get = "GetScale", disabled = "GetScaleDisabled", hidden = "GetScaleDisabled", }, }, }, alpha = { name = L["Transparency"], order = 94, type = "group", inline = true, args = { enableAlpha = { name = L["Set Transparency"], order = 1, type = "toggle", set = "SetProp", get = "GetProp", }, alpha = { name = L["Transparency"], order = 2, type = "range", min = 0, max = 1, step = 0.01, bigStep = 0.05, isPercent = true, set = "SetProp", get = "GetAlpha", disabled = "GetAlphaDisabled", hidden = "GetAlphaDisabled", }, }, }, }, plugins = { } }, rules = { name = L["Rule"], order = 3, type = "group", args = { mode = { name = L["Select this state"], order = 2, type = "select", style = "radio", values = { default = L["by default"], any = L["when ANY of these"], all = L["when ALL of these"], custom = L["via custom rule"], keybind = L["via keybinding"], }, set = "SetType", get = "GetType", }, clear = { name = L["Clear All"], order = 3, type = "execute", hidden = "GetClearAllDisabled", disabled = "GetClearAllDisabled", func = "ClearAllConditions", }, inputs = { name = L["Conditions"], order = 4, type = "multiselect", hidden = "GetConditionsDisabled", disabled = "GetConditionsDisabled", values = ruleSelect, set = "SetCondition", get = "GetCondition", }, custom = { name = L["Custom Rule"], order = 5, type = "input", multiline = true, hidden = "GetCustomDisabled", disabled = "GetCustomDisabled", desc = L["Syntax like macro rules: see preset rules for examples"], set = "SetCustomRule", get = "GetCustomRule", validate = "ValidateCustomRule", }, keybind = { name = L["Keybinding"], order = 6, inline = true, hidden = "GetKeybindDisabled", disabled = "GetKeybindDisabled", type = "group", args = { desc = { name = L["Invoking a state keybind toggles an override of all other transition rules."], order = 1, type = "description", }, keybind = { name = L["State Hotkey"], desc = L["Define an override toggle keybind"], order = 2, type = "keybinding", set = "SetKeybind", get = "GetKeybind", }, }, }, }, }, } local handlers = { } local meta = { __index = function(self, key) for _, h in pairs(handlers) do if h[key] then return h[key] end end end, } local StateHandler = setmetatable({ }, meta) local proto = { __index = StateHandler } function RegisterPropertyOptions( field, options, handler ) stateOptions.properties.plugins[field] = options handlers[field] = handler end function UnregisterPropertyOptions( field ) stateOptions.properties.plugins[field] = nil handlers[field] = nil end function StateHandler:New( bar, opts ) local self = setmetatable( { bar = bar }, proto ) function self:GetName() return opts.name end function self:SetName(name) opts.name = name end function self:GetOrder() return opts.order end -- get reference to states table: even if the bar -- name changes the states table ref won't self.states = tbuild(module.db.profile.bars, bar:GetName(), "states") self.state = tbuild(self.states, opts.name) opts.order = self:GetRuleField("order") if opts.order == nil then -- add after the highest opts.order = 100 for _, state in pairs(self.states) do local x = tonumber(tfetch(state, "rule", "order")) if x and x >= opts.order then opts.order = x + 1 end end self:SetRuleField("order",opts.order) end return self end -- helper methods function StateHandler:SetRuleField( key, value, ... ) tbuild(self.state, "rule", ...)[key] = value end function StateHandler:GetRuleField( ... ) return tfetch(self.state, "rule", ...) end function StateHandler:FixAll( setkey ) -- if multiple selections in the same group are chosen when 'all' is selected, -- keep only one of them. If changing the mode, the first in the fields list will -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, -- it will be retained. local notified = false if self:GetRuleField("type") == "all" then for _, c in ipairs(rules) do local rule, fields = unpack(c) local once = false if setkey then for idx, field in ipairs(fields) do if next(field) == setkey then once = true end end end for idx, field in ipairs(fields) do local key = next(field) if self:GetRuleField("values",key) then if once and key ~= setkey then self:SetRuleField(key,false,"values") if not setkey and not notified then ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) notified = true end end once = true end end end end end function StateHandler:GetNeighbors() local before, after for k, v in pairs(self.states) do local o = tonumber(tfetch(v, "rule", "order")) if o and k ~= self:GetName() then local obefore = tfetch(self.states,before,"rule","order") local oafter = tfetch(self.states,after,"rule","order") if o < self:GetOrder() and (not obefore or obefore < o) then before = k end if o > self:GetOrder() and (not oafter or oafter > o) then after = k end end end return before, after end function StateHandler:SwapOrder( a, b ) -- do options table local args = optionMap[self.bar].args args[a].order, args[b].order = args[b].order, args[a].order -- do profile a = tbuild(self.states, a, "rule") b = tbuild(self.states, b, "rule") a.order, b.order = b.order, a.order end -- handler methods function StateHandler:GetProp( info ) -- gets property of the same name as the options arg return GetProperty(self.bar, self:GetName(), info[#info]) end function StateHandler:SetProp( info, value ) -- sets property of the same name as the options arg SetProperty(self.bar, self:GetName(), info[#info], value) end function StateHandler:DeleteState() if self.states[self:GetName()] then self.states[self:GetName()] = nil ApplyStates(self.bar) end optionMap[self.bar].args[self:GetName()] = nil end function StateHandler:SetStateName(info, value) -- check for existing state name if self.states[value] then ReAction:UserError(format(L["State named '%s' already exists"],value)) return end local args = optionMap[self.bar].args local name = self:GetName() self.states[value], args[value], self.states[name], args[name] = self.states[name], args[name], nil, nil self:SetName(value) ApplyStates(self.bar) ReAction:ShowEditor(self.bar, moduleID, value) end function StateHandler:MoveStateUp() local before, after = self:GetNeighbors() if before then self:SwapOrder(before, self:GetName()) ApplyStates(self.bar) end end function StateHandler:MoveStateDown() local before, after = self:GetNeighbors() if after then self:SwapOrder(self:GetName(), after) ApplyStates(self.bar) end end function StateHandler:GetAnchorDisabled() return not GetProperty(self.bar, self:GetName(), "anchorEnable") end function StateHandler:GetAnchorFrames(info) self._anchorframes = self._anchorframes or { } table.wipe(self._anchorframes) table.insert(self._anchorframes, "UIParent") for name, bar in ReAction:IterateBars() do table.insert(self._anchorframes, bar:GetFrame():GetName()) end return self._anchorframes end function StateHandler:GetAnchorFrame(info) local value = self:GetProp(info) for k,v in pairs(self._anchorframes) do if v == value then return k end end end function StateHandler:SetAnchorFrame(info, value) local f = _G[self._anchorframes[value]] if f then SetFrameRef(self.bar:GetFrame(), "anchor-"..self:GetName(), f) self:SetProp(info, f:GetName()) end end function StateHandler:SetAnchorPointProp(info, value) self:SetProp(info, value ~= "NONE" and value or nil) end function StateHandler:GetAnchorPointProp(info) return self:GetProp(info) or "NONE" end function StateHandler:GetScale(info) return self:GetProp(info) or 1.0 end function StateHandler:GetScaleDisabled() return not GetProperty(self.bar, self:GetName(), "enableScale") end function StateHandler:GetAlpha(info) return self:GetProp(info) or 1.0 end function StateHandler:GetAlphaDisabled() return not GetProperty(self.bar, self:GetName(), "enableAlpha") end function StateHandler:SetType(info, value) self:SetRuleField("type", value) self:FixAll() ApplyStates(self.bar) end function StateHandler:GetType() return self:GetRuleField("type") end function StateHandler:GetClearAllDisabled() local t = self:GetRuleField("type") return not( t == "any" or t == "all" or t == "custom") end function StateHandler:ClearAllConditions() local t = self:GetRuleField("type") if t == "custom" then self:SetRuleField("custom","") elseif t == "any" or t == "all" then self:SetRuleField("values", {}) end ApplyStates(self.bar) end function StateHandler:GetConditionsDisabled() local t = self:GetRuleField("type") return not( t == "any" or t == "all") end function StateHandler:SetCondition(info, key, value) self:SetRuleField(ruleMap[key], value or nil, "values") if value then self:FixAll(ruleMap[key]) end ApplyStates(self.bar) end function StateHandler:GetCondition(info, key) return self:GetRuleField("values", ruleMap[key]) or false end function StateHandler:GetCustomDisabled() return self:GetRuleField("type") ~= "custom" end function StateHandler:SetCustomRule(info, value) self:SetRuleField("custom",value) ApplyStates(self.bar) end function StateHandler:GetCustomRule() return self:GetRuleField("custom") or "" end function StateHandler:ValidateCustomRule(info, value) local s = value:gsub("%s","") -- remove all spaces -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler repeat if s == "" then return true end local c, r = s:match("(%b[])(.*)") if c == nil and s and #s > 0 then return format(L["Invalid custom rule '%s': each clause must appear within [brackets]"],value or "") end s = r until c == nil return true end function StateHandler:GetKeybindDisabled() return self:GetRuleField("type") ~= "keybind" end function StateHandler:GetKeybind() return self:GetRuleField("keybind") end function StateHandler:SetKeybind(info, value) if value and #value == 0 then value = nil end self:SetRuleField("keybind",value) ApplyStates(self.bar) end local function CreateStateOptions(bar, name) local opts = { type = "group", name = name, childGroups = "tab", args = stateOptions } opts.handler = StateHandler:New(bar,opts) return opts end function module:GetBarOptions(bar) local private = { } local states = tbuild(module.db.profile.bars, bar:GetName(), "states") local options = { name = L["Dynamic State"], type = "group", order = -1, childGroups = "tree", disabled = InCombatLockdown, args = { __desc__ = { name = L["States are evaluated in the order they are listed"], order = 1, type = "description", }, __new__ = { name = L["New State..."], order = 2, type = "group", args = { name = { name = L["State Name"], desc = L["Set a name for the new state"], order = 1, type = "input", get = function() return private.newstatename or "" end, set = function(info,value) private.newstatename = value end, pattern = "^%w*$", usage = L["State names must be alphanumeric without spaces"], }, create = { name = L["Create State"], order = 2, type = "execute", func = function () local name = private.newstatename if states[name] then ReAction:UserError(format(L["State named '%s' already exists"],name)) else -- TODO: select default state options and pass as final argument states[name] = { } optionMap[bar].args[name] = CreateStateOptions(bar,name) ReAction:ShowEditor(bar, moduleID, name) private.newstatename = "" end end, disabled = function() local name = private.newstatename or "" return #name == 0 or name:find("%W") end, } } } } } for name, config in pairs(states) do options.args[name] = CreateStateOptions(bar,name) end optionMap[bar] = options return options end end -- Module API -- -- Pass in a property field-name, an implementation secure snippet, a static options table, and an -- optional options handler method-table -- -- The options table is static, i.e. not bar-specific and should only reference handler method -- strings (either existing ones or those added via optHandler). The existing options are ordered -- 90-99. Order #1 is reserved for the heading. -- -- The contents of optHandler, if provided, will be added to the existing StateHandler options metatable. -- See above, for existing API. In particular see the properties set up in the New method: self.bar, -- self.states, and self:GetName(), and the generic property handlers self:GetProp() and self:SetProp(). -- function module:RegisterStateProperty( field, snippetHandler, options, optHandler ) RegisterProperty(field, snippetHandler) RegisterPropertyOptions(field, options, optHandler) end function module:UnregisterStateProperty( field ) UnregisterProperty(field) UnregisterPropertyOptions(field) end -- Export methods to Bar class -- function ReAction.Bar:GetState() local env = GetManagedEnvironment(self:GetFrame()) return env and env.state end ReAction.Bar.GetStateProperty = GetProperty ReAction.Bar.SetStateProperty = SetProperty