flickerstreak@25: local ReAction = ReAction flickerstreak@25: local L = ReAction.L flickerstreak@25: local _G = _G flickerstreak@25: local CreateFrame = CreateFrame flickerstreak@33: local InCombatLockdown = InCombatLockdown flickerstreak@33: local floor = math.floor flickerstreak@33: local min = math.min flickerstreak@33: local format = string.format flickerstreak@33: local GameTooltip = GameTooltip 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@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@63: self:ShowControls(mode) 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@68: function Bar:GetNumPages() flickerstreak@68: return self.config.nPages or 1 flickerstreak@68: end flickerstreak@28: 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@68: -- use a tiny offscreen button to get around making the bar itself a clickable button flickerstreak@68: local f = self.statebuttonframe flickerstreak@68: local off = ("%s_off"):format(state) flickerstreak@70: if key then flickerstreak@68: if not f then flickerstreak@68: f = CreateFrame("Button",self:GetName().."_statebutton",UIParent,"SecureActionButtonTemplate") flickerstreak@68: f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT") flickerstreak@68: f:SetWidth(1) flickerstreak@68: f:SetHeight(1) flickerstreak@68: f:SetAttribute("attribute-name", "state") flickerstreak@68: f:SetAttribute("attribute-frame",self.frame) flickerstreak@68: f:SetAttribute("stateheader",self.frame) flickerstreak@68: f:Show() flickerstreak@68: self.statebuttonframe = f flickerstreak@68: end flickerstreak@68: -- map two virtual buttons to toggle between the state and the default flickerstreak@68: f:SetAttribute(("statebutton-%s"):format(state),("%s:%s;%s"):format(state,off,state)) flickerstreak@68: f:SetAttribute(("type-%s"):format(state),"attribute") flickerstreak@68: f:SetAttribute(("type-%s"):format(off),"attribute") flickerstreak@68: f:SetAttribute(("attribute-value-%s"):format(state), state) flickerstreak@68: f:SetAttribute(("attribute-value-%s"):format(off), defaultstate) flickerstreak@70: SetBindingClick(key, f:GetName(), state) flickerstreak@68: elseif f then flickerstreak@68: f:SetAttribute(("type-%s"):format(state),ATTRIBUTE_NOOP) flickerstreak@68: f:SetAttribute(("type-%s"):format(off),ATTRIBUTE_NOOP) flickerstreak@70: local action = ("CLICK %s:%s"):format(f:GetName(),state) flickerstreak@70: key = GetBindingKey(action) flickerstreak@70: if key then flickerstreak@70: SetBinding(key,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@68: table.insert(tmp, ("%s:%d"):format(s,p)) flickerstreak@68: end flickerstreak@68: local spec = table.concat(tmp,";") flickerstreak@68: f:SetAttribute("statebutton",spec) 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@68: table.insert(states,"_default") flickerstreak@68: f:SetAttribute("statebindings",table.concat(states,";")) flickerstreak@68: for b in pairs(self.buttons) do flickerstreak@68: -- TODO: signal child frames that they should flickerstreak@68: -- maintain multiple bindings flickerstreak@68: end flickerstreak@68: end flickerstreak@33: flickerstreak@33: -- flickerstreak@33: -- Bar config overlay flickerstreak@33: -- flickerstreak@52: local CreateControls flickerstreak@33: flickerstreak@33: do flickerstreak@33: -- upvalue some of these for small OnUpdate performance boost flickerstreak@33: local GetSize = Bar.GetSize flickerstreak@33: local GetButtonSize = Bar.GetButtonSize flickerstreak@33: local GetButtonGrid = Bar.GetButtonGrid flickerstreak@33: local SetSize = Bar.SetSize flickerstreak@33: local SetButtonSize = Bar.SetButtonSize flickerstreak@33: local SetButtonGrid = Bar.SetButtonGrid flickerstreak@33: local ApplyAnchor = Bar.ApplyAnchor flickerstreak@33: flickerstreak@52: local function StoreExtents(bar) flickerstreak@33: local f = bar.frame flickerstreak@33: local point, relativeTo, relativePoint, x, y = f:GetPoint(1) flickerstreak@33: relativeTo = relativeTo or f:GetParent() flickerstreak@33: local anchorTo flickerstreak@63: for name, b in ReAction:IterateBars() do flickerstreak@52: if b and b:GetFrame() == relativeTo then flickerstreak@52: anchorTo = name flickerstreak@52: break flickerstreak@33: end flickerstreak@33: end flickerstreak@33: anchorTo = anchorTo or relativeTo:GetName() flickerstreak@33: local c = bar.config flickerstreak@33: c.anchor = point flickerstreak@33: c.anchorTo = anchorTo flickerstreak@33: c.relativePoint = relativePoint flickerstreak@33: c.x = x flickerstreak@33: c.y = y flickerstreak@33: c.width, c.height = f:GetWidth(), f:GetHeight() flickerstreak@33: end flickerstreak@33: flickerstreak@52: local function StoreSize(bar) flickerstreak@52: local f = bar.frame flickerstreak@52: local c = bar.config flickerstreak@52: c.width, c.height = f:GetWidth(), f:GetHeight() flickerstreak@52: end flickerstreak@52: flickerstreak@52: local function RecomputeButtonSize(bar) flickerstreak@33: local w, h = GetSize(bar) flickerstreak@33: local bw, bh = GetButtonSize(bar) flickerstreak@33: local r, c, s = GetButtonGrid(bar) flickerstreak@33: flickerstreak@33: local scaleW = (floor(w/c) - s) / bw flickerstreak@33: local scaleH = (floor(h/r) - s) / bh flickerstreak@33: local scale = min(scaleW, scaleH) flickerstreak@33: flickerstreak@33: SetButtonSize(bar, scale * bw, scale * bh, s) flickerstreak@33: end flickerstreak@33: flickerstreak@52: local function RecomputeButtonSpacing(bar) flickerstreak@33: local w, h = GetSize(bar) flickerstreak@33: local bw, bh = GetButtonSize(bar) flickerstreak@33: local r, c, s = GetButtonGrid(bar) flickerstreak@33: flickerstreak@33: SetButtonGrid(bar,r,c,min(floor(w/c) - bw, floor(h/r) - bh)) flickerstreak@33: end flickerstreak@33: flickerstreak@52: local function RecomputeGrid(bar) flickerstreak@33: local w, h = GetSize(bar) flickerstreak@33: local bw, bh = GetButtonSize(bar) flickerstreak@33: local r, c, s = GetButtonGrid(bar) flickerstreak@33: flickerstreak@33: SetButtonGrid(bar, floor(h/(bh+s)), floor(w/(bw+s)), s) flickerstreak@33: end flickerstreak@33: flickerstreak@52: local function ClampToButtons(bar) flickerstreak@33: local bw, bh = GetButtonSize(bar) flickerstreak@33: local r, c, s = GetButtonGrid(bar) flickerstreak@50: SetSize(bar, (bw+s)*c + 1, (bh+s)*r + 1) flickerstreak@33: end flickerstreak@33: flickerstreak@52: local function HideGameTooltip() flickerstreak@33: GameTooltip:Hide() flickerstreak@33: end flickerstreak@33: flickerstreak@52: local anchorInside = { inside = true } flickerstreak@52: local anchorOutside = { outside = true } flickerstreak@52: local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" } flickerstreak@52: local oppositeEdges = { flickerstreak@52: TOP = "BOTTOM", flickerstreak@52: BOTTOM = "TOP", flickerstreak@52: LEFT = "RIGHT", flickerstreak@52: RIGHT = "LEFT" flickerstreak@52: } flickerstreak@52: local pointsOnEdge = { flickerstreak@52: BOTTOM = { "BOTTOM", "BOTTOMLEFT", "BOTTOMRIGHT", }, flickerstreak@52: TOP = { "TOP", "TOPLEFT", "TOPRIGHT", }, flickerstreak@52: RIGHT = { "RIGHT", "BOTTOMRIGHT", "TOPRIGHT", }, flickerstreak@52: LEFT = { "LEFT", "BOTTOMLEFT", "TOPLEFT", }, flickerstreak@52: } flickerstreak@52: local edgeSelector = { flickerstreak@52: BOTTOM = 1, -- select x of x,y flickerstreak@52: TOP = 1, -- select x of x,y flickerstreak@52: LEFT = 2, -- select y of x,y flickerstreak@52: RIGHT = 2, -- select y of x,y flickerstreak@52: } flickerstreak@52: local snapPoints = { flickerstreak@52: [anchorOutside] = { flickerstreak@52: BOTTOMLEFT = {"BOTTOMRIGHT","TOPLEFT","TOPRIGHT"}, flickerstreak@52: BOTTOM = {"TOP"}, flickerstreak@52: BOTTOMRIGHT = {"BOTTOMLEFT","TOPRIGHT","TOPLEFT"}, flickerstreak@52: RIGHT = {"LEFT"}, flickerstreak@52: TOPRIGHT = {"TOPLEFT","BOTTOMRIGHT","BOTTOMLEFT"}, flickerstreak@52: TOP = {"BOTTOM"}, flickerstreak@52: TOPLEFT = {"TOPRIGHT","BOTTOMLEFT","BOTTOMRIGHT"}, flickerstreak@52: LEFT = {"RIGHT"}, flickerstreak@52: CENTER = {"CENTER"} flickerstreak@52: }, flickerstreak@52: [anchorInside] = { flickerstreak@52: BOTTOMLEFT = {"BOTTOMLEFT"}, flickerstreak@52: BOTTOM = {"BOTTOM"}, flickerstreak@52: BOTTOMRIGHT = {"BOTTOMRIGHT"}, flickerstreak@52: RIGHT = {"RIGHT"}, flickerstreak@52: TOPRIGHT = {"TOPRIGHT"}, flickerstreak@52: TOP = {"TOP"}, flickerstreak@52: TOPLEFT = {"TOPLEFT"}, flickerstreak@52: LEFT = {"LEFT"}, flickerstreak@52: CENTER = {"CENTER"} flickerstreak@52: } flickerstreak@52: } flickerstreak@52: local insidePointOffsetFuncs = { flickerstreak@52: BOTTOMLEFT = function(x, y) return x, y end, flickerstreak@52: BOTTOM = function(x, y) return 0, y end, flickerstreak@52: BOTTOMRIGHT = function(x, y) return -x, y end, flickerstreak@52: RIGHT = function(x, y) return -x, 0 end, flickerstreak@52: TOPRIGHT = function(x, y) return -x, -y end, flickerstreak@52: TOP = function(x, y) return 0, -y end, flickerstreak@52: TOPLEFT = function(x, y) return x, -y end, flickerstreak@52: LEFT = function(x, y) return x, 0 end, flickerstreak@52: CENTER = function(x, y) return 0, 0 end, flickerstreak@52: } flickerstreak@52: local pointCoordFuncs = { flickerstreak@52: BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end, flickerstreak@52: BOTTOM = function(f) return nil, f:GetBottom() end, flickerstreak@52: BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end, flickerstreak@52: RIGHT = function(f) return f:GetRight(), nil end, flickerstreak@52: TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end, flickerstreak@52: TOP = function(f) return nil, f:GetTop() end, flickerstreak@52: TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end, flickerstreak@52: LEFT = function(f) return f:GetLeft(), nil end, flickerstreak@52: CENTER = function(f) return f:GetCenter() end, flickerstreak@52: } flickerstreak@52: local edgeBoundsFuncs = { flickerstreak@52: BOTTOM = function(f) return f:GetLeft(), f:GetRight() end, flickerstreak@52: LEFT = function(f) return f:GetBottom(), f:GetTop() end flickerstreak@52: } flickerstreak@52: edgeBoundsFuncs.TOP = edgeBoundsFuncs.BOTTOM flickerstreak@52: edgeBoundsFuncs.RIGHT = edgeBoundsFuncs.LEFT flickerstreak@52: flickerstreak@52: flickerstreak@52: -- Returns absolute coordinates x,y of the named point 'p' of frame 'f' flickerstreak@52: local function GetPointCoords( f, p ) flickerstreak@52: local x, y = pointCoordFuncs[p](f) flickerstreak@52: if not(x and y) then flickerstreak@52: local cx, cy = f:GetCenter() flickerstreak@52: x = x or cx flickerstreak@52: y = y or cy flickerstreak@52: end flickerstreak@52: return x, y flickerstreak@52: end flickerstreak@52: flickerstreak@52: flickerstreak@52: -- Returns true if frame 'f1' can be anchored to frame 'f2' flickerstreak@52: local function CheckAnchorable( f1, f2 ) flickerstreak@52: -- can't anchor a frame to itself or to nil flickerstreak@52: if f1 == f2 or f2 == nil then flickerstreak@52: return false flickerstreak@52: end flickerstreak@52: flickerstreak@52: -- can always anchor to UIParent flickerstreak@52: if f2 == UIParent then flickerstreak@52: return true flickerstreak@52: end flickerstreak@52: flickerstreak@52: -- also can't do circular anchoring of frames flickerstreak@52: -- walk the anchor chain, which generally shouldn't be that expensive flickerstreak@52: -- (who nests draggables that deep anyway?) flickerstreak@52: for i = 1, f2:GetNumPoints() do flickerstreak@52: local _, f = f2:GetPoint(i) flickerstreak@52: if not f then f = f2:GetParent() end flickerstreak@52: return CheckAnchorable(f1,f) flickerstreak@52: end flickerstreak@52: flickerstreak@52: return true flickerstreak@52: end flickerstreak@52: flickerstreak@52: -- Returns true if frames f1 and f2 specified edges overlap flickerstreak@52: local function CheckEdgeOverlap( f1, f2, e ) flickerstreak@52: local l1, u1 = edgeBoundsFuncs[e](f1) flickerstreak@52: local l2, u2 = edgeBoundsFuncs[e](f2) flickerstreak@52: return l1 <= l2 and l2 <= u1 or l2 <= l1 and l1 <= u2 flickerstreak@52: end flickerstreak@52: flickerstreak@52: -- Returns true if point p1 on frame f1 overlaps edge e2 on frame f2 flickerstreak@52: local function CheckPointEdgeOverlap( f1, p1, f2, e2 ) flickerstreak@52: local l, u = edgeBoundsFuncs[e2](f2) flickerstreak@52: local x, y = GetPointCoords(f1,p1) flickerstreak@52: x = select(edgeSelector[e2], x, y) flickerstreak@52: return l <= x and x <= u flickerstreak@52: end flickerstreak@52: flickerstreak@52: -- Returns the distance between corresponding edges. It is flickerstreak@52: -- assumed that the passed in edges e1 and e2 are the same or opposites flickerstreak@52: local function GetEdgeDistance( f1, f2, e1, e2 ) flickerstreak@52: local x1, y1 = pointCoordFuncs[e1](f1) flickerstreak@52: local x2, y2 = pointCoordFuncs[e2](f2) flickerstreak@52: return math.abs((x1 or y1) - (x2 or y2)) flickerstreak@52: end flickerstreak@52: flickerstreak@52: local globalSnapTargets = { [UIParent] = anchorInside } flickerstreak@52: flickerstreak@52: local function GetClosestFrameEdge(f1,f2,a) flickerstreak@52: local dist, edge, opp flickerstreak@52: if f2:IsVisible() and CheckAnchorable(f1,f2) then flickerstreak@52: for _, e in pairs(edges) do flickerstreak@52: local o = a.inside and e or oppositeEdges[e] flickerstreak@52: if CheckEdgeOverlap(f1,f2,e) then flickerstreak@52: local d = GetEdgeDistance(f1, f2, e, o) flickerstreak@52: if not dist or (d < dist) then flickerstreak@52: dist, edge, opp = d, e, o flickerstreak@52: end flickerstreak@52: end flickerstreak@52: end flickerstreak@52: end flickerstreak@52: return dist, edge, opp flickerstreak@52: end flickerstreak@52: flickerstreak@52: local function GetClosestVisibleEdge( f ) flickerstreak@52: local r, o, e1, e2 flickerstreak@52: local a = anchorOutside flickerstreak@63: for _, b in ReAction:IterateBars() do flickerstreak@52: local d, e, opp = GetClosestFrameEdge(f,b:GetFrame(),a) flickerstreak@52: if d and (not r or d < r) then flickerstreak@52: r, o, e1, e2 = d, b:GetFrame(), e, opp flickerstreak@52: end flickerstreak@52: end flickerstreak@52: for f2, a2 in pairs(globalSnapTargets) do flickerstreak@52: local d, e, opp = GetClosestFrameEdge(f,f2,a2) flickerstreak@52: if d and (not r or d < r) then flickerstreak@52: r, o, e1, e2, a = d, f2, e, opp, a2 flickerstreak@52: end flickerstreak@52: end flickerstreak@52: return o, e1, e2, a flickerstreak@52: end flickerstreak@52: flickerstreak@52: local function GetClosestVisiblePoint(f1) flickerstreak@52: local f2, e1, e2, a = GetClosestVisibleEdge(f1) flickerstreak@52: if f2 then flickerstreak@52: local rsq, p, rp, x, y flickerstreak@52: -- iterate pointsOnEdge in order and use < to prefer edge centers to corners flickerstreak@52: for _, p1 in ipairs(pointsOnEdge[e1]) do flickerstreak@52: if CheckPointEdgeOverlap(f1,p1,f2,e2) then flickerstreak@52: for _, p2 in pairs(snapPoints[a][p1]) do flickerstreak@52: local x1, y1 = GetPointCoords(f1,p1) flickerstreak@52: local x2, y2 = GetPointCoords(f2,p2) flickerstreak@52: local dx = x1 - x2 flickerstreak@52: local dy = y1 - y2 flickerstreak@52: local rsq2 = dx*dx + dy*dy flickerstreak@52: if not rsq or rsq2 < rsq then flickerstreak@52: rsq, p, rp, x, y = rsq2, p1, p2, dx, dy flickerstreak@52: end flickerstreak@52: end flickerstreak@52: end flickerstreak@52: end flickerstreak@52: return f2, p, rp, x, y flickerstreak@52: end flickerstreak@52: end flickerstreak@52: flickerstreak@52: local function GetClosestPointSnapped(f1, rx, ry, xOff, yOff) flickerstreak@52: local o, p, rp, x, y = GetClosestVisiblePoint(f1) flickerstreak@52: local s = false flickerstreak@52: flickerstreak@52: local sx, sy = insidePointOffsetFuncs[p](xOff or 0, yOff or 0) flickerstreak@52: local xx, yy = pointCoordFuncs[p](f1) flickerstreak@52: if xx and yy then flickerstreak@52: if math.abs(x) <= rx then flickerstreak@52: x = sx flickerstreak@52: s = true flickerstreak@52: end flickerstreak@52: if math.abs(y) <= ry then flickerstreak@52: y = sy flickerstreak@52: s = true flickerstreak@52: end flickerstreak@52: elseif xx then flickerstreak@52: if math.abs(x) <= rx then flickerstreak@52: x = sx flickerstreak@52: s = true flickerstreak@52: if math.abs(y) <= ry then flickerstreak@52: y = sy flickerstreak@52: end flickerstreak@52: end flickerstreak@52: elseif yy then flickerstreak@52: if math.abs(y) <= ry then flickerstreak@52: y = sy flickerstreak@52: s = true flickerstreak@52: if math.abs(x) <= rx then flickerstreak@52: x = sx flickerstreak@52: end flickerstreak@52: end flickerstreak@52: end flickerstreak@52: flickerstreak@52: if x == -0 then x = 0 end flickerstreak@52: if y == -0 then y = 0 end flickerstreak@52: flickerstreak@52: if s then flickerstreak@52: return o, p, rp, math.floor(x), math.floor(y) flickerstreak@52: end flickerstreak@52: end flickerstreak@52: flickerstreak@52: local function CreateSnapIndicator() flickerstreak@52: local si = CreateFrame("Frame",nil,UIParent) flickerstreak@52: si:SetFrameStrata("HIGH") flickerstreak@52: si:SetHeight(8) flickerstreak@52: si:SetWidth(8) flickerstreak@52: local tex = si:CreateTexture() flickerstreak@52: tex:SetAllPoints() flickerstreak@52: tex:SetTexture(1.0, 0.82, 0, 0.8) flickerstreak@52: tex:SetBlendMode("ADD") flickerstreak@52: tex:SetDrawLayer("OVERLAY") flickerstreak@52: return si flickerstreak@52: end flickerstreak@52: flickerstreak@52: local si1 = CreateSnapIndicator() flickerstreak@52: local si2 = CreateSnapIndicator() flickerstreak@52: flickerstreak@52: local function DisplaySnapIndicator( f, rx, ry, xOff, yOff ) flickerstreak@52: local o, p, rp, x, y, snap = GetClosestPointSnapped(f, rx, ry, xOff, yOff) flickerstreak@52: if o then flickerstreak@52: si1:ClearAllPoints() flickerstreak@52: si2:ClearAllPoints() flickerstreak@52: si1:SetPoint("CENTER", f, p, 0, 0) flickerstreak@52: local xx, yy = pointCoordFuncs[rp](o) flickerstreak@52: x = math.abs(x) <=rx and xx and 0 or x flickerstreak@52: y = math.abs(y) <=ry and yy and 0 or y flickerstreak@52: si2:SetPoint("CENTER", o, rp, x, y) flickerstreak@52: si1:Show() flickerstreak@52: si2:Show() flickerstreak@52: else flickerstreak@52: if si1:IsVisible() then flickerstreak@52: si1:Hide() flickerstreak@52: si2:Hide() flickerstreak@52: end flickerstreak@52: end flickerstreak@52: end flickerstreak@52: flickerstreak@52: local function HideSnapIndicator() flickerstreak@52: if si1:IsVisible() then flickerstreak@52: si1:Hide() flickerstreak@52: si2:Hide() flickerstreak@52: end flickerstreak@52: end flickerstreak@52: flickerstreak@33: CreateControls = function(bar) flickerstreak@33: local f = bar.frame flickerstreak@33: flickerstreak@33: f:SetMovable(true) flickerstreak@33: f:SetResizable(true) flickerstreak@33: f:SetClampedToScreen(true) flickerstreak@33: flickerstreak@33: -- buttons on the bar should be direct children of the bar frame. flickerstreak@33: -- The control elements need to float on top of this, which we could flickerstreak@33: -- do with SetFrameLevel() or Raise(), but it's more reliable to do it flickerstreak@33: -- via frame nesting, hence good old foo's appearance here. flickerstreak@33: local foo = CreateFrame("Frame",nil,f) flickerstreak@33: foo:SetAllPoints() flickerstreak@51: foo:SetClampedToScreen(true) flickerstreak@33: flickerstreak@33: local control = CreateFrame("Button", nil, foo) flickerstreak@33: control:EnableMouse(true) flickerstreak@33: control:SetToplevel(true) flickerstreak@33: control:SetPoint("TOPLEFT", -4, 4) flickerstreak@33: control:SetPoint("BOTTOMRIGHT", 4, -4) flickerstreak@33: control:SetBackdrop({ flickerstreak@33: edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", flickerstreak@33: tile = true, flickerstreak@33: tileSize = 16, flickerstreak@33: edgeSize = 16, flickerstreak@33: insets = { left = 0, right = 0, top = 0, bottom = 0 }, flickerstreak@33: }) flickerstreak@33: flickerstreak@33: -- textures flickerstreak@33: local bgTex = control:CreateTexture(nil,"BACKGROUND") flickerstreak@33: bgTex:SetTexture(0.7,0.7,1.0,0.2) flickerstreak@33: bgTex:SetPoint("TOPLEFT",4,-4) flickerstreak@33: bgTex:SetPoint("BOTTOMRIGHT",-4,4) flickerstreak@33: local hTex = control:CreateTexture(nil,"HIGHLIGHT") flickerstreak@33: hTex:SetTexture(0.7,0.7,1.0,0.2) flickerstreak@33: hTex:SetPoint("TOPLEFT",4,-4) flickerstreak@33: hTex:SetPoint("BOTTOMRIGHT",-4,4) flickerstreak@33: hTex:SetBlendMode("ADD") flickerstreak@33: flickerstreak@33: -- label flickerstreak@33: local label = control:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") flickerstreak@33: label:SetAllPoints() flickerstreak@33: label:SetJustifyH("CENTER") flickerstreak@33: label:SetShadowColor(0,0,0,1) flickerstreak@33: label:SetShadowOffset(2,-2) flickerstreak@33: label:SetTextColor(1,1,1,1) flickerstreak@33: label:SetText(bar:GetName()) flickerstreak@33: label:Show() flickerstreak@33: bar.controlLabelString = label -- so that bar:SetName() can update it flickerstreak@33: flickerstreak@33: local StopResize = function() flickerstreak@33: f:StopMovingOrSizing() flickerstreak@33: f.isMoving = false flickerstreak@33: f:SetScript("OnUpdate",nil) flickerstreak@52: StoreSize(bar) flickerstreak@33: ClampToButtons(bar) flickerstreak@33: ApplyAnchor(bar) flickerstreak@63: ReAction:RefreshOptions() flickerstreak@33: end flickerstreak@33: flickerstreak@33: -- edge drag handles flickerstreak@33: for _, point in pairs({"LEFT","TOP","RIGHT","BOTTOM"}) do flickerstreak@33: local edge = CreateFrame("Frame",nil,control) flickerstreak@33: edge:EnableMouse(true) flickerstreak@33: edge:SetWidth(8) flickerstreak@33: edge:SetHeight(8) flickerstreak@33: if point == "TOP" or point == "BOTTOM" then flickerstreak@33: edge:SetPoint(point.."LEFT") flickerstreak@33: edge:SetPoint(point.."RIGHT") flickerstreak@33: else flickerstreak@33: edge:SetPoint("TOP"..point) flickerstreak@33: edge:SetPoint("BOTTOM"..point) flickerstreak@33: end flickerstreak@33: local tex = edge:CreateTexture(nil,"HIGHLIGHT") flickerstreak@33: tex:SetTexture(1.0,0.82,0,0.7) flickerstreak@33: tex:SetBlendMode("ADD") flickerstreak@33: tex:SetAllPoints() flickerstreak@33: edge:RegisterForDrag("LeftButton") flickerstreak@33: edge:SetScript("OnMouseDown", flickerstreak@33: function() flickerstreak@33: local bw, bh = GetButtonSize(bar) flickerstreak@33: local r, c, s = GetButtonGrid(bar) flickerstreak@33: f:SetMinResize( bw+s+1, bh+s+1 ) flickerstreak@33: f:StartSizing(point) flickerstreak@33: f:SetScript("OnUpdate", flickerstreak@33: function() flickerstreak@33: RecomputeGrid(bar) flickerstreak@33: bar:RefreshLayout() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: edge:SetScript("OnMouseUp", StopResize) flickerstreak@33: edge:SetScript("OnEnter", flickerstreak@33: function() flickerstreak@33: GameTooltip:SetOwner(f, "ANCHOR_"..point) flickerstreak@33: GameTooltip:AddLine(L["Drag to add/remove buttons"]) flickerstreak@33: GameTooltip:Show() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: edge:SetScript("OnLeave", HideGameTooltip) flickerstreak@33: edge:Show() flickerstreak@33: end flickerstreak@33: flickerstreak@33: -- corner drag handles, again nested in an anonymous frame so that they are on top flickerstreak@33: local foo2 = CreateFrame("Frame",nil,control) flickerstreak@33: foo2:SetAllPoints(true) flickerstreak@33: for _, point in pairs({"BOTTOMLEFT","TOPLEFT","BOTTOMRIGHT","TOPRIGHT"}) do flickerstreak@33: local corner = CreateFrame("Frame",nil,foo2) flickerstreak@33: corner:EnableMouse(true) flickerstreak@33: corner:SetWidth(12) flickerstreak@33: corner:SetHeight(12) flickerstreak@33: corner:SetPoint(point) flickerstreak@33: local tex = corner:CreateTexture(nil,"HIGHLIGHT") flickerstreak@33: tex:SetTexture(1.0,0.82,0,0.7) flickerstreak@33: tex:SetBlendMode("ADD") flickerstreak@33: tex:SetAllPoints() flickerstreak@33: corner:RegisterForDrag("LeftButton","RightButton") flickerstreak@33: local updateTooltip = function() flickerstreak@33: local size, size2 = bar:GetButtonSize() flickerstreak@33: local rows, cols, spacing = bar:GetButtonGrid() flickerstreak@33: size = (size == size2) and tostring(size) or format("%dx%d",size,size2) flickerstreak@33: GameTooltipTextRight4:SetText(size) flickerstreak@33: GameTooltipTextRight5:SetText(tostring(spacing)) flickerstreak@33: end flickerstreak@33: corner:SetScript("OnMouseDown", flickerstreak@33: function(_,btn) flickerstreak@33: local bw, bh = GetButtonSize(bar) flickerstreak@33: local r, c, s = GetButtonGrid(bar) flickerstreak@33: if btn == "LeftButton" then -- button resize flickerstreak@33: f:SetMinResize( (s+12)*c+1, (s+12)*r+1 ) flickerstreak@33: f:SetScript("OnUpdate", flickerstreak@33: function() flickerstreak@33: RecomputeButtonSize(bar) flickerstreak@33: bar:RefreshLayout() flickerstreak@33: updateTooltip() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: elseif btn == "RightButton" then -- spacing resize flickerstreak@33: f:SetMinResize( bw*c, bh*r ) flickerstreak@33: f:SetScript("OnUpdate", flickerstreak@33: function() flickerstreak@33: RecomputeButtonSpacing(bar) flickerstreak@33: bar:RefreshLayout() flickerstreak@33: updateTooltip() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: end flickerstreak@33: f:StartSizing(point) flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: corner:SetScript("OnMouseUp",StopResize) flickerstreak@33: corner:SetScript("OnEnter", flickerstreak@33: function() flickerstreak@33: GameTooltip:SetOwner(f, "ANCHOR_"..point) flickerstreak@33: GameTooltip:AddLine(L["Drag to resize buttons"]) flickerstreak@33: GameTooltip:AddLine(L["Right-click-drag"]) flickerstreak@33: GameTooltip:AddLine(L["to change spacing"]) flickerstreak@33: local size, size2 = bar:GetButtonSize() flickerstreak@33: local rows, cols, spacing = bar:GetButtonGrid() flickerstreak@33: size = (size == size2) and tostring(size) or format("%dx%d",size,size2) flickerstreak@33: GameTooltip:AddDoubleLine(L["Size:"], size) flickerstreak@33: GameTooltip:AddDoubleLine(L["Spacing:"], tostring(spacing)) flickerstreak@33: GameTooltip:Show() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: corner:SetScript("OnLeave", flickerstreak@33: function() flickerstreak@33: GameTooltip:Hide() flickerstreak@33: f:SetScript("OnUpdate",nil) flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: flickerstreak@33: end flickerstreak@33: flickerstreak@33: control:RegisterForDrag("LeftButton") flickerstreak@33: control:RegisterForClicks("RightButtonDown") flickerstreak@33: flickerstreak@33: control:SetScript("OnDragStart", flickerstreak@33: function() flickerstreak@33: f:StartMoving() flickerstreak@33: f.isMoving = true flickerstreak@52: local w,h = bar:GetButtonSize() flickerstreak@52: f:ClearAllPoints() flickerstreak@52: f:SetScript("OnUpdate", function() flickerstreak@52: if IsShiftKeyDown() then flickerstreak@52: DisplaySnapIndicator(f,w,h) flickerstreak@52: else flickerstreak@52: HideSnapIndicator() flickerstreak@52: end flickerstreak@52: end) flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: flickerstreak@52: local function updateDragTooltip() flickerstreak@52: GameTooltip:SetOwner(f, "ANCHOR_TOPRIGHT") flickerstreak@52: GameTooltip:AddLine(bar.name) flickerstreak@52: GameTooltip:AddLine(L["Drag to move"]) flickerstreak@52: GameTooltip:AddLine(("|cff00ff00%s|r %s"):format(L["Shift-drag"],L["to anchor to nearby frames"])) flickerstreak@52: GameTooltip:AddLine(("|cff00cccc%s|r %s"):format(L["Right-click"],L["for options"])) flickerstreak@52: local _, a = bar:GetAnchor() flickerstreak@52: if a and a ~= "UIParent" then flickerstreak@52: GameTooltip:AddLine(L["Currently anchored to <%s>"]:format(a)) flickerstreak@52: end flickerstreak@52: GameTooltip:Show() flickerstreak@52: end flickerstreak@52: flickerstreak@33: control:SetScript("OnDragStop", flickerstreak@33: function() flickerstreak@33: f:StopMovingOrSizing() flickerstreak@33: f.isMoving = false flickerstreak@33: f:SetScript("OnUpdate",nil) flickerstreak@52: flickerstreak@52: if IsShiftKeyDown() then flickerstreak@52: local w, h = bar:GetButtonSize() flickerstreak@52: local a, p, rp, x, y = GetClosestPointSnapped(f,w,h) flickerstreak@52: if a then flickerstreak@52: f:ClearAllPoints() flickerstreak@52: f:SetPoint(p,a,rp,x,y) flickerstreak@52: end flickerstreak@52: HideSnapIndicator() flickerstreak@52: end flickerstreak@52: flickerstreak@33: StoreExtents(bar) flickerstreak@63: ReAction:RefreshOptions() flickerstreak@52: updateDragTooltip() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: flickerstreak@33: control:SetScript("OnEnter", flickerstreak@33: function() flickerstreak@63: -- TODO: add bar type and status information to name flickerstreak@63: --[[ flickerstreak@33: local name = bar.name flickerstreak@33: for _, m in ReAction:IterateModules() do flickerstreak@33: local suffix = safecall(m,"GetBarNameModifier",bar) flickerstreak@33: if suffix then flickerstreak@33: name = ("%s %s"):format(name,suffix) flickerstreak@33: end flickerstreak@33: end flickerstreak@63: ]]-- flickerstreak@52: flickerstreak@52: updateDragTooltip() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: flickerstreak@33: control:SetScript("OnLeave", HideGameTooltip) flickerstreak@33: flickerstreak@33: control:SetScript("OnClick", flickerstreak@33: function() flickerstreak@33: bar:ShowMenu() flickerstreak@33: end flickerstreak@33: ) flickerstreak@33: flickerstreak@33: return control flickerstreak@33: end flickerstreak@33: end flickerstreak@33: flickerstreak@33: flickerstreak@33: local OpenMenu, CloseMenu flickerstreak@33: do flickerstreak@33: -- Looking for a lightweight AceConfig3-struct-compatible flickerstreak@33: -- replacement for Dewdrop, encapsulate here flickerstreak@33: -- Considering Blizzard's EasyMenu/UIDropDownMenu, but that's flickerstreak@33: -- a bit tricky to convert from AceConfig3-struct flickerstreak@33: local Dewdrop = AceLibrary("Dewdrop-2.0") flickerstreak@33: OpenMenu = function(frame, opts) flickerstreak@33: Dewdrop:Open(frame, "children", opts, "cursorX", true, "cursorY", true) flickerstreak@33: end flickerstreak@33: CloseMenu = function(frame) flickerstreak@33: if Dewdrop:GetOpenedParent() == frame then flickerstreak@33: Dewdrop:Close() flickerstreak@33: end flickerstreak@33: end flickerstreak@33: end flickerstreak@33: flickerstreak@33: flickerstreak@33: function Bar:ShowControls(show) flickerstreak@33: if show then flickerstreak@33: if not self.controlFrame then flickerstreak@33: self.controlFrame = CreateControls(self) flickerstreak@33: end flickerstreak@33: self.controlFrame:Show() flickerstreak@33: elseif self.controlFrame then flickerstreak@33: CloseMenu(self.controlFrame) flickerstreak@33: self.controlFrame:Hide() flickerstreak@33: end flickerstreak@33: end flickerstreak@33: flickerstreak@33: function Bar:ShowMenu() flickerstreak@33: if not self.menuOpts then flickerstreak@33: self.menuOpts = { flickerstreak@33: type = "group", flickerstreak@33: args = { flickerstreak@33: openConfig = { flickerstreak@33: type = "execute", flickerstreak@58: name = L["Settings..."], flickerstreak@58: desc = L["Open the editor for this bar"], flickerstreak@63: func = function() CloseMenu(self.controlFrame); ReAction:ShowEditor(self) end, flickerstreak@33: disabled = InCombatLockdown, flickerstreak@33: order = 1 flickerstreak@33: }, flickerstreak@33: delete = { flickerstreak@33: type = "execute", flickerstreak@33: name = L["Delete Bar"], flickerstreak@33: desc = L["Remove the bar from the current profile"], flickerstreak@50: confirm = L["Are you sure you want to remove this bar?"], flickerstreak@33: func = function() ReAction:EraseBar(self) end, flickerstreak@33: order = 2 flickerstreak@33: }, flickerstreak@33: } flickerstreak@33: } flickerstreak@33: end flickerstreak@33: OpenMenu(self.controlFrame, self.menuOpts) flickerstreak@33: end flickerstreak@33: flickerstreak@33: flickerstreak@33: flickerstreak@28: ------ Export as a class-factory ------ flickerstreak@28: ReAction.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: }