Mercurial > wow > reaction
view Editor.lua @ 284:499ca4edf033 1.1 beta 8
Fixed Pet action buttons not accepting click-hold drags
author | Flick |
---|---|
date | Sat, 11 Jun 2011 10:54:52 -0700 |
parents | f2ed8a8e2320 |
children | 0cb6a9944497 |
line wrap: on
line source
local _, ns = ... local ReAction = ns.ReAction local L = ReAction.L local _G = _G local wipe = wipe local format = string.format local InCombatLockdown = InCombatLockdown local tfetch = ns.tfetch local tbuild = ns.tbuild local AceConfigReg = LibStub("AceConfigRegistry-3.0") local AceConfigDialog = LibStub("AceConfigDialog-3.0") 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"], } local Editor = { buttonHandlers = { } } function Editor:New() -- create new self self = setmetatable( { }, { __index = self } ) self.barOptMap = setmetatable({},{__mode="v"}) self.tmp = { } self.configID = "ReAction-Editor" self.gui = LibStub("AceGUI-3.0"):Create("Frame") local frame = self.gui.frame frame:SetClampedToScreen(true) frame:Hide() self.title = ("%s - %s"):format(L["ReAction"],L["Bar Editor"]) self.gui:SetTitle(self.title) self.options = { type = "group", name = self.title, handler = self, childGroups = "select", args = { launchConfig = { type = "execute", name = L["Global Config"], desc = L["Opens ReAction global configuration settings panel"], func = function() -- AceConfigDialog calls :Open() after every selection, making it -- generally not possible to cleanly close from a menu item. -- If you don't use a custom frame, you can use ACD:Close(), but since -- we're using a custom frame, we have to do the work of closing later in an -- OnUpdate script. ReAction:ShowOptions(); frame:SetScript("OnUpdate", function() self:Close() frame:SetScript("OnUpdate",nil) end) end, order = 1 }, desc = { type = "description", name = L["Use the mouse to arrange and resize the bars on screen. Tooltips on bars indicate additional functionality."], order = 2, }, hdr = { type = "header", name = L["Select Bar"], order = 3, }, _new = { type = "group", name = L["New Bar..."], order = 4, args = { desc = { type = "description", name = L["Choose a name, type, and initial grid for your new action bar:"], order = 1, }, name = { type = "input", name = L["Bar Name"], desc = L["Enter a name for your new action bar"], get = function() return self.tmp.barName or "" end, set = function(info, val) self.tmp.barName = val end, order = 2, }, type = { type = "select", name = L["Button Type"], get = function() return self.tmp.barType or ReAction:GetDefaultBarType() or "" end, set = function(info, val) local c = ReAction:GetDefaultBarConfig(val) self.tmp.barType = val self.tmp.barSize = c.btnWidth or self.tmp.barSize self.tmp.barRows = c.btnRows or self.tmp.barRows self.tmp.barCols = c.btnColumns or self.tmp.barCols self.tmp.barSpacing = c.spacing or self.tmp.barSpacing end, values = "GetBarTypes", order = 3, }, go = { type = "execute", name = L["Create Bar"], func = "CreateBar", order = 4, }, grid = { type = "group", name = L["Button Grid"], inline = true, order = 5, args = { rows = { type = "range", name = L["Rows"], get = function() return self.tmp.barRows or 1 end, set = function(info, val) self.tmp.barRows = val end, width = "double", min = 1, max = 32, step = 1, order = 2, }, cols = { type = "range", name = L["Columns"], get = function() return self.tmp.barCols or 12 end, set = function(info, val) self.tmp.barCols = val end, width = "double", min = 1, max = 32, step = 1, order = 3, }, sz = { type = "range", name = L["Size"], get = function() return self.tmp.barSize or 36 end, set = function(info, val) self.tmp.barSize = val end, width = "double", min = 10, max = 72, step = 1, order = 4, }, spacing = { type = "range", name = L["Spacing"], get = function() return self.tmp.barSpacing or 3 end, set = function(info, val) self.tmp.barSpacing = val end, width = "double", min = 0, max = 24, step = 1, order = 5, } } } } } } } self.gui:SetCallback("OnClose", function() ReAction:SetConfigMode(false) end ) AceConfigReg:RegisterOptionsTable(self.configID, self.options) AceConfigDialog:SetDefaultSize(self.configID, 700, 540) ReAction.RegisterCallback(self,"OnCreateBar") ReAction.RegisterCallback(self,"OnDestroyBar") ReAction.RegisterCallback(self,"OnRenameBar") self:RefreshBarOptions() return self end function Editor:Open(bar, ...) if bar then AceConfigDialog:SelectGroup(self.configID, self.barOptMap[bar:GetName()], ...) end AceConfigDialog:Open(self.configID,self.gui) self.gui:SetTitle(self.title) end function Editor:Close() if self.gui then self.gui:ReleaseChildren() self.gui:Hide() end end function Editor:Refresh() AceConfigReg:NotifyChange(self.configID) end function Editor:UpdateBarOptions(bar) local name = bar:GetName() local key = self.barOptMap[name] local args = self.options.args if not key then -- AceConfig doesn't allow spaces, etc, in arg key names, and they must be -- unique strings. So generate a unique key (it can be whatever) for the bar local i = 1 repeat key = ("bar%s"):format(i) i = i+1 until args[key] == nil self.barOptMap[name] = key args[key] = { type = "group", name = name, childGroups = "tab", order = i+100, args = { general = { type = "group", name = L["General"], order = 1, args = { name = { type = "input", name = L["Rename Bar"], get = function() return bar:GetName() end, set = function(info, value) return ReAction:RenameBar(bar, value) end, order = 1, }, delete = { type = "execute", name = L["Delete Bar"], desc = function() return bar:GetName() end, confirm = true, func = function() ReAction:EraseBar(bar) end, order = 2 }, optionsHdr = { type = "header", name = "", order = 3, }, clickDown = { type = "toggle", name = L["Activate on Down"], desc = L["Activate the button when the key or mouse button is pressed down instead of when it is released"], order = 4, width = "full", set = function(info, value) bar:GetConfig().clickDown = value; ReAction:RebuildAll() end, get = function() return bar:GetConfig().clickDown end, }, alpha = { type = "range", name = L["Transparency"], get = function() return bar:GetAlpha() end, set = function(info, val) bar:SetAlpha(val) end, min = 0, max = 1, isPercent = true, step = 0.01, bigStep = 0.05, order = 5, }, grid = { type = "group", name = L["Button Grid"], inline = true, order = 6, args = { rows = { type = "range", name = L["Rows"], get = function() return select(1,bar:GetButtonGrid()) end, set = function(info, val) bar:SetButtonGrid(val) end, width = "double", min = 1, max = 32, step = 1, order = 2, }, cols = { type = "range", name = L["Columns"], get = function() return select(2,bar:GetButtonGrid()) end, set = function(info, val) bar:SetButtonGrid(nil,val) end, width = "double", min = 1, max = 32, step = 1, order = 3, }, sz = { type = "range", name = L["Size"], get = function() return select(1,bar:GetButtonSize()) end, set = function(info, val) bar:SetButtonSize(val,val) end, width = "double", min = 10, max = 72, step = 1, order = 4, }, spacing = { type = "range", name = L["Spacing"], get = function() return select(3,bar:GetButtonGrid()) end, set = function(info, val) bar:SetButtonGrid(nil,nil,val) end, width = "double", min = 0, max = 24, step = 1, order = 5, } } }, anchor = { type = "group", name = L["Anchor"], inline = true, order = 7, args = { frame = { type = "input", name = L["Frame"], desc = L["The frame that the bar is anchored to"], get = function() local _, f = bar:GetAnchor(); return f end, set = function(info, val) bar:SetAnchor(nil,val) end, validate = function(info, name) if name then local f = ReAction:GetBar(name) if f then return true else f = _G[name] if f and type(f) == "table" and f.IsObjectType and f:IsObjectType("Frame") then local _, explicit = f:IsProtected() return explicit end end end return false end, width = "double", order = 1 }, point = { type = "select", name = L["Point"], desc = L["Anchor point on the bar frame"], style = "dropdown", get = function() return bar:GetAnchor() end, set = function(info, val) bar:SetAnchor(val) end, values = pointTable, order = 2, }, relativePoint = { type = "select", name = L["Relative Point"], desc = L["Anchor point on the target frame"], style = "dropdown", get = function() local p,f,r = bar:GetAnchor(); return r end, set = function(info, val) bar:SetAnchor(nil,nil,val) end, values = pointTable, order = 3, }, x = { type = "input", pattern = "\-?%d+", name = L["X offset"], get = function() local p,f,r,x = bar:GetAnchor(); return ("%d"):format(x) end, set = function(info,val) bar:SetAnchor(nil,nil,nil,val) end, order = 4 }, y = { type = "input", pattern = "\-?%d+", name = L["Y offset"], get = function() local p,f,r,x,y = bar:GetAnchor(); return ("%d"):format(y) end, set = function(info,val) bar:SetAnchor(nil,nil,nil,nil,val) end, order = 5 }, }, }, }, }, buttonOpts = self:CreateButtonOptions(bar), stateOpts = self:CreateStateOptions(bar) } } end end function Editor:CreateButtonOptions(bar) local buttonClass = bar:GetButtonClass() local classID = buttonClass:GetButtonTypeID() local handler = self.buttonHandlers[classID] if handler then local h = handler:New(bar) return h:GetOptions() end end function Editor:RefreshBarOptions() for name, key in pairs(self.barOptMap) do if not ReAction:GetBar(name) then self.barOptMap[name] = nil self.options.args[key] = nil end end for name, bar in ReAction:IterateBars() do self:UpdateBarOptions(bar) end self:Refresh() end function Editor:OnCreateBar(evt, bar) self:UpdateBarOptions(bar) self:Refresh() end function Editor:OnDestroyBar(evt, bar, name) local key = self.barOptMap[name] if key then self.barOptMap[name] = nil self.options.args[key] = nil self:Refresh() end end function Editor:OnRenameBar(evt, bar, oldname, newname) local key = self.barOptMap[oldname] if key then self.barOptMap[oldname], self.barOptMap[newname] = nil, key self.options.args[key].name = newname self:Refresh() end end local _scratch = { } function Editor:GetBarTypes() wipe(_scratch) return ReAction:GetBarTypeOptions(_scratch) end function Editor:CreateBar() if self.tmp.barName and self.tmp.barName ~= "" then local bar = ReAction:CreateBar(self.tmp.barName, self.tmp.barType or ReAction:GetDefaultBarType(), self.tmp.barRows, self.tmp.barCols, self.tmp.barSize, self.tmp.barSpacing) if bar then AceConfigDialog:SelectGroup(self.configID, self.barOptMap[self.tmp.barName]) self.tmp.barName = nil end end end ------------------------------- ---- Action button handler ---- ------------------------------- do local ActionHandler = { buttonClass = ReAction.Button.Action, options = { pages = { name = L["# Pages"], desc = L["Use the Dynamic State tab to specify page transitions"], order = 1, type = "range", min = 1, max = 10, step = 1, get = "GetNumPages", set = "SetNumPages", }, mindcontrol = { name = L["Mind Control Support"], desc = L["When possessing a target (e.g. via Mind Control), map the first 12 buttons of this bar to the possessed target's actions."], order = 2, type = "toggle", set = "SetMindControl", get = "GetMindControl", }, vehicle = { name = L["Vehicle Support"], desc = L["When on a vehicle, map the first 6 buttons of this bar to the vehicle actions. The vehicle-exit button is mapped to the 7th button. Pitch controls are not supported."], order = 3, type = "toggle", get = "GetVehicle", set = "SetVehicle", }, hideEmpty = { name = L["Hide Empty Buttons"], order = 4, type = "toggle", width = "full", get = "GetHideEmpty", set = "SetHideEmpty", }, lockButtons = { name = L["Lock Buttons"], desc = L["Prevents picking up/dragging actions (use SHIFT to override this behavior)"], order = 5, width = "full", type = "toggle", get = "GetLockButtons", set = "SetLockButtons", }, lockOnlyCombat = { name = L["Only in Combat"], desc = L["Only lock the buttons when in combat"], order = 6, width = "full", type = "toggle", disabled = "LockButtonsCombatDisabled", get = "GetLockButtonsCombat", set = "SetLockButtonsCombat", }, actions = { name = L["Edit Action IDs"], order = 7, type = "group", inline = true, args = { method = { name = L["Assign"], order = 1, type = "select", width = "full", values = { [0] = L["Choose Method..."], [1] = L["Individually"], [2] = L["All at Once"], }, get = "GetActionEditMethod", set = "SetActionEditMethod", }, rowSelect = { name = L["Row"], desc = L["Rows are numbered top to bottom"], order = 2, type = "select", width = "half", hidden = "IsButtonSelectHidden", values = "GetRowList", get = "GetSelectedRow", set = "SetSelectedRow", }, colSelect = { name = L["Col"], desc = L["Columns are numbered left to right"], order = 3, type = "select", width = "half", hidden = "IsButtonSelectHidden", values = "GetColumnList", get = "GetSelectedColumn", set = "SetSelectedColumn", }, pageSelect = { name = L["Page"], order = 4, type = "select", width = "half", hidden = "IsPageSelectHidden", values = "GetPageList", get = "GetSelectedPage", set = "SetSelectedPage", }, single = { name = L["Action ID"], usage = L["Specify ID 1-120"], order = 5, type = "input", width = "half", hidden = "IsButtonSelectHidden", get = "GetActionID", set = "SetActionID", validate = "ValidateActionID", }, multi = { name = L["ID List"], usage = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"], order = 6, type = "input", multiline = true, width = "double", hidden = "IsMultiIDHidden", get = "GetMultiID", set = "SetMultiID", validate = "ValidateMultiID", }, }, }, } } Editor.buttonHandlers[ActionHandler.buttonClass:GetButtonTypeID()] = ActionHandler local meta = { __index = ActionHandler } function ActionHandler:New( bar ) return setmetatable( { bar = bar, config = bar:GetConfig(), }, meta) end function ActionHandler:Refresh() self.buttonClass:SetupBar(self.bar) end function ActionHandler:UpdateButtonLock() self.buttonClass:SetButtonLock(self.bar, self.config.lockButtons, self.config.lockButtonsCombat) end function ActionHandler:GetLastButton() return self.bar:GetButton(self.bar:GetNumButtons()) end -- options handlers function ActionHandler:GetOptions() return { type = "group", name = L["Action Buttons"], handler = self, order = 2, args = self.options } end function ActionHandler:SetHideEmpty(info, value) if value ~= self.config.hideEmpty then self.config.hideEmpty = value for _, b in self.bar:IterateButtons() do b:ShowGrid(not value) end end end function ActionHandler:GetHideEmpty() return self.config.hideEmpty end function ActionHandler:GetLockButtons() return self.config.lockButtons end function ActionHandler:SetLockButtons(info, value) self.config.lockButtons = value self:UpdateButtonLock() end function ActionHandler:GetLockButtonsCombat() return self.config.lockButtonsCombat end function ActionHandler:SetLockButtonsCombat(info, value) self.config.lockButtonsCombat = value self:UpdateButtonLock() end function ActionHandler:LockButtonsCombatDisabled() return not self.config.lockButtons end function ActionHandler:GetNumPages() return self.config.nPages end function ActionHandler:SetNumPages(info, value) self.config.nPages = value self:Refresh() end function ActionHandler:GetMindControl() return self.config.mindcontrol end function ActionHandler:SetMindControl(info, value) self.config.mindcontrol = value self:Refresh() end function ActionHandler:GetVehicle() return self.config.vehicle end function ActionHandler:SetVehicle(info, value) self.config.vehicle = value self:Refresh() end function ActionHandler:GetActionEditMethod() return self.editMethod or 0 end function ActionHandler:SetActionEditMethod(info, value) self.editMethod = value end function ActionHandler:IsButtonSelectHidden() return self.editMethod ~= 1 end function ActionHandler:GetRowList() local r,c = self.bar:GetButtonGrid() if self.rowList == nil or #self.rowList ~= r then local list = { } for i = 1, r do table.insert(list,i) end self.rowList = list end return self.rowList end function ActionHandler:GetSelectedRow() local r, c = self.bar:GetButtonGrid() local row = self.selectedRow or 1 if row > r then row = 1 end self.selectedRow = row return row end function ActionHandler:SetSelectedRow(info, value) self.selectedRow = value end function ActionHandler:GetColumnList() local r,c = self.bar:GetButtonGrid() if self.columnList == nil or #self.columnList ~= c then local list = { } for i = 1, c do table.insert(list,i) end self.columnList = list end return self.columnList end function ActionHandler:GetSelectedColumn() local r, c = self.bar:GetButtonGrid() local col = self.selectedColumn or 1 if col > c then col = 1 end self.selectedColumn = col return col end function ActionHandler:SetSelectedColumn(info, value) self.selectedColumn = value end function ActionHandler:IsPageSelectHidden() return self.editMethod ~= 1 or (self.config.nPages or 1) < 2 end function ActionHandler:GetPageList() local n = self.config.nPages or 1 if self.pageList == nil or #self.pageList ~= n then local p = { } for i = 1, n do table.insert(p,i) end self.pageList = p end return self.pageList end function ActionHandler:GetSelectedPage() local p = self.selectedPage or 1 if p > (self.config.nPages or 1) then p = 1 end self.selectedPage = p return p end function ActionHandler:SetSelectedPage(info, value) self.selectedPage = value end function ActionHandler:GetActionID() local row = self.selectedRow or 1 local col = self.selectedColumn or 1 local r, c = self.bar:GetButtonGrid() local n = (row-1) * c + col local btn = self.bar:GetButton(n) if btn then return tostring(btn:GetActionID(self.selectedPage or 1)) end end function ActionHandler:SetActionID(info, value) local row = self.selectedRow or 1 local col = self.selectedColumn or 1 local r, c = self.bar:GetButtonGrid() local n = (row-1) * c + col local btn = self.bar:GetButton(n) if btn then btn:SetActionID(tonumber(value), self.selectedPage or 1) end end function ActionHandler:ValidateActionID(info, value) value = tonumber(value) if value == nil or value < 1 or value > 120 then return L["Specify ID 1-120"] end return true end function ActionHandler:IsMultiIDHidden() return self.editMethod ~= 2 end function ActionHandler:GetMultiID() local p = { } for i = 1, self.config.nPages or 1 do local b = { } for _, btn in self.bar:IterateButtons() do table.insert(b, btn:GetActionID(i)) end table.insert(p, table.concat(b,",")) end return table.concat(p,";\n") end local function ParseMultiID(nBtns, nPages, s) if s:match("[^%d%s,;]") then return nil end local p = { } for list in s:gmatch("[^;]+") do local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns)) local ids = { list:match(pattern) } if #ids ~= nBtns then return nil end table.insert(p,ids) end if #p ~= nPages then return nil end return p end function ActionHandler:SetMultiID(info, value) local p = ParseMultiID(self.bar:GetNumButtons(), self.config.nPages or 1, value) for page, b in ipairs(p) do for button, id in ipairs(b) do self.bar:GetButton(button):SetActionID(id, page) end end end function ActionHandler:ValidateMultiID(info, value) local bad = L["Invalid action ID list string"] if value == nil or ParseMultiID(self.bar:GetNumButtons(), self.config.nPages or 1, value) == nil then return bad end return true end end ---------------------------------- ---- PetAction button handler ---- ---------------------------------- do local PetHandler = { buttonClass = ReAction.Button.PetAction, } Editor.buttonHandlers[PetHandler.buttonClass:GetButtonTypeID()] = PetHandler local meta = { __index = PetHandler } function PetHandler:New(bar) return setmetatable( { bar = bar, config = bar.config }, meta) end function PetHandler:GetLockButtons() return self.config.lockButtons end function PetHandler:SetLockButtons(info, value) self.config.lockButtons = value self.buttonClass:UpdateButtonLock(self.bar) end function PetHandler:GetLockButtonsCombat() return self.config.lockButtonsCombat end function PetHandler:SetLockButtonsCombat(info, value) self.config.lockButtonsCombat = value self.buttonClass:UpdateButtonLock(self.bar) end function PetHandler:LockButtonsCombatDisabled() return not self.config.lockButtons end function PetHandler:GetOptions() return { type = "group", name = L["Pet Buttons"], handler = self, order = 2, args = { lockButtons = { name = L["Lock Buttons"], desc = L["Prevents picking up/dragging actions (use SHIFT to override this behavior)"], order = 2, type = "toggle", get = "GetLockButtons", set = "SetLockButtons", }, lockOnlyCombat = { name = L["Only in Combat"], desc = L["Only lock the buttons when in combat"], order = 3, type = "toggle", disabled = "LockButtonsCombatDisabled", get = "GetLockButtonsCombat", set = "SetLockButtonsCombat", }, } } end end ------------------------------------- ---- Vehicle Exit button handler ---- ------------------------------------- do local VExitHandler = { buttonClass = ReAction.Button.VehicleExit, } Editor.buttonHandlers[VExitHandler.buttonClass:GetButtonTypeID()] = VExitHandler local meta = { __index = VExitHandler } function VExitHandler:New(bar) return setmetatable( { bar = bar, }, meta) end function VExitHandler:GetConfig() return self.bar:GetConfig() end function VExitHandler:GetPassengerOnly() return not self:GetConfig().withControls end function VExitHandler:SetPassengerOnly(info, value) self:GetConfig().withControls = not value self.buttonClass:UpdateRegistration(self.bar) end function VExitHandler:GetOptions() return { type = "group", name = L["Exit Vehicle"], handler = self, args = { passengerOnly = { name = L["Show only when passenger"], desc = L["Only show the button when riding as a passenger in a vehicle (no vehicle controls)"], order = 2, width = "double", type = "toggle", get = "GetPassengerOnly", set = "SetPassengerOnly", }, } } end end ------------------------------ --- Dynamic State options ---- ------------------------------ do local ApplyStates = ReAction.Bar.ApplyStates local CleanupStates = ReAction.Bar.CleanupStates local SetProperty = ReAction.Bar.SetStateProperty local GetProperty = ReAction.Bar.GetStateProperty -- 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"]}, {shadowdance = L["Shadow Dance"]} } }, { "shadow", { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, { "demon", { {demon = L["Demon Form"]}, {nodemon = L["No Demon Form"]} } }, { "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"}) -- 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 = { rename = { name = L["Name"], order = 1, type = "input", get = "GetName", set = "SetStateName", pattern = "^%w*$", usage = L["State names must be alphanumeric without spaces"], }, delete = { name = L["Delete this State"], order = 2, type = "execute", func = "DeleteState", confirm = true, }, ordering = { name = L["Evaluation Order"], desc = L["State transitions are evaluated in the order listed: Move a state up or down to change the order"], order = 3, 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" }, page = { name = L["Show Page #"], order = 11, type = "select", width = "half", disabled = "IsPageDisabled", hidden = "IsPageHidden", values = "GetPageValues", set = "SetProp", get = "GetPage", }, hide = { name = L["Hide Bar"], order = 90, width = "full", 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", }, ]] anchorEnable = { name = L["Reposition"], order = 111, type = "toggle", set = "SetProp", get = "GetProp", }, anchorGroup = { name = L["Position"], order = 112, type = "group", inline = true, disabled = "GetAnchorDisabled", args = { anchorFrame = { name = L["Anchor Frame"], order = 1, type = "select", values = "GetAnchorFrames", set = "SetAnchorFrame", get = "GetAnchorFrame", }, anchorPoint = { name = L["Point"], order = 2, type = "select", values = pointTable, set = "SetAnchorPointProp", get = "GetAnchorPointProp", }, anchorRelPoint = { name = L["Relative Point"], order = 3, type = "select", values = pointTable, set = "SetAnchorPointProp", get = "GetAnchorPointProp", }, anchorX = { name = L["X Offset"], order = 4, type = "range", min = -100, max = 100, step = 1, set = "SetProp", get = "GetProp", }, anchorY = { name = L["Y Offset"], order = 5, type = "range", min = -100, max = 100, step = 1, set = "SetProp", get = "GetProp", }, }, }, enableScale = { name = L["Set New Scale"], order = 121, type = "toggle", set = "SetProp", get = "GetProp", }, scaleGroup = { name = L["Scale"], order = 122, type = "group", inline = true, disabled = "GetScaleDisabled", args = { scale = { name = L["Scale"], order = 1, type = "range", min = 0.25, max = 2.5, step = 0.05, isPercent = true, set = "SetProp", get = "GetScale", }, }, }, enableAlpha = { name = L["Set Transparency"], order = 131, type = "toggle", set = "SetProp", get = "GetProp", }, alphaGroup = { name = L["Transparency"], order = 132, type = "group", inline = true, disabled = "GetAlphaDisabled", args = { alpha = { name = L["Transparency"], order = 1, type = "range", min = 0, max = 1, step = 0.01, bigStep = 0.05, isPercent = true, set = "SetProp", get = "GetAlpha", }, }, }, }, plugins = { } }, rules = { name = L["Rule"], order = 3, type = "group", args = { mode = { name = L["Select this state"], order = 2, type = "select", style = "dropdown", 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 StateHandler = { } local meta = { __index = StateHandler } function StateHandler:New( bar, opts ) local self = setmetatable( { bar = bar }, meta ) 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(bar:GetConfig(), "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:IsPageDisabled() local n = self.bar:GetConfig().nPages or 1 return not (n > 1) end function StateHandler:IsPageHidden() return not self.bar:GetConfig().nPages end function StateHandler:GetPageValues() if not self._pagevalues then self._pagevalues = { } end local n = self.bar:GetConfig().nPages -- cache the results if self._npages ~= n then self._npages = n wipe(self._pagevalues) for i = 1, n do self._pagevalues["page"..i] = i end end return self._pagevalues end function StateHandler:GetPage(info) return self:GetProp(info) or 1 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 self.bar:SetFrameRef("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 Editor:CreateStateOptions(bar) local private = { } local states = tbuild(bar:GetConfig(), "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, "stateOpts", 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 ---- Export to ReAction ---- function ReAction:ShowEditor(bar, ...) if InCombatLockdown() then self:UserError(L["ReAction config mode disabled during combat."]) else self.editor = self.editor or Editor:New() self.editor:Open(bar, ...) self:SetConfigMode(true) end end function ReAction:CloseEditor() if self.editor then self.editor:Close() end end function ReAction:RefreshEditor() if self.editor then self.editor:RefreshBarOptions() end end