# HG changeset patch # User Flick # Date 1212533854 0 # Node ID fcb5dad031f9b672785870b671e93a232f172731 # Parent 84721edaa749d60691db56ca139fefc42599eb5a State transitions now working. Keybind toggle states, stance switching, etc. Hide bar is the only working state property, but the config is there for page #, re-anchoring, re-scaling, and keybind override. diff -r 84721edaa749 -r fcb5dad031f9 Bar.lua --- a/Bar.lua Wed May 28 17:59:44 2008 +0000 +++ b/Bar.lua Tue Jun 03 22:57:34 2008 +0000 @@ -21,13 +21,14 @@ local function Constructor( self, name, config ) self.name, self.config = name, config + self.buttons = setmetatable({},{__mode="k"}) if type(config) ~= "table" then error("ReAction.Bar: config table required") end local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent - local f = CreateFrame("Frame",nil,parent,"SecureStateDriverTemplate") + local f = CreateFrame("Frame",nil,parent,"SecureStateHeaderTemplate") f:SetFrameStrata("MEDIUM") config.width = config.width or 480 config.height = config.height or 40 @@ -37,9 +38,9 @@ ReAction.RegisterCallback(self, "OnConfigModeChanged") self.frame = f - self:RefreshLayout() self:ApplyAnchor() f:Show() + self:RefreshLayout() end function Bar:Destroy() @@ -165,13 +166,74 @@ f:ClearAllPoints() f:SetPoint("TOPLEFT",x/scale,-y/scale) f:SetScale(scale) + self.buttons[f] = true end +function Bar:GetNumPages() + return self.config.nPages or 1 +end +function Bar:SetHideStates(s) + for f in pairs(self.buttons) do + if f:GetParent() == self.frame then + f:SetAttribute("hidestates",s) + end + end + SecureStateHeader_Refresh(self.frame) +end +function Bar:SetStateKeybind(keybind, state, defaultstate) + -- use a tiny offscreen button to get around making the bar itself a clickable button + local f = self.statebuttonframe + local off = ("%s_off"):format(state) + if keybind then + if not f then + f = CreateFrame("Button",self:GetName().."_statebutton",UIParent,"SecureActionButtonTemplate") + f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT") + f:SetWidth(1) + f:SetHeight(1) + f:SetAttribute("attribute-name", "state") + f:SetAttribute("attribute-frame",self.frame) + f:SetAttribute("stateheader",self.frame) + f:Show() + self.statebuttonframe = f + end + -- map two virtual buttons to toggle between the state and the default + f:SetAttribute(("statebutton-%s"):format(state),("%s:%s;%s"):format(state,off,state)) + f:SetAttribute(("type-%s"):format(state),"attribute") + f:SetAttribute(("type-%s"):format(off),"attribute") + f:SetAttribute(("attribute-value-%s"):format(state), state) + f:SetAttribute(("attribute-value-%s"):format(off), defaultstate) + SetBindingClick(keybind, f:GetName(), state) + elseif f then + f:SetAttribute(("type-%s"):format(state),ATTRIBUTE_NOOP) + f:SetAttribute(("type-%s"):format(off),ATTRIBUTE_NOOP) + end +end +function Bar:SetStatePageMap(state, map) -- map is a { ["statename"] = pagenumber } table + local f = self.frame + local tmp = { } + for s, p in pairs(map) do + table.insert(tmp, ("%s:%d"):format(s,p)) + end + local spec = table.concat(tmp,";") + f:SetAttribute("statebutton",spec) +end - +function Bar:SetStateKeybindOverrideMap(states) -- 'states' is an array of state-names that should have keybind overrides enabled + local f = self.frame + for i = 1, #states do + local s = states[i] + states[i] = ("%s:%s"):format(s,s) + end + table.insert(states,"_default") + f:SetAttribute("statebindings",table.concat(states,";")) + for b in pairs(self.buttons) do + -- TODO: signal child frames that they should + -- maintain multiple bindings + end +end -- -- Bar config overlay diff -r 84721edaa749 -r fcb5dad031f9 locale/enUS.lua --- a/locale/enUS.lua Wed May 28 17:59:44 2008 +0000 +++ b/locale/enUS.lua Tue Jun 03 22:57:34 2008 +0000 @@ -127,23 +127,45 @@ "In Combat", "Out of Combat", "Warning: one or more incompatible rules were turned off", -"Properties", +"Info", "Delete this State", "Name", "Evaluation Order", "State transitions are evaluated in the order listed:\nMove a state up or down to change the order", "Up", "Down", -"Rules", +"Properties", +"Set the properties for the bar when in this state", +"Hide Bar", +"Show Page #", +"Override Keybinds", +"Set this state to maintain its own set of keybinds which override the defaults when active", +"Position", +"Set New Position", +"Point", +"Relative Point", +"X Offset", +"Y Offset", +"Scale", +"Set New Scale", +"Selection Rule", "Select this state", "by default", "when ANY of these", "when ALL of these", "via custom rule", +"via keybinding", "Clear All", +"Conditions", "Custom Rule", "Syntax like macro rules: see preset rules for examples", "Invalid custom rule '%s': each clause must appear within [brackets]", +"Keybinding", +"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.", +"State Hotkey", +"Define an override toggle keybind", +"Toggle Off State", +"Select a state to return to when the keybind override is toggled off", "Dynamic State", "States are evaluated in the order they are listed", "New State...", diff -r 84721edaa749 -r fcb5dad031f9 modules/ReAction_State/ReAction_State.lua --- a/modules/ReAction_State/ReAction_State.lua Wed May 28 17:59:44 2008 +0000 +++ b/modules/ReAction_State/ReAction_State.lua Tue Jun 03 22:57:34 2008 +0000 @@ -34,7 +34,8 @@ end -- PRIVATE -- -local InitRules, ApplyStates + +local InitRules, ApplyStates, SetProperty, GetProperty do -- As far as I can tell the macro clauses are NOT locale-specific. local ruleformats = { @@ -89,18 +90,26 @@ ruleformats.treeOrMoonkin = ("form:%d"):format(treekin) 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 dotdotdot = { ... } + table.sort(r, function(lhs, rhs) + local olhs = tfetch(t[lhs], unpack(dotdotdot)) or 0 + local orhs = tfetch(t[rhs], unpack(dotdotdot)) or 0 + return olhs < orhs + end) + return r + end + local function BuildRuleString(states) local s = "" local default - local sorted = { } - for name in pairs(states) do - table.insert(sorted,name) - end - table.sort(sorted, function(lhs, rhs) - local olhs = tfetch(states[lhs],"rule","order") or 0 - local orhs = tfetch(states[rhs],"rule","order") or 0 - return olhs < orhs - end) + local sorted = fieldsort(states, "rule", "order") for idx, name in ipairs(sorted) do local state = states[name] local semi = #s > 0 and "; " or "" @@ -137,36 +146,130 @@ if default then s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default) end - return s + return s, default end local drivers = setmetatable({},{__mode="k"}) + local propertyFuncs = { } function ApplyStates( bar ) local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then local frame = bar:GetFrame() - local string = BuildRuleString(states) - ReAction:Print("'"..string.."'") + local string, default = BuildRuleString(states) if string and #string > 0 then + drivers[bar] = true + -- register a map for each "statemap-reaction-XXX" to set 'state' to 'XXX' + -- UNLESS we're in a keybound state AND there's a default state, in which case + -- all keybound states go back to themselves. + local keybindprefix + if default then + local tmp = { } + for state, config in pairs(states) do + if tfetch(config, "rule", "type") == "keybind" then + bar:SetStateKeybind(tfetch(config,"rule","keybind"), state, tfetch(config,"rule","keybindreturn") or default or 0) + table.insert(tmp, ("%s:%s"):format(state,state)) + end + end + if #tmp > 0 then + table.insert(tmp,"") -- to get a final ';' + end + keybindprefix = table.concat(tmp,";") + end + for state in pairs(states) do + frame:SetAttribute(("statemap-reaction-%s"):format(state), ("%s%s"):format(keybindprefix or "",state)) + end -- register a handler to set the value of attribute "state-reaction" -- in response to events as per the rule string RegisterStateDriver(frame, "reaction", string) - drivers[bar] = true - -- register a trivial map for each "statemap-reaction-XXX" to set 'state' to 'XXX' - for state in pairs(states) do - frame:SetAttribute(("statemap-reaction-%s"):format(state), state) - end + SecureStateHeader_Refresh(frame) elseif drivers[bar] then UnregisterStateDriver(frame, "reaction") drivers[bar] = nil end + for k, f in pairs(propertyFuncs) do + f(bar, states) + 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 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 + + -- state property functions + function propertyFuncs.hide( bar, states ) + local tmp = { } + for state, config in pairs(states) do + if config.hide then + table.insert(tmp, state) + end + end + local s = table.concat(tmp,",") + bar:SetHideStates(s) + end + + function propertyFuncs.page( bar, states ) + local map = { } + for state, config in pairs(states) do + map[state] = config.page + end + bar:SetStatePageMap(state, map) + end + + function propertyFuncs.keybindstate( bar, states ) + local map = { } + for state, config in pairs(states) do + if config.keybindstate then + table.insert(map,state) + end + end + bar:SetStateKeybindOverrideMap(map) + end + + function propertyFuncs.enableanchor( bar, states ) + + end + + function propertyFuncs.anchorPoint( bar, states ) + + end + + function propertyFuncs.anchorRelPoint( bar, states ) + + end + + function propertyFuncs.anchorX( bar, states ) + + end + + function propertyFuncs.anchorY( bar, states ) + + end + + function propertyFuncs.enablescale( bar, states ) + + end + + function propertyFuncs.scale( bar, states ) + + end + end --- module event handlers + +-- module event handlers -- + function module:OnInitialize() self.db = ReAction.db:RegisterNamespace( moduleID, { @@ -205,7 +308,7 @@ function module:OnRefreshBar(event, bar, name) local c = self.db.profile.bars[name] if c then - self:UpdateStates(bar) + ApplyStates(bar) end end @@ -223,28 +326,6 @@ end --- API -- - -function module:UpdateStates( bar ) - ApplyStates(bar) -end - -function module:CreateState( bar, name ) - local states = tbuild(self.db.profile.bars, bar:GetName(), "states") - if states[name] then - ReAction:UserError(L["State named '%s' already exists"]:format(name)) - else - states[name] = { } - end -end - -function module:DeleteState( bar, name ) - local states = tfetch(self.db.profile.bars, bar:GetName(), "states") - if states[name] then - states[name] = nil - self:UpdateStates(bar) - end -end -- Options -- @@ -278,6 +359,19 @@ 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, hidden, fields = unpack(c) @@ -299,14 +393,26 @@ local states = tbuild(module.db.profile.bars, bar:GetName(), "states") - local function put( key, value, ... ) + local function update() + ApplyStates(bar) + end + + local function setrule( key, value, ... ) tbuild(states, opts.name, "rule", ...)[key] = value end - local function fetch( ... ) + local function getrule( ... ) return tfetch(states, opts.name, "rule", ...) end + local function setprop(info, value) + SetProperty(bar, opts.name, info[#info], value) + end + + local function getprop(info) + return GetProperty(bar, opts.name, info[#info]) + end + local function 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 @@ -317,9 +423,9 @@ local rule, hidden, fields = unpack(c) local found = false for key in ipairs(fields) do - if fetch("values",key) then + if getrule("values",key) then if (found or setkey) and key ~= setkey then - put(key,false,"values") + setrule(key,false,"values") if not setkey and not notified then ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) notified = true @@ -359,12 +465,13 @@ a.order, b.order = b.order, a.order end - local function update() - module:UpdateStates(bar) + local function anchordisable() + return not GetProperty(bar, opts.name, "enableanchor") end + tbuild(states, name) - opts.order = fetch("order") + opts.order = getrule("order") if opts.order == nil then -- add after the highest opts.order = 100 @@ -374,28 +481,31 @@ opts.order = x + 1 end end - put("order",opts.order) + setrule("order",opts.order) end opts.args = { - properties = { + ordering = { + name = L["Info"], + order = 1, type = "group", - name = L["Properties"], - order = 1, args = { delete = { + name = L["Delete this State"], + order = -1, type = "execute", - name = L["Delete this State"], func = function(info) - module:DeleteState(bar,opts.name) + if states[opts.name] then + states[opts.name] = nil + ApplyStates(bar) + end optionMap[bar].args[opts.name] = nil end, - order = -1 }, rename = { - type = "input", name = L["Name"], order = 1, + type = "input", get = function() return opts.name end, set = function(info, value) -- check for existing state name @@ -411,17 +521,17 @@ usage = L["State names must be alphanumeric without spaces"], }, ordering = { - type = "group", - inline = true, 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", - name = L["Up"], width = "half", - order = 1, func = function() local before, after = getNeighbors() if before then @@ -431,105 +541,254 @@ end, }, down = { + name = L["Down"], + order = 2, type = "execute", - name = L["Down"], width = "half", - order = 2, func = function() local before, after = getNeighbors() if after then - ReAction:Print(opts.name, after) swapOrder(opts.name, after) update() end end, } } - }, - -- keybinding for show-this-state would go here - -- show/hide would go here - -- page # would go here - -- anchoring would go here + } } }, + 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 = 2, + type = "toggle", + set = setprop, + get = getprop, + }, + page = { + name = L["Show Page #"], + order = 3, + type = "select", + disabled = function() + return bar:GetNumPages() < 2 + end, + hidden = function() + return bar:GetNumPages() < 2 + end, + values = function() + local pages = { none = " " } + for i = 1, bar:GetNumPages() do + pages[i] = i + end + return pages + end, + set = function(info, value) + if value == "none" then + setprop(info, nil) + else + setprop(info, value) + end + end, + get = function(info) + return getprop(info) or "none" + end, + }, + keybindstate = { + name = L["Override Keybinds"], + desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"], + order = 4, + type = "toggle", + set = setprop, + get = getprop, + }, + position = { + name = L["Position"], + order = 5, + type = "group", + inline = true, + args = { + enableanchor = { + name = L["Set New Position"], + order = 1, + type = "toggle", + set = setprop, + get = getprop, + }, + anchorPoint = { + name = L["Point"], + order = 2, + type = "select", + values = pointTable, + set = function(info, value) setprop(info, value ~= "NONE" and value or nil) end, + get = function(info) return getprop(info) or "NONE" end, + disabled = anchordisable, + hidden = anchordisable, + }, + anchorRelPoint = { + name = L["Relative Point"], + order = 3, + type = "select", + values = pointTable, + set = function(info, value) setprop(info, value ~= "NONE" and value or nil) end, + get = function(info) return getprop(info) or "NONE" end, + disabled = anchordisable, + hidden = anchordisable, + }, + anchorX = { + name = L["X Offset"], + order = 4, + type = "range", + min = -100, + max = 100, + step = 1, + set = setprop, + get = getprop, + disabled = anchordisable, + hidden = anchordisable, + }, + anchorY = { + name = L["Y Offset"], + order = 5, + type = "range", + min = -100, + max = 100, + step = 1, + set = setprop, + get = getprop, + disabled = anchordisable, + hidden = anchordisable, + }, + }, + }, + scale = { + name = L["Scale"], + order = 6, + 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.1, + max = 2.5, + step = 0.05, + isPercent = true, + set = setprop, + get = function(info) return getprop(info) or 1 end, + disabled = function() return not GetProperty(bar, opts.name, "enablescale") end, + hidden = function() return not GetProperty(bar, opts.name, "enablescale") end, + }, + }, + }, + }, + }, rules = { + name = L["Selection Rule"], + order = 3, type = "group", - name = L["Rules"], - order = 2, args = { mode = { + name = L["Select this state"], + order = 2, type = "select", style = "radio", - name = L["Select this state"], values = { default = L["by default"], any = L["when ANY of these"], all = L["when ALL of these"], - custom = L["via custom rule"] + custom = L["via custom rule"], + keybind = L["via keybinding"], }, set = function( info, value ) - put("type", value) + setrule("type", value) fixall() update() end, get = function( info ) - return fetch("type") + return getrule("type") end, - order = 2 }, clear = { + name = L["Clear All"], + order = 3, type = "execute", - name = L["Clear All"], + hidden = function() + local t = getrule("type") + return t ~= "any" and t ~= "all" + end, + disabled = function() + local t = getrule("type") + return t ~= "any" and t ~= "all" + end, func = function() - local type = fetch("type") + local type = getrule("type") if type == "custom" then - put("custom","") + setrule("custom","") elseif type == "any" or type == "all" then - put("values", {}) + setrule("values", {}) end update() end, - order = 3 }, inputs = { + name = L["Conditions"], + order = 4, type = "multiselect", - name = L["Rules"], hidden = function() - return fetch("type") == "custom" + local t = getrule("type") + return t ~= "any" and t ~= "all" end, disabled = function() - return fetch("type") == "default" + local t = getrule("type") + return t ~= "any" and t ~= "all" end, values = ruleSelect, set = function(info, key, value ) - put(ruleMap[key], value or nil, "values") + setrule(ruleMap[key], value or nil, "values") if value then fixall(ruleMap[key]) end update() end, get = function(info, key) - return fetch("values", ruleMap[key]) or false + return getrule("values", ruleMap[key]) or false end, - order = 4 }, custom = { + name = L["Custom Rule"], + order = 5, type = "input", multiline = true, hidden = function() - return fetch("type") ~= "custom" + return getrule("type") ~= "custom" end, disabled = function() - return fetch("type") == "default" + return getrule("type") ~= "custom" end, - name = L["Custom Rule"], desc = L["Syntax like macro rules: see preset rules for examples"], set = function(info, value) - put("custom",value) + setrule("custom",value) update() end, get = function(info) - return fetch("custom") or "" + return getrule("custom") or "" end, validate = function (info, rule) local s = rule:gsub("%s","") -- remove all spaces @@ -546,10 +805,55 @@ until c == nil return true end, - order = 5, - } - } - } + }, + keybind = { + name = L["Keybinding"], + order = 6, + inline = true, + hidden = function() return getrule("type") ~= "keybind" end, + disabled = function() return getrule("type") ~= "keybind" end, + type = "group", + args = { + desc = { + 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."], + order = 1, + type = "description", + }, + keybind = { + name = L["State Hotkey"], + desc = L["Define an override toggle keybind"], + order = 2, + type = "keybinding", + set = function(info, value) + setrule("keybind",value) + update() + end, + get = function() return getrule("keybind") end, + }, + default = { + name = L["Toggle Off State"], + desc = L["Select a state to return to when the keybind override is toggled off"], + order = 3, + type = "select", + values = function() + local t = { } + for k in pairs(states) do + if k ~= opts.name then + t[k] = k + end + end + return t + end, + set = function(info, value) + setrule("keybindreturn",value) + update() + end, + get = function() return getrule("keybindreturn") end, + }, + }, + }, + }, + }, } return opts end @@ -564,39 +868,44 @@ disabled = InCombatLockdown, args = { __desc__ = { + name = L["States are evaluated in the order they are listed"], + order = 1, type = "description", - name = L["States are evaluated in the order they are listed"], - order = 1 }, __new__ = { - type = "group", name = L["New State..."], order = 2, + type = "group", args = { name = { - type = "input", 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"], - order = 1 }, create = { + name = L["Create State"], + order = 2, type = "execute", - name = L["Create State"], func = function () local name = private.newstatename - module:CreateState(bar,name) -- TODO: select default state options and pass as final argument - optionMap[bar].args[name] = CreateStateOptions(bar,name) - private.newstatename = "" + if states[name] then + ReAction:UserError(L["State named '%s' already exists"]:format(name)) + else + -- TODO: select default state options and pass as final argument + states[name] = { } + optionMap[bar].args[name] = CreateStateOptions(bar,name) + private.newstatename = "" + end end, disabled = function() local name = private.newstatename or "" return #name == 0 or name:find("%W") end, - order = 2, } } }