Mercurial > wow > reaction
diff State.lua @ 90:7cabc8ac6c16
Updates for wow 3.0
- TOC update
- updated changed APIs/frame names
- rewrote state code per new SecureHandlers API
- cleaned up Bar, ActionButton code
- removed AceLibrary/Dewdrop, menu from bar right-click
- fixed various small bugs
Updated WowAce external locations
Updated README.html
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Wed, 15 Oct 2008 16:29:41 +0000 |
parents | 1ad208c25618 |
children | c2504a8b996c |
line wrap: on
line diff
--- a/State.lua Mon Oct 13 23:32:33 2008 +0000 +++ b/State.lua Wed Oct 15 16:29:41 2008 +0000 @@ -7,8 +7,9 @@ local ReAction = ReAction local L = ReAction.L local _G = _G +local format = string.format local InCombatLockdown = InCombatLockdown -local format = string.format +local RegisterStateDriver = RegisterStateDriver ReAction:UpdateRevision("$Revision$") @@ -53,10 +54,205 @@ end -local InitRules, ApplyStates, SetProperty, GetProperty, RegisterProperty +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 + 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 + ]], + + --keybindState TODO: broken + + -- the anchoring is handled in a special handler + anchorEnable = true, + --anchorFrame = true, TODO: broken + anchorPoint = true, + anchorRelPoint = true, + anchorX = true, + anchorY = true, + enableScale = true, + scale = true, + } + + + -- + -- 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 ) + [[ + print("received state",newstate,"on bar",self:GetName()) + set_state = newstate or set_state + + local oldState = state + state = state_override or set_state or state + + for i = 1, #propfuncs do + print("running state func",propfuncs[i]) + control:RunAttribute("func-"..propfuncs[i]) + end + + if anchorEnable and anchorEnable[state] ~= anchorstate then + anchorstate = anchorEnable[state] + control:RunAttribute("func-doanchor") + end + + control:ChildUpdate() + ]] + + local anchorHandler = + -- function func-doanchor( self ) + [[ + -- TODO + if anchorstate then + -- TODO: get anchor data from state tables + else + -- TODO: get anchor data from defaults + end + ]] + + local onClickHandler = + -- function OnClick( self, button, down ) + [[ + if state_override == button then + state_override = nil -- toggle + else + state_override = button + end + ]] .. onStateHandler + + local weak = { __mode = "k" } + local statedrivers = setmetatable( { }, weak ) + local keybinds = setmetatable( { }, weak ) + + -- 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") + + if frame then + local f = bar:GetFrame() + f:SetFrameRef("defaultAnchor", f) + f:Execute( + [[ + defaultAnchor.frame = self:GetAttribute("frameref-defaultAnchor") + ]]) + end + end + + function RefreshState( bar ) + SetDefaultAnchor(bar) + bar:GetFrame():Execute([[control:RunAttribute("reaction-refresh")]]) + end + + function SetStateDriver( bar, rule ) + local f = bar:GetFrame() + + 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) + f:SetAttribute("func-doanchor", anchorHandler) + + 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 + -- As far as I can tell the macro clauses are NOT locale-specific. local ruleformats = { stealth = "stealth", @@ -113,146 +309,10 @@ ruleformats.moonkin = format("form:%d",moonkin) end - - -- state property functions - local ofskeys = { - anchorPoint = "point", - anchorRelPoint = "relpoint", - anchorX = "x", - anchorY = "y" - } - - local barofsidx = { - anchorPoint = 1, - anchorRelPoint = 3, - anchorX = 4, - anchorY = 5 - } - - local function UpdatePartialAnchor(bar, states, ckey) - local map = { } - local bc = bar.config - for state, c in pairs(states) do - if c.enableAnchor then - map[state] = c[ckey] - end - end - local ofskey = ofskeys[ckey] - local default = select(barofsidx[ckey], bar:GetAnchor()) - bar:SetStateAttribute(format("headofs%s",ofskeys[ckey]), map, default) - end - - -- the table key name for each function maps to the name of the config element - local propertyFuncs = { - hide = function( bar, states ) - local hs = { } - for state, config in pairs(states) do - if config.hide then - table.insert(hs, state) - end - end - bar:GetButtonFrame():SetAttribute("hidestates", table.concat(hs,",")) - end, - - keybindstate = function( bar, states ) - local map = { } - for state, config in pairs(states) do - local kbset = config.keybindstate and state - map[state] = kbset - for button in bar:IterateButtons() do - -- TODO: inform children they should maintain multiple binding sets - -- ?? button:UpdateBindingSet(kbset) - end - end - bar:SetStateAttribute("statebindings", map, true) -- apply to button frame, bindings only work for direct children - end, - - enableAnchor = function( bar, states ) - for ckey in pairs(ofskeys) do - UpdatePartialAnchor(bar, states, ckey) - end - end, - - enableScale = function( bar, states ) - local map = { } - for state, c in pairs(states) do - if c.enableScale then - map[state] = c.scale - end - end - bar:SetStateAttribute("headscale", map, 1.0) - end, - } - - -- generate some table entries - propertyFuncs.scale = propertyFuncs.enableScale - for ckey in pairs(ofskeys) do - propertyFuncs[ckey] = function( bar, states ) - UpdatePartialAnchor(bar, states, ckey) - 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 states = tbuild(module.db.profile.bars, bar:GetName(), "states") - tbuild(states, state)[propname] = value - local f = propertyFuncs[propname] - if f then - f(bar, states) - end - end - - function RegisterProperty( propname, f ) - propertyFuncs[propname] = f - for bar in ReAction:IterateBars() do - local states = tfetch(module.db.profile.bars, bar:GetName(), "states") - if states then - f(bar,states) - end - end - end - - - - -- - -- Build a state-transition spec string and statemap to be passed to - -- Bar:SetStateDriver(). - -- - -- The statemap building is complex: keybound states override all - -- other transitions, so must remain in their current state, but must - -- also remember other transitions that happen while they're stuck there - -- so that when the binding is toggled off it can return to the proper state - -- - local function BuildStateMap(states) + local function BuildRule(states) local rules = { } - local statemap = { } - local keybinds = { } local default - -- first grab all the keybind override states - -- and construct an override template - local override - do - local overrides = { } - for name, state in pairs(states) do - local type = tfetch(state, "rule", "type") - if type == "keybind" then - -- use the state-stack to remember the current transition - -- use $s as a marker for a later call to gsub() - table.insert(overrides, format("%s:$s set() %s", name, name)) - end - end - if #overrides > 0 then - table.insert(overrides, "") -- for a trailing ';' - end - override = table.concat(overrides, ";") or "" - end - - -- now iterate the rules in order for idx, state in ipairs(fieldsort(states, "rule", "order")) do local c = states[state].rule local type = c.type @@ -284,43 +344,83 @@ end end end - - -- use a different virtual button for the actual keybind transition, - -- to implement a toggle. You have to clear it regardless of the type - -- (which is usually a no-op) to unbind state transitions when switching - -- transition types. - local bindbutton = format("%s_binding",state) - if type == "keybind" then - keybinds[bindbutton] = c.keybind or false - statemap[bindbutton] = format("%s:pop();*:set(%s)", state, state) - else - keybinds[bindbutton] = false - end - - -- construct the statemap. gsub() the state name into the override template. - statemap[state] = format("%s%s", override:gsub("%$s",state), state) end -- make sure that the default, if any, is last if default then table.insert(rules, default) end - return table.concat(rules,";"), statemap, keybinds + 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 + print("registered property",propname) + 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 - local rule, statemap, keybinds = BuildStateMap(states) - bar:SetStateDriver("reaction", rule, statemap) - for state, key in pairs(keybinds) do - bar:SetAttributeBinding(state, key, "state-reaction", state) + for propname in pairs(properties) do + for name, s in pairs(states) do + SetHandlerData(bar, propname, s[propname], name) + end end - for k, f in pairs(propertyFuncs) do - f(bar, states) - end + BuildKeybinds(bar, states) + SetStateDriver(bar, BuildRule(states)) + RefreshState(bar) end end + function CleanupStates( bar ) + SetStateDriver(bar, nil) + end + + function ShowAll( bar, show ) + SetHandlerData(bar, "showAll", show) + RefreshState(bar) + end end @@ -342,6 +442,7 @@ 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") @@ -351,7 +452,7 @@ function module:PLAYER_AURAS_CHANGED() self:UnregisterEvent("PLAYER_AURAS_CHANGED") -- on login the number of stances is 0 until this event fires during the init sequence. - -- however if you reload just the UI the number of stances is correct immediately + -- however if you just reload the UI the number of stances is correct immediately -- and this event won't fire until you gain/lose buffs/debuffs, at which point you might -- be in combat. if not InCombatLockdown() then @@ -369,6 +470,10 @@ end end +function module:OnDestroyBar(event, bar, name) + CleanupStates(bar) +end + function module:OnEraseBar(event, bar, name) self.db.profile.bars[name] = nil end @@ -379,7 +484,11 @@ end function module:OnConfigModeChanged(event, mode) - -- nothing to do (yet) + for name, bar in ReAction:IterateBars() do + if self.db.profile.bars[name] then + ShowAll(bar, mode) + end + end end @@ -505,30 +614,40 @@ set = "SetProp", get = "GetProp", }, - keybindstate = { + --[[ 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 = { - enableAnchor = { + anchorEnable = { name = L["Set New Position"], order = 1, type = "toggle", set = "SetProp", get = "GetProp", }, + --[[ TODO: broken + anchorFrame = { + name = L["Anchor Frame"], + order = 2, + type = "select", + values = "GetAnchorFrames", + set = ??? + get = ??? + }, ]] anchorPoint = { name = L["Point"], - order = 2, + order = 3, type = "select", values = pointTable, set = "SetAnchorPointProp", @@ -538,7 +657,7 @@ }, anchorRelPoint = { name = L["Relative Point"], - order = 3, + order = 4, type = "select", values = pointTable, set = "SetAnchorPointProp", @@ -548,7 +667,7 @@ }, anchorX = { name = L["X Offset"], - order = 4, + order = 5, type = "range", min = -100, max = 100, @@ -560,7 +679,7 @@ }, anchorY = { name = L["Y Offset"], - order = 5, + order = 6, type = "range", min = -100, max = 100, @@ -680,10 +799,35 @@ }, } - local StateHandler = { } + 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 }, { __index = StateHandler }) + local self = setmetatable( + { + bar = bar + }, + proto ) function self:GetName() return opts.name @@ -700,10 +844,9 @@ -- 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) - tbuild(self.states, opts.name) - - opts.order = self:GetRule("order") + opts.order = self:GetRuleField("order") if opts.order == nil then -- add after the highest opts.order = 100 @@ -713,7 +856,7 @@ opts.order = x + 1 end end - self:SetRule("order",opts.order) + self:SetRuleField("order",opts.order) end return self @@ -721,12 +864,12 @@ -- helper methods - function StateHandler:SetRule( key, value, ... ) - tbuild(self.states, self:GetName(), "rule", ...)[key] = value + function StateHandler:SetRuleField( key, value, ... ) + tbuild(self.state, "rule", ...)[key] = value end - function StateHandler:GetRule( ... ) - return tfetch(self.states, self:GetName(), "rule", ...) + function StateHandler:GetRuleField( ... ) + return tfetch(self.state, "rule", ...) end function StateHandler:FixAll( setkey ) @@ -735,7 +878,7 @@ -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, -- it will be retained. local notified = false - if self:GetRule("type") == "all" then + if self:GetRuleField("type") == "all" then for _, c in ipairs(rules) do local rule, hidden, fields = unpack(c) local once = false @@ -748,9 +891,9 @@ end for idx, field in ipairs(fields) do local key = next(field) - if self:GetRule("values",key) then + if self:GetRuleField("values",key) then if once and key ~= setkey then - self:SetRule(key,false,"values") + 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 @@ -858,37 +1001,37 @@ end function StateHandler:SetType(info, value) - self:SetRule("type", value) + self:SetRuleField("type", value) self:FixAll() ApplyStates(self.bar) end function StateHandler:GetType() - return self:GetRule("type") + return self:GetRuleField("type") end function StateHandler:GetClearAllDisabled() - local t = self:GetRule("type") + local t = self:GetRuleField("type") return not( t == "any" or t == "all" or t == "custom") end function StateHandler:ClearAllConditions() - local t = self:GetRule("type") + local t = self:GetRuleField("type") if t == "custom" then - self:SetRule("custom","") + self:SetRuleField("custom","") elseif t == "any" or t == "all" then - self:SetRule("values", {}) + self:SetRuleField("values", {}) end ApplyStates(self.bar) end function StateHandler:GetConditionsDisabled() - local t = self:GetRule("type") + local t = self:GetRuleField("type") return not( t == "any" or t == "all") end function StateHandler:SetCondition(info, key, value) - self:SetRule(ruleMap[key], value or nil, "values") + self:SetRuleField(ruleMap[key], value or nil, "values") if value then self:FixAll(ruleMap[key]) end @@ -896,20 +1039,20 @@ end function StateHandler:GetCondition(info, key) - return self:GetRule("values", ruleMap[key]) or false + return self:GetRuleField("values", ruleMap[key]) or false end function StateHandler:GetCustomDisabled() - return self:GetRule("type") ~= "custom" + return self:GetRuleField("type") ~= "custom" end function StateHandler:SetCustomRule(info, value) - self:SetRule("custom",value) + self:SetRuleField("custom",value) ApplyStates(self.bar) end function StateHandler:GetCustomRule() - return self:GetRule("custom") or "" + return self:GetRuleField("custom") or "" end function StateHandler:ValidateCustomRule(info, value) @@ -929,18 +1072,18 @@ end function StateHandler:GetKeybindDisabled() - return self:GetRule("type") ~= "keybind" + return self:GetRuleField("type") ~= "keybind" end function StateHandler:GetKeybind() - return self:GetRule("keybind") + return self:GetRuleField("keybind") end function StateHandler:SetKeybind(info, value) if value and #value == 0 then value = nil end - self:SetRule("keybind",value) + self:SetRuleField("keybind",value) ApplyStates(self.bar) end @@ -957,17 +1100,6 @@ return opts end - - function RegisterPropertyOptions( field, options, handler ) - stateOptions.properties.plugins[field] = options - if handler then - for k,v in pairs(handler) do - StateHandler[k] = v - end - end - end - - function module:GetBarOptions(bar) local private = { } local states = tbuild(module.db.profile.bars, bar:GetName(), "states") @@ -1033,23 +1165,23 @@ -- Module API -- --- Pass in a property field-name, an implementation function, a static options table, and an +-- Pass in a property field-name, an implementation secure snippet, a static options table, and an -- optional options handler method-table -- --- propertyImplFunc prototype: --- propertyImplFunc( bar, stateTable ) --- where stateTable is a { ["statename"] = { state config } } 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 metatable. +-- 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, propertyImplFunc, options, optHandler ) - RegisterProperty(field, propertyImplFunc) +function module:RegisterStateProperty( field, snippetHandler, options, optHandler ) + RegisterProperty(field, snippetHandler) RegisterPropertyOptions(field, options, optHandler) end +function module:UnregisterStateProperty( field ) + UnregisterProperty(field) + UnregisterPropertyOptions(field) +end