flickerstreak@25: local ReAction = ReAction flickerstreak@25: local L = ReAction.L flickerstreak@25: local _G = _G flickerstreak@25: local CreateFrame = CreateFrame flickerstreak@33: local floor = math.floor flickerstreak@71: local SecureStateHeader_Refresh = SecureStateHeader_Refresh flickerstreak@33: flickerstreak@33: flickerstreak@25: flickerstreak@25: -- update ReAction revision if this file is newer flickerstreak@33: local revision = tonumber(("$Revision$"):match("%d+")) flickerstreak@25: if revision > ReAction.revision then flickerstreak@52: ReAction.revision = revision flickerstreak@25: end flickerstreak@25: flickerstreak@28: ------ BAR CLASS ------ flickerstreak@28: local Bar = { _classID = {} } flickerstreak@25: flickerstreak@28: local function Constructor( self, name, config ) flickerstreak@25: self.name, self.config = name, config flickerstreak@68: self.buttons = setmetatable({},{__mode="k"}) flickerstreak@25: flickerstreak@25: if type(config) ~= "table" then flickerstreak@28: error("ReAction.Bar: config table required") flickerstreak@25: end flickerstreak@25: flickerstreak@54: local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent flickerstreak@68: local f = CreateFrame("Frame",nil,parent,"SecureStateHeaderTemplate") flickerstreak@25: f:SetFrameStrata("MEDIUM") flickerstreak@30: config.width = config.width or 480 flickerstreak@30: config.height = config.height or 40 flickerstreak@25: f:SetWidth(config.width) flickerstreak@25: f:SetWidth(config.height) flickerstreak@25: flickerstreak@63: ReAction.RegisterCallback(self, "OnConfigModeChanged") flickerstreak@63: flickerstreak@25: self.frame = f flickerstreak@25: self:ApplyAnchor() flickerstreak@25: f:Show() flickerstreak@68: self:RefreshLayout() flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:Destroy() flickerstreak@25: local f = self.frame flickerstreak@25: f:UnregisterAllEvents() flickerstreak@25: f:Hide() flickerstreak@25: f:SetParent(UIParent) flickerstreak@25: f:ClearAllPoints() flickerstreak@63: ReAction.UnregisterAllCallbacks(self) flickerstreak@72: if self.statedriver then flickerstreak@72: UnregisterStateDriver(f, "reaction") flickerstreak@72: end flickerstreak@25: self.labelString = nil flickerstreak@25: self.controlFrame = nil flickerstreak@25: self.frame = nil flickerstreak@25: self.config = nil flickerstreak@25: end flickerstreak@25: flickerstreak@63: function Bar:OnConfigModeChanged(event, mode) flickerstreak@73: self:ShowControls(mode) -- ShowControls() defined in Overlay.lua flickerstreak@63: end flickerstreak@63: flickerstreak@25: function Bar:RefreshLayout() flickerstreak@63: ReAction:RefreshBar(self) flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:ApplyAnchor() flickerstreak@25: local f, config = self.frame, self.config flickerstreak@25: f:SetWidth(config.width) flickerstreak@25: f:SetHeight(config.height) flickerstreak@25: local anchor = config.anchor flickerstreak@51: f:ClearAllPoints() flickerstreak@25: if anchor then flickerstreak@52: local anchorTo = f:GetParent() flickerstreak@25: if config.anchorTo then flickerstreak@52: local bar = ReAction:GetBar(config.anchorTo) flickerstreak@52: if bar then flickerstreak@52: anchorTo = bar:GetFrame() flickerstreak@52: else flickerstreak@52: anchorTo = _G[config.anchorTo] flickerstreak@52: end flickerstreak@25: end flickerstreak@52: f:SetPoint(anchor, anchorTo or f:GetParent(), config.relativePoint, config.x or 0, config.y or 0) flickerstreak@25: else flickerstreak@25: f:SetPoint("CENTER") flickerstreak@25: end flickerstreak@25: end flickerstreak@25: flickerstreak@51: function Bar:SetAnchor(point, frame, relativePoint, x, y) flickerstreak@51: local c = self.config flickerstreak@51: c.anchor = point or c.anchor flickerstreak@51: c.anchorTo = frame and frame:GetName() or c.anchorTo flickerstreak@51: c.relativePoint = relativePoint or c.relativePoint flickerstreak@51: c.x = x or c.x flickerstreak@51: c.y = y or c.y flickerstreak@51: self:ApplyAnchor() flickerstreak@51: end flickerstreak@51: flickerstreak@51: function Bar:GetAnchor() flickerstreak@51: local c = self.config flickerstreak@51: return (c.anchor or "CENTER"), (c.anchorTo or self.frame:GetParent():GetName()), (c.relativePoint or c.anchor or "CENTER"), (c.x or 0), (c.y or 0) flickerstreak@51: end flickerstreak@51: flickerstreak@25: function Bar:GetFrame() flickerstreak@25: return self.frame flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetSize() flickerstreak@25: return self.frame:GetWidth() or 200, self.frame:GetHeight() or 200 flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:SetSize(w,h) flickerstreak@25: self.config.width = w flickerstreak@25: self.config.height = h flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetButtonSize() flickerstreak@25: local w = self.config.btnWidth or 32 flickerstreak@25: local h = self.config.btnHeight or 32 flickerstreak@25: -- TODO: get from modules? flickerstreak@25: return w,h flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:SetButtonSize(w,h) flickerstreak@25: if w > 0 and h > 0 then flickerstreak@25: self.config.btnWidth = w flickerstreak@25: self.config.btnHeight = h flickerstreak@25: end flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetButtonGrid() flickerstreak@25: local cfg = self.config flickerstreak@25: local r = cfg.btnRows or 1 flickerstreak@25: local c = cfg.btnColumns or 1 flickerstreak@25: local s = cfg.spacing or 4 flickerstreak@25: return r,c,s flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:SetButtonGrid(r,c,s) flickerstreak@25: if r > 0 and c > 0 and s > 0 then flickerstreak@25: local cfg = self.config flickerstreak@25: cfg.btnRows = r flickerstreak@25: cfg.btnColumns = c flickerstreak@25: cfg.spacing = s flickerstreak@25: end flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetName() flickerstreak@25: return self.name flickerstreak@25: end flickerstreak@25: flickerstreak@33: function Bar:SetName(name) flickerstreak@33: self.name = name flickerstreak@33: if self.controlLabelString then flickerstreak@33: self.controlLabelString:SetText(self.name) flickerstreak@33: end flickerstreak@33: end flickerstreak@33: flickerstreak@25: function Bar:PlaceButton(f, idx, baseW, baseH) flickerstreak@25: local r, c, s = self:GetButtonGrid() flickerstreak@25: local bh, bw = self:GetButtonSize() flickerstreak@25: local row, col = floor((idx-1)/c), mod((idx-1),c) -- zero-based flickerstreak@25: local x, y = col*bw + (col+0.5)*s, row*bh + (row+0.5)*s flickerstreak@25: local scale = bw/baseW flickerstreak@25: flickerstreak@25: f:ClearAllPoints() flickerstreak@25: f:SetPoint("TOPLEFT",x/scale,-y/scale) flickerstreak@25: f:SetScale(scale) flickerstreak@68: self.buttons[f] = true flickerstreak@25: end flickerstreak@25: flickerstreak@71: flickerstreak@71: -- multi-state functions -- flickerstreak@68: function Bar:GetNumPages() flickerstreak@68: return self.config.nPages or 1 flickerstreak@68: end flickerstreak@28: flickerstreak@72: -- flickerstreak@72: -- 'rule' is a rule-string to pass to RegisterStateDriver flickerstreak@72: -- 'states' is a { ["statename"] = } table of all state names flickerstreak@72: -- 'keybinds' is a { ["statename"] = keybind } table of all keybound states flickerstreak@72: -- flickerstreak@72: function Bar:SetStateDriver( rule, states, keybinds ) flickerstreak@72: local f = self.frame flickerstreak@72: local kbprefix = "" flickerstreak@72: do flickerstreak@72: local tmp = { } flickerstreak@72: for s, k in pairs(keybinds) do flickerstreak@72: if k and #k > 0 then -- filter out false table entries flickerstreak@72: -- if in a keybound state, set the stack to the new state but stay in the keybound state. flickerstreak@72: -- use $s as a placeholder for the current state, it will be gsub()'d in later flickerstreak@72: table.insert(tmp,("%s:$s set() %s"):format(s,s)) flickerstreak@72: end flickerstreak@72: end flickerstreak@72: table.insert(tmp,kbprefix) -- to get a trailing ';' if the table is not empty flickerstreak@72: kbprefix = table.concat(tmp,";") flickerstreak@72: end flickerstreak@72: for state in pairs(states) do flickerstreak@72: -- For all states: if in a keybound state, stay there (with stack manipulation, see above). flickerstreak@72: -- Otherwise, go to the state flickerstreak@72: f:SetAttribute(("statemap-reaction-%s"):format(state),("%s%s"):format(kbprefix:gsub("%$s",state),state)) flickerstreak@72: flickerstreak@72: local binding = keybinds[state] flickerstreak@72: self:SetStateKeybind(binding, state) -- set the binding even if nil, to clear it unconditionally flickerstreak@72: if binding then flickerstreak@72: -- for key bindings, use the state-stack to toggle between the last state and the keybound state flickerstreak@72: -- use a different 'virtual state' passed to attribute 'reaction-state' for key bindings, "_binding" flickerstreak@72: f:SetAttribute(("statemap-reaction-%s_binding"):format(state), ("%s:pop();*:set(%s)"):format(state,state)) flickerstreak@72: end flickerstreak@72: end flickerstreak@72: flickerstreak@72: if rule and #rule > 0 then flickerstreak@72: self.stateDriver = true flickerstreak@72: RegisterStateDriver(f, "reaction", rule) flickerstreak@72: elseif self.statedriver then flickerstreak@72: self.statedriver = false flickerstreak@72: UnregisterStateDriver(f, "reaction") flickerstreak@72: end flickerstreak@72: end flickerstreak@72: flickerstreak@68: function Bar:SetHideStates(s) flickerstreak@68: for f in pairs(self.buttons) do flickerstreak@68: if f:GetParent() == self.frame then flickerstreak@68: f:SetAttribute("hidestates",s) flickerstreak@68: end flickerstreak@68: end flickerstreak@68: SecureStateHeader_Refresh(self.frame) flickerstreak@68: end flickerstreak@28: flickerstreak@70: function Bar:SetStateKeybind(key, state, defaultstate) flickerstreak@72: -- Lazily create a tiny offscreen button which sends "_binding" values to the flickerstreak@72: -- bar frame's state-reaction attribute, by using an override binding to generate a flickerstreak@72: -- click on the button with a virtual mouse button "state". flickerstreak@72: -- This gets around making the bar itself a clickable button, which is not desirable flickerstreak@68: local f = self.statebuttonframe flickerstreak@70: if key then flickerstreak@68: if not f then flickerstreak@72: f = CreateFrame("Button",self:GetName().."_statebutton",self.frame,"SecureActionButtonTemplate") flickerstreak@68: f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT") flickerstreak@68: f:SetWidth(1) flickerstreak@68: f:SetHeight(1) flickerstreak@72: f:SetAttribute("type*","attribute") flickerstreak@72: f:SetAttribute("attribute-name*","state-reaction") flickerstreak@72: f:SetAttribute("attribute-frame*",self.frame) flickerstreak@68: f:Show() flickerstreak@72: f.bindings = { } flickerstreak@68: self.statebuttonframe = f flickerstreak@68: end flickerstreak@72: f:SetAttribute(("attribute-value-%s"):format(state),("%s_binding"):format(state)) flickerstreak@72: -- clear the old binding, if any, for this state flickerstreak@72: if f.bindings[state] then flickerstreak@72: SetOverrideBinding(self.frame, false, f.bindings[state], nil) flickerstreak@72: end flickerstreak@72: SetOverrideBindingClick(self.frame, false, key, f:GetName(), state) -- the state name is used as the virtual button flickerstreak@72: f.bindings[state] = key flickerstreak@68: elseif f then flickerstreak@72: key = f.bindings[state] flickerstreak@70: if key then flickerstreak@72: SetOverrideBinding(self.frame, false, key, nil) flickerstreak@72: f.bindings[state] = nil flickerstreak@70: end flickerstreak@68: end flickerstreak@68: end flickerstreak@33: flickerstreak@68: function Bar:SetStatePageMap(state, map) -- map is a { ["statename"] = pagenumber } table flickerstreak@68: local f = self.frame flickerstreak@68: local tmp = { } flickerstreak@68: for s, p in pairs(map) do flickerstreak@71: table.insert(tmp, ("%s:page%d"):format(s,p)) flickerstreak@68: end flickerstreak@68: local spec = table.concat(tmp,";") flickerstreak@72: local current = f:GetAttribute("statebutton") flickerstreak@72: if spec ~= f:GetAttribute("statebutton") then flickerstreak@72: f:SetAttribute("statebutton", spec) flickerstreak@72: end flickerstreak@71: SecureStateHeader_Refresh(f) flickerstreak@68: end flickerstreak@33: flickerstreak@68: function Bar:SetStateKeybindOverrideMap(states) -- 'states' is an array of state-names that should have keybind overrides enabled flickerstreak@68: local f = self.frame flickerstreak@68: for i = 1, #states do flickerstreak@68: local s = states[i] flickerstreak@68: states[i] = ("%s:%s"):format(s,s) flickerstreak@68: end flickerstreak@71: table.insert(states,"_defaultbindings") flickerstreak@68: f:SetAttribute("statebindings",table.concat(states,";")) flickerstreak@71: SecureStateHeader_Refresh(f) flickerstreak@68: for b in pairs(self.buttons) do flickerstreak@71: -- TODO: signal child frames that they should maintain multiple bindings flickerstreak@68: end flickerstreak@68: end flickerstreak@33: flickerstreak@71: local _ofskeys = { "point", "relpoint", "x", "y" } flickerstreak@71: function Bar:SetStateAnchorMap( map ) -- 'map' is a { ["statename"] = { point=point, relpoint=relpoint, x=x, y=y } } table flickerstreak@71: local f = self.frame flickerstreak@71: local c = self.config flickerstreak@71: local default = { point = c.anchor, relpoint = c.relativePoint, x = c.x, y = c.y } flickerstreak@71: for _, key in pairs(_ofskeys) do flickerstreak@71: local t = { } flickerstreak@71: for state, info in pairs(map) do flickerstreak@71: if info[key] then flickerstreak@71: table.insert(t, ("%s:%s"):format(state, info[key])) flickerstreak@71: end flickerstreak@71: end flickerstreak@71: if #t > 0 and default[key] then table.insert(t, tostring(default[key])) end flickerstreak@71: f:SetAttribute(("headofs%s"):format(key), table.concat(t,";") or "") flickerstreak@71: end flickerstreak@71: SecureStateHeader_Refresh(f) flickerstreak@71: end flickerstreak@71: flickerstreak@71: function Bar:SetStateScaleMap( map ) -- 'map' is a { ["statename"] = scalevalue } table flickerstreak@71: local f = self.frame flickerstreak@71: local t = { } flickerstreak@71: for state, scale in pairs(map) do flickerstreak@71: table.insert( t, ("%s:%s"):format(state,scale) ) flickerstreak@71: end flickerstreak@71: if #t > 0 then table.insert(t, "1.0") end flickerstreak@71: f:SetAttribute("headscale",table.concat(t,";") or "") flickerstreak@71: SecureStateHeader_Refresh(f) flickerstreak@71: end flickerstreak@71: flickerstreak@71: flickerstreak@33: flickerstreak@28: ------ Export as a class-factory ------ flickerstreak@28: ReAction.Bar = { flickerstreak@73: prototype = Bar, flickerstreak@28: new = function(self, ...) flickerstreak@28: local x = { } flickerstreak@28: for k,v in pairs(Bar) do flickerstreak@28: x[k] = v flickerstreak@28: end flickerstreak@28: Constructor(x, ...) flickerstreak@28: return x flickerstreak@28: end flickerstreak@28: }