| flickerstreak@25 | 1 local ReAction = ReAction | 
| flickerstreak@25 | 2 local L = ReAction.L | 
| flickerstreak@25 | 3 local _G = _G | 
| flickerstreak@25 | 4 local CreateFrame = CreateFrame | 
| flickerstreak@33 | 5 local InCombatLockdown = InCombatLockdown | 
| flickerstreak@33 | 6 local floor = math.floor | 
| flickerstreak@33 | 7 local min = math.min | 
| flickerstreak@33 | 8 local format = string.format | 
| flickerstreak@33 | 9 local GameTooltip = GameTooltip | 
| flickerstreak@33 | 10 | 
| flickerstreak@33 | 11 | 
| flickerstreak@25 | 12 | 
| flickerstreak@25 | 13 -- update ReAction revision if this file is newer | 
| flickerstreak@33 | 14 local revision = tonumber(("$Revision$"):match("%d+")) | 
| flickerstreak@25 | 15 if revision > ReAction.revision then | 
| flickerstreak@52 | 16   ReAction.revision = revision | 
| flickerstreak@25 | 17 end | 
| flickerstreak@25 | 18 | 
| flickerstreak@28 | 19 ------ BAR CLASS ------ | 
| flickerstreak@28 | 20 local Bar = { _classID = {} } | 
| flickerstreak@25 | 21 | 
| flickerstreak@28 | 22 local function Constructor( self, name, config ) | 
| flickerstreak@25 | 23   self.name, self.config = name, config | 
| flickerstreak@68 | 24   self.buttons = setmetatable({},{__mode="k"}) | 
| flickerstreak@25 | 25 | 
| flickerstreak@25 | 26   if type(config) ~= "table" then | 
| flickerstreak@28 | 27     error("ReAction.Bar: config table required") | 
| flickerstreak@25 | 28   end | 
| flickerstreak@25 | 29 | 
| flickerstreak@54 | 30   local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent | 
| flickerstreak@68 | 31   local f = CreateFrame("Frame",nil,parent,"SecureStateHeaderTemplate") | 
| flickerstreak@25 | 32   f:SetFrameStrata("MEDIUM") | 
| flickerstreak@30 | 33   config.width = config.width or 480 | 
| flickerstreak@30 | 34   config.height = config.height or 40 | 
| flickerstreak@25 | 35   f:SetWidth(config.width) | 
| flickerstreak@25 | 36   f:SetWidth(config.height) | 
| flickerstreak@25 | 37 | 
| flickerstreak@63 | 38   ReAction.RegisterCallback(self, "OnConfigModeChanged") | 
| flickerstreak@63 | 39 | 
| flickerstreak@25 | 40   self.frame = f | 
| flickerstreak@25 | 41   self:ApplyAnchor() | 
| flickerstreak@25 | 42   f:Show() | 
| flickerstreak@68 | 43   self:RefreshLayout() | 
| flickerstreak@25 | 44 end | 
| flickerstreak@25 | 45 | 
| flickerstreak@25 | 46 function Bar:Destroy() | 
| flickerstreak@25 | 47   local f = self.frame | 
| flickerstreak@25 | 48   f:UnregisterAllEvents() | 
| flickerstreak@25 | 49   f:Hide() | 
| flickerstreak@25 | 50   f:SetParent(UIParent) | 
| flickerstreak@25 | 51   f:ClearAllPoints() | 
| flickerstreak@63 | 52   ReAction.UnregisterAllCallbacks(self) | 
| flickerstreak@25 | 53   self.labelString = nil | 
| flickerstreak@25 | 54   self.controlFrame = nil | 
| flickerstreak@25 | 55   self.frame = nil | 
| flickerstreak@25 | 56   self.config = nil | 
| flickerstreak@25 | 57 end | 
| flickerstreak@25 | 58 | 
| flickerstreak@63 | 59 function Bar:OnConfigModeChanged(event, mode) | 
| flickerstreak@63 | 60   self:ShowControls(mode) | 
| flickerstreak@63 | 61 end | 
| flickerstreak@63 | 62 | 
| flickerstreak@25 | 63 function Bar:RefreshLayout() | 
| flickerstreak@63 | 64   ReAction:RefreshBar(self) | 
| flickerstreak@25 | 65 end | 
| flickerstreak@25 | 66 | 
| flickerstreak@25 | 67 function Bar:ApplyAnchor() | 
| flickerstreak@25 | 68   local f, config = self.frame, self.config | 
| flickerstreak@25 | 69   f:SetWidth(config.width) | 
| flickerstreak@25 | 70   f:SetHeight(config.height) | 
| flickerstreak@25 | 71   local anchor = config.anchor | 
| flickerstreak@51 | 72   f:ClearAllPoints() | 
| flickerstreak@25 | 73   if anchor then | 
| flickerstreak@52 | 74     local anchorTo = f:GetParent() | 
| flickerstreak@25 | 75     if config.anchorTo then | 
| flickerstreak@52 | 76       local bar = ReAction:GetBar(config.anchorTo) | 
| flickerstreak@52 | 77       if bar then | 
| flickerstreak@52 | 78         anchorTo = bar:GetFrame() | 
| flickerstreak@52 | 79       else | 
| flickerstreak@52 | 80         anchorTo = _G[config.anchorTo] | 
| flickerstreak@52 | 81       end | 
| flickerstreak@25 | 82     end | 
| flickerstreak@52 | 83     f:SetPoint(anchor, anchorTo or f:GetParent(), config.relativePoint, config.x or 0, config.y or 0) | 
| flickerstreak@25 | 84   else | 
| flickerstreak@25 | 85     f:SetPoint("CENTER") | 
| flickerstreak@25 | 86   end | 
| flickerstreak@25 | 87 end | 
| flickerstreak@25 | 88 | 
| flickerstreak@51 | 89 function Bar:SetAnchor(point, frame, relativePoint, x, y) | 
| flickerstreak@51 | 90   local c = self.config | 
| flickerstreak@51 | 91   c.anchor = point or c.anchor | 
| flickerstreak@51 | 92   c.anchorTo = frame and frame:GetName() or c.anchorTo | 
| flickerstreak@51 | 93   c.relativePoint = relativePoint or c.relativePoint | 
| flickerstreak@51 | 94   c.x = x or c.x | 
| flickerstreak@51 | 95   c.y = y or c.y | 
| flickerstreak@51 | 96   self:ApplyAnchor() | 
| flickerstreak@51 | 97 end | 
| flickerstreak@51 | 98 | 
| flickerstreak@51 | 99 function Bar:GetAnchor() | 
| flickerstreak@51 | 100   local c = self.config | 
| flickerstreak@51 | 101   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 | 102 end | 
| flickerstreak@51 | 103 | 
| flickerstreak@25 | 104 function Bar:GetFrame() | 
| flickerstreak@25 | 105   return self.frame | 
| flickerstreak@25 | 106 end | 
| flickerstreak@25 | 107 | 
| flickerstreak@25 | 108 function Bar:GetSize() | 
| flickerstreak@25 | 109   return self.frame:GetWidth() or 200, self.frame:GetHeight() or 200 | 
| flickerstreak@25 | 110 end | 
| flickerstreak@25 | 111 | 
| flickerstreak@25 | 112 function Bar:SetSize(w,h) | 
| flickerstreak@25 | 113   self.config.width = w | 
| flickerstreak@25 | 114   self.config.height = h | 
| flickerstreak@25 | 115 end | 
| flickerstreak@25 | 116 | 
| flickerstreak@25 | 117 function Bar:GetButtonSize() | 
| flickerstreak@25 | 118   local w = self.config.btnWidth or 32 | 
| flickerstreak@25 | 119   local h = self.config.btnHeight or 32 | 
| flickerstreak@25 | 120   -- TODO: get from modules? | 
| flickerstreak@25 | 121   return w,h | 
| flickerstreak@25 | 122 end | 
| flickerstreak@25 | 123 | 
| flickerstreak@25 | 124 function Bar:SetButtonSize(w,h) | 
| flickerstreak@25 | 125   if w > 0 and h > 0 then | 
| flickerstreak@25 | 126     self.config.btnWidth = w | 
| flickerstreak@25 | 127     self.config.btnHeight = h | 
| flickerstreak@25 | 128   end | 
| flickerstreak@25 | 129 end | 
| flickerstreak@25 | 130 | 
| flickerstreak@25 | 131 function Bar:GetButtonGrid() | 
| flickerstreak@25 | 132   local cfg = self.config | 
| flickerstreak@25 | 133   local r = cfg.btnRows or 1 | 
| flickerstreak@25 | 134   local c = cfg.btnColumns or 1 | 
| flickerstreak@25 | 135   local s = cfg.spacing or 4 | 
| flickerstreak@25 | 136   return r,c,s | 
| flickerstreak@25 | 137 end | 
| flickerstreak@25 | 138 | 
| flickerstreak@25 | 139 function Bar:SetButtonGrid(r,c,s) | 
| flickerstreak@25 | 140   if r > 0 and c > 0 and s > 0 then | 
| flickerstreak@25 | 141     local cfg = self.config | 
| flickerstreak@25 | 142     cfg.btnRows = r | 
| flickerstreak@25 | 143     cfg.btnColumns = c | 
| flickerstreak@25 | 144     cfg.spacing = s | 
| flickerstreak@25 | 145   end | 
| flickerstreak@25 | 146 end | 
| flickerstreak@25 | 147 | 
| flickerstreak@25 | 148 function Bar:GetName() | 
| flickerstreak@25 | 149   return self.name | 
| flickerstreak@25 | 150 end | 
| flickerstreak@25 | 151 | 
| flickerstreak@33 | 152 function Bar:SetName(name) | 
| flickerstreak@33 | 153   self.name = name | 
| flickerstreak@33 | 154   if self.controlLabelString then | 
| flickerstreak@33 | 155     self.controlLabelString:SetText(self.name) | 
| flickerstreak@33 | 156   end | 
| flickerstreak@33 | 157 end | 
| flickerstreak@33 | 158 | 
| flickerstreak@25 | 159 function Bar:PlaceButton(f, idx, baseW, baseH) | 
| flickerstreak@25 | 160   local r, c, s = self:GetButtonGrid() | 
| flickerstreak@25 | 161   local bh, bw = self:GetButtonSize() | 
| flickerstreak@25 | 162   local row, col = floor((idx-1)/c), mod((idx-1),c) -- zero-based | 
| flickerstreak@25 | 163   local x, y = col*bw + (col+0.5)*s, row*bh + (row+0.5)*s | 
| flickerstreak@25 | 164   local scale = bw/baseW | 
| flickerstreak@25 | 165 | 
| flickerstreak@25 | 166   f:ClearAllPoints() | 
| flickerstreak@25 | 167   f:SetPoint("TOPLEFT",x/scale,-y/scale) | 
| flickerstreak@25 | 168   f:SetScale(scale) | 
| flickerstreak@68 | 169   self.buttons[f] = true | 
| flickerstreak@25 | 170 end | 
| flickerstreak@25 | 171 | 
| flickerstreak@68 | 172 function Bar:GetNumPages() | 
| flickerstreak@68 | 173   return self.config.nPages or 1 | 
| flickerstreak@68 | 174 end | 
| flickerstreak@28 | 175 | 
| flickerstreak@68 | 176 function Bar:SetHideStates(s) | 
| flickerstreak@68 | 177   for f in pairs(self.buttons) do | 
| flickerstreak@68 | 178     if f:GetParent() == self.frame then | 
| flickerstreak@68 | 179       f:SetAttribute("hidestates",s) | 
| flickerstreak@68 | 180     end | 
| flickerstreak@68 | 181   end | 
| flickerstreak@68 | 182   SecureStateHeader_Refresh(self.frame) | 
| flickerstreak@68 | 183 end | 
| flickerstreak@28 | 184 | 
| flickerstreak@68 | 185 function Bar:SetStateKeybind(keybind, state, defaultstate) | 
| flickerstreak@68 | 186   -- use a tiny offscreen button to get around making the bar itself a clickable button | 
| flickerstreak@68 | 187   local f = self.statebuttonframe | 
| flickerstreak@68 | 188   local off = ("%s_off"):format(state) | 
| flickerstreak@68 | 189   if keybind then | 
| flickerstreak@68 | 190     if not f then | 
| flickerstreak@68 | 191       f = CreateFrame("Button",self:GetName().."_statebutton",UIParent,"SecureActionButtonTemplate") | 
| flickerstreak@68 | 192       f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT") | 
| flickerstreak@68 | 193       f:SetWidth(1) | 
| flickerstreak@68 | 194       f:SetHeight(1) | 
| flickerstreak@68 | 195       f:SetAttribute("attribute-name", "state") | 
| flickerstreak@68 | 196       f:SetAttribute("attribute-frame",self.frame) | 
| flickerstreak@68 | 197       f:SetAttribute("stateheader",self.frame) | 
| flickerstreak@68 | 198       f:Show() | 
| flickerstreak@68 | 199       self.statebuttonframe = f | 
| flickerstreak@68 | 200     end | 
| flickerstreak@68 | 201     -- map two virtual buttons to toggle between the state and the default | 
| flickerstreak@68 | 202     f:SetAttribute(("statebutton-%s"):format(state),("%s:%s;%s"):format(state,off,state)) | 
| flickerstreak@68 | 203     f:SetAttribute(("type-%s"):format(state),"attribute") | 
| flickerstreak@68 | 204     f:SetAttribute(("type-%s"):format(off),"attribute") | 
| flickerstreak@68 | 205     f:SetAttribute(("attribute-value-%s"):format(state), state) | 
| flickerstreak@68 | 206     f:SetAttribute(("attribute-value-%s"):format(off), defaultstate) | 
| flickerstreak@68 | 207     SetBindingClick(keybind, f:GetName(), state) | 
| flickerstreak@68 | 208   elseif f then | 
| flickerstreak@68 | 209     f:SetAttribute(("type-%s"):format(state),ATTRIBUTE_NOOP) | 
| flickerstreak@68 | 210     f:SetAttribute(("type-%s"):format(off),ATTRIBUTE_NOOP) | 
| flickerstreak@68 | 211   end | 
| flickerstreak@68 | 212 end | 
| flickerstreak@33 | 213 | 
| flickerstreak@68 | 214 function Bar:SetStatePageMap(state, map)  -- map is a { ["statename"] = pagenumber } table | 
| flickerstreak@68 | 215   local f = self.frame | 
| flickerstreak@68 | 216   local tmp = { } | 
| flickerstreak@68 | 217   for s, p in pairs(map) do | 
| flickerstreak@68 | 218     table.insert(tmp, ("%s:%d"):format(s,p)) | 
| flickerstreak@68 | 219   end | 
| flickerstreak@68 | 220   local spec = table.concat(tmp,";") | 
| flickerstreak@68 | 221   f:SetAttribute("statebutton",spec) | 
| flickerstreak@68 | 222 end | 
| flickerstreak@33 | 223 | 
| flickerstreak@68 | 224 function Bar:SetStateKeybindOverrideMap(states) -- 'states' is an array of state-names that should have keybind overrides enabled | 
| flickerstreak@68 | 225   local f = self.frame | 
| flickerstreak@68 | 226   for i = 1, #states do | 
| flickerstreak@68 | 227     local s = states[i] | 
| flickerstreak@68 | 228     states[i] = ("%s:%s"):format(s,s) | 
| flickerstreak@68 | 229   end | 
| flickerstreak@68 | 230   table.insert(states,"_default") | 
| flickerstreak@68 | 231   f:SetAttribute("statebindings",table.concat(states,";")) | 
| flickerstreak@68 | 232   for b in pairs(self.buttons) do | 
| flickerstreak@68 | 233     -- TODO: signal child frames that they should | 
| flickerstreak@68 | 234     -- maintain multiple bindings | 
| flickerstreak@68 | 235   end | 
| flickerstreak@68 | 236 end | 
| flickerstreak@33 | 237 | 
| flickerstreak@33 | 238 -- | 
| flickerstreak@33 | 239 -- Bar config overlay | 
| flickerstreak@33 | 240 -- | 
| flickerstreak@52 | 241 local CreateControls | 
| flickerstreak@33 | 242 | 
| flickerstreak@33 | 243 do | 
| flickerstreak@33 | 244   -- upvalue some of these for small OnUpdate performance boost | 
| flickerstreak@33 | 245   local GetSize       = Bar.GetSize | 
| flickerstreak@33 | 246   local GetButtonSize = Bar.GetButtonSize | 
| flickerstreak@33 | 247   local GetButtonGrid = Bar.GetButtonGrid | 
| flickerstreak@33 | 248   local SetSize       = Bar.SetSize | 
| flickerstreak@33 | 249   local SetButtonSize = Bar.SetButtonSize | 
| flickerstreak@33 | 250   local SetButtonGrid = Bar.SetButtonGrid | 
| flickerstreak@33 | 251   local ApplyAnchor   = Bar.ApplyAnchor | 
| flickerstreak@33 | 252 | 
| flickerstreak@52 | 253   local function StoreExtents(bar) | 
| flickerstreak@33 | 254     local f = bar.frame | 
| flickerstreak@33 | 255     local point, relativeTo, relativePoint, x, y = f:GetPoint(1) | 
| flickerstreak@33 | 256     relativeTo = relativeTo or f:GetParent() | 
| flickerstreak@33 | 257     local anchorTo | 
| flickerstreak@63 | 258     for name, b in ReAction:IterateBars() do | 
| flickerstreak@52 | 259       if b and b:GetFrame() == relativeTo then | 
| flickerstreak@52 | 260         anchorTo = name | 
| flickerstreak@52 | 261         break | 
| flickerstreak@33 | 262       end | 
| flickerstreak@33 | 263     end | 
| flickerstreak@33 | 264     anchorTo = anchorTo or relativeTo:GetName() | 
| flickerstreak@33 | 265     local c = bar.config | 
| flickerstreak@33 | 266     c.anchor = point | 
| flickerstreak@33 | 267     c.anchorTo = anchorTo | 
| flickerstreak@33 | 268     c.relativePoint = relativePoint | 
| flickerstreak@33 | 269     c.x = x | 
| flickerstreak@33 | 270     c.y = y | 
| flickerstreak@33 | 271     c.width, c.height = f:GetWidth(), f:GetHeight() | 
| flickerstreak@33 | 272   end | 
| flickerstreak@33 | 273 | 
| flickerstreak@52 | 274   local function StoreSize(bar) | 
| flickerstreak@52 | 275     local f = bar.frame | 
| flickerstreak@52 | 276     local c = bar.config | 
| flickerstreak@52 | 277     c.width, c.height = f:GetWidth(), f:GetHeight() | 
| flickerstreak@52 | 278   end | 
| flickerstreak@52 | 279 | 
| flickerstreak@52 | 280   local function RecomputeButtonSize(bar) | 
| flickerstreak@33 | 281     local w, h = GetSize(bar) | 
| flickerstreak@33 | 282     local bw, bh = GetButtonSize(bar) | 
| flickerstreak@33 | 283     local r, c, s = GetButtonGrid(bar) | 
| flickerstreak@33 | 284 | 
| flickerstreak@33 | 285     local scaleW = (floor(w/c) - s) / bw | 
| flickerstreak@33 | 286     local scaleH = (floor(h/r) - s) / bh | 
| flickerstreak@33 | 287     local scale = min(scaleW, scaleH) | 
| flickerstreak@33 | 288 | 
| flickerstreak@33 | 289     SetButtonSize(bar, scale * bw, scale * bh, s) | 
| flickerstreak@33 | 290   end | 
| flickerstreak@33 | 291 | 
| flickerstreak@52 | 292   local function RecomputeButtonSpacing(bar) | 
| flickerstreak@33 | 293     local w, h = GetSize(bar) | 
| flickerstreak@33 | 294     local bw, bh = GetButtonSize(bar) | 
| flickerstreak@33 | 295     local r, c, s = GetButtonGrid(bar) | 
| flickerstreak@33 | 296 | 
| flickerstreak@33 | 297     SetButtonGrid(bar,r,c,min(floor(w/c) - bw, floor(h/r) - bh)) | 
| flickerstreak@33 | 298   end | 
| flickerstreak@33 | 299 | 
| flickerstreak@52 | 300   local function RecomputeGrid(bar) | 
| flickerstreak@33 | 301     local w, h = GetSize(bar) | 
| flickerstreak@33 | 302     local bw, bh = GetButtonSize(bar) | 
| flickerstreak@33 | 303     local r, c, s = GetButtonGrid(bar) | 
| flickerstreak@33 | 304 | 
| flickerstreak@33 | 305     SetButtonGrid(bar, floor(h/(bh+s)), floor(w/(bw+s)), s) | 
| flickerstreak@33 | 306   end | 
| flickerstreak@33 | 307 | 
| flickerstreak@52 | 308   local function ClampToButtons(bar) | 
| flickerstreak@33 | 309     local bw, bh = GetButtonSize(bar) | 
| flickerstreak@33 | 310     local r, c, s = GetButtonGrid(bar) | 
| flickerstreak@50 | 311     SetSize(bar, (bw+s)*c + 1, (bh+s)*r + 1) | 
| flickerstreak@33 | 312   end | 
| flickerstreak@33 | 313 | 
| flickerstreak@52 | 314   local function HideGameTooltip() | 
| flickerstreak@33 | 315     GameTooltip:Hide() | 
| flickerstreak@33 | 316   end | 
| flickerstreak@33 | 317 | 
| flickerstreak@52 | 318   local anchorInside  = { inside = true } | 
| flickerstreak@52 | 319   local anchorOutside = { outside = true } | 
| flickerstreak@52 | 320   local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" } | 
| flickerstreak@52 | 321   local oppositeEdges = { | 
| flickerstreak@52 | 322     TOP = "BOTTOM", | 
| flickerstreak@52 | 323     BOTTOM = "TOP", | 
| flickerstreak@52 | 324     LEFT = "RIGHT", | 
| flickerstreak@52 | 325     RIGHT = "LEFT" | 
| flickerstreak@52 | 326   } | 
| flickerstreak@52 | 327   local pointsOnEdge = { | 
| flickerstreak@52 | 328     BOTTOM = { "BOTTOM", "BOTTOMLEFT",  "BOTTOMRIGHT",  }, | 
| flickerstreak@52 | 329     TOP    = { "TOP",    "TOPLEFT",     "TOPRIGHT",     }, | 
| flickerstreak@52 | 330     RIGHT  = { "RIGHT",  "BOTTOMRIGHT", "TOPRIGHT",     }, | 
| flickerstreak@52 | 331     LEFT   = { "LEFT",   "BOTTOMLEFT",  "TOPLEFT",      }, | 
| flickerstreak@52 | 332   } | 
| flickerstreak@52 | 333   local edgeSelector = { | 
| flickerstreak@52 | 334     BOTTOM = 1,  -- select x of x,y | 
| flickerstreak@52 | 335     TOP    = 1,  -- select x of x,y | 
| flickerstreak@52 | 336     LEFT   = 2,  -- select y of x,y | 
| flickerstreak@52 | 337     RIGHT  = 2,  -- select y of x,y | 
| flickerstreak@52 | 338   } | 
| flickerstreak@52 | 339   local snapPoints = { | 
| flickerstreak@52 | 340     [anchorOutside] = { | 
| flickerstreak@52 | 341       BOTTOMLEFT  = {"BOTTOMRIGHT","TOPLEFT","TOPRIGHT"}, | 
| flickerstreak@52 | 342       BOTTOM      = {"TOP"}, | 
| flickerstreak@52 | 343       BOTTOMRIGHT = {"BOTTOMLEFT","TOPRIGHT","TOPLEFT"}, | 
| flickerstreak@52 | 344       RIGHT       = {"LEFT"}, | 
| flickerstreak@52 | 345       TOPRIGHT    = {"TOPLEFT","BOTTOMRIGHT","BOTTOMLEFT"}, | 
| flickerstreak@52 | 346       TOP         = {"BOTTOM"}, | 
| flickerstreak@52 | 347       TOPLEFT     = {"TOPRIGHT","BOTTOMLEFT","BOTTOMRIGHT"}, | 
| flickerstreak@52 | 348       LEFT        = {"RIGHT"}, | 
| flickerstreak@52 | 349       CENTER      = {"CENTER"} | 
| flickerstreak@52 | 350     }, | 
| flickerstreak@52 | 351     [anchorInside] = { | 
| flickerstreak@52 | 352       BOTTOMLEFT  = {"BOTTOMLEFT"}, | 
| flickerstreak@52 | 353       BOTTOM      = {"BOTTOM"}, | 
| flickerstreak@52 | 354       BOTTOMRIGHT = {"BOTTOMRIGHT"}, | 
| flickerstreak@52 | 355       RIGHT       = {"RIGHT"}, | 
| flickerstreak@52 | 356       TOPRIGHT    = {"TOPRIGHT"}, | 
| flickerstreak@52 | 357       TOP         = {"TOP"}, | 
| flickerstreak@52 | 358       TOPLEFT     = {"TOPLEFT"}, | 
| flickerstreak@52 | 359       LEFT        = {"LEFT"}, | 
| flickerstreak@52 | 360       CENTER      = {"CENTER"} | 
| flickerstreak@52 | 361     } | 
| flickerstreak@52 | 362   } | 
| flickerstreak@52 | 363   local insidePointOffsetFuncs = { | 
| flickerstreak@52 | 364     BOTTOMLEFT  = function(x, y) return x, y end, | 
| flickerstreak@52 | 365     BOTTOM      = function(x, y) return 0, y end, | 
| flickerstreak@52 | 366     BOTTOMRIGHT = function(x, y) return -x, y end, | 
| flickerstreak@52 | 367     RIGHT       = function(x, y) return -x, 0 end, | 
| flickerstreak@52 | 368     TOPRIGHT    = function(x, y) return -x, -y end, | 
| flickerstreak@52 | 369     TOP         = function(x, y) return 0, -y end, | 
| flickerstreak@52 | 370     TOPLEFT     = function(x, y) return x, -y end, | 
| flickerstreak@52 | 371     LEFT        = function(x, y) return x, 0 end, | 
| flickerstreak@52 | 372     CENTER      = function(x, y) return 0, 0 end, | 
| flickerstreak@52 | 373   } | 
| flickerstreak@52 | 374   local pointCoordFuncs = { | 
| flickerstreak@52 | 375     BOTTOMLEFT  = function(f) return f:GetLeft(),  f:GetBottom() end, | 
| flickerstreak@52 | 376     BOTTOM      = function(f) return nil,          f:GetBottom() end, | 
| flickerstreak@52 | 377     BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end, | 
| flickerstreak@52 | 378     RIGHT       = function(f) return f:GetRight(), nil end, | 
| flickerstreak@52 | 379     TOPRIGHT    = function(f) return f:GetRight(), f:GetTop() end, | 
| flickerstreak@52 | 380     TOP         = function(f) return nil,          f:GetTop() end, | 
| flickerstreak@52 | 381     TOPLEFT     = function(f) return f:GetLeft(),  f:GetTop() end, | 
| flickerstreak@52 | 382     LEFT        = function(f) return f:GetLeft(),  nil end, | 
| flickerstreak@52 | 383     CENTER      = function(f) return f:GetCenter() end, | 
| flickerstreak@52 | 384   } | 
| flickerstreak@52 | 385   local edgeBoundsFuncs = { | 
| flickerstreak@52 | 386     BOTTOM = function(f) return f:GetLeft(), f:GetRight() end, | 
| flickerstreak@52 | 387     LEFT   = function(f) return f:GetBottom(), f:GetTop() end | 
| flickerstreak@52 | 388   } | 
| flickerstreak@52 | 389   edgeBoundsFuncs.TOP   = edgeBoundsFuncs.BOTTOM | 
| flickerstreak@52 | 390   edgeBoundsFuncs.RIGHT = edgeBoundsFuncs.LEFT | 
| flickerstreak@52 | 391 | 
| flickerstreak@52 | 392 | 
| flickerstreak@52 | 393   -- Returns absolute coordinates x,y of the named point 'p' of frame 'f' | 
| flickerstreak@52 | 394   local function GetPointCoords( f, p ) | 
| flickerstreak@52 | 395     local x, y = pointCoordFuncs[p](f) | 
| flickerstreak@52 | 396     if not(x and y) then | 
| flickerstreak@52 | 397       local cx, cy = f:GetCenter() | 
| flickerstreak@52 | 398       x = x or cx | 
| flickerstreak@52 | 399       y = y or cy | 
| flickerstreak@52 | 400     end | 
| flickerstreak@52 | 401     return x, y | 
| flickerstreak@52 | 402   end | 
| flickerstreak@52 | 403 | 
| flickerstreak@52 | 404 | 
| flickerstreak@52 | 405   -- Returns true if frame 'f1' can be anchored to frame 'f2' | 
| flickerstreak@52 | 406   local function CheckAnchorable( f1, f2 ) | 
| flickerstreak@52 | 407     -- can't anchor a frame to itself or to nil | 
| flickerstreak@52 | 408     if f1 == f2 or f2 == nil then | 
| flickerstreak@52 | 409       return false | 
| flickerstreak@52 | 410     end | 
| flickerstreak@52 | 411 | 
| flickerstreak@52 | 412     -- can always anchor to UIParent | 
| flickerstreak@52 | 413     if f2 == UIParent then | 
| flickerstreak@52 | 414       return true | 
| flickerstreak@52 | 415     end | 
| flickerstreak@52 | 416 | 
| flickerstreak@52 | 417     -- also can't do circular anchoring of frames | 
| flickerstreak@52 | 418     -- walk the anchor chain, which generally shouldn't be that expensive | 
| flickerstreak@52 | 419     -- (who nests draggables that deep anyway?) | 
| flickerstreak@52 | 420     for i = 1, f2:GetNumPoints() do | 
| flickerstreak@52 | 421       local _, f = f2:GetPoint(i) | 
| flickerstreak@52 | 422       if not f then f = f2:GetParent() end | 
| flickerstreak@52 | 423       return CheckAnchorable(f1,f) | 
| flickerstreak@52 | 424     end | 
| flickerstreak@52 | 425 | 
| flickerstreak@52 | 426     return true | 
| flickerstreak@52 | 427   end | 
| flickerstreak@52 | 428 | 
| flickerstreak@52 | 429   -- Returns true if frames f1 and f2 specified edges overlap | 
| flickerstreak@52 | 430   local function CheckEdgeOverlap( f1, f2, e ) | 
| flickerstreak@52 | 431     local l1, u1 = edgeBoundsFuncs[e](f1) | 
| flickerstreak@52 | 432     local l2, u2 = edgeBoundsFuncs[e](f2) | 
| flickerstreak@52 | 433     return l1 <= l2 and l2 <= u1 or l2 <= l1 and l1 <= u2 | 
| flickerstreak@52 | 434   end | 
| flickerstreak@52 | 435 | 
| flickerstreak@52 | 436   -- Returns true if point p1 on frame f1 overlaps edge e2 on frame f2 | 
| flickerstreak@52 | 437   local function CheckPointEdgeOverlap( f1, p1, f2, e2 ) | 
| flickerstreak@52 | 438     local l, u = edgeBoundsFuncs[e2](f2) | 
| flickerstreak@52 | 439     local x, y = GetPointCoords(f1,p1) | 
| flickerstreak@52 | 440     x = select(edgeSelector[e2], x, y) | 
| flickerstreak@52 | 441     return l <= x and x <= u | 
| flickerstreak@52 | 442   end | 
| flickerstreak@52 | 443 | 
| flickerstreak@52 | 444   -- Returns the distance between corresponding edges. It is | 
| flickerstreak@52 | 445   -- assumed that the passed in edges e1 and e2 are the same or opposites | 
| flickerstreak@52 | 446   local function GetEdgeDistance( f1, f2, e1, e2 ) | 
| flickerstreak@52 | 447     local x1, y1 = pointCoordFuncs[e1](f1) | 
| flickerstreak@52 | 448     local x2, y2 = pointCoordFuncs[e2](f2) | 
| flickerstreak@52 | 449     return math.abs((x1 or y1) - (x2 or y2)) | 
| flickerstreak@52 | 450   end | 
| flickerstreak@52 | 451 | 
| flickerstreak@52 | 452   local globalSnapTargets = { [UIParent] = anchorInside } | 
| flickerstreak@52 | 453 | 
| flickerstreak@52 | 454   local function GetClosestFrameEdge(f1,f2,a) | 
| flickerstreak@52 | 455     local dist, edge, opp | 
| flickerstreak@52 | 456     if f2:IsVisible() and CheckAnchorable(f1,f2) then | 
| flickerstreak@52 | 457       for _, e in pairs(edges) do | 
| flickerstreak@52 | 458         local o = a.inside and e or oppositeEdges[e] | 
| flickerstreak@52 | 459         if CheckEdgeOverlap(f1,f2,e) then | 
| flickerstreak@52 | 460           local d = GetEdgeDistance(f1, f2, e, o) | 
| flickerstreak@52 | 461           if not dist or (d < dist) then | 
| flickerstreak@52 | 462             dist, edge, opp = d, e, o | 
| flickerstreak@52 | 463           end | 
| flickerstreak@52 | 464         end | 
| flickerstreak@52 | 465       end | 
| flickerstreak@52 | 466     end | 
| flickerstreak@52 | 467     return dist, edge, opp | 
| flickerstreak@52 | 468   end | 
| flickerstreak@52 | 469 | 
| flickerstreak@52 | 470   local function GetClosestVisibleEdge( f ) | 
| flickerstreak@52 | 471     local r, o, e1, e2 | 
| flickerstreak@52 | 472     local a = anchorOutside | 
| flickerstreak@63 | 473     for _, b in ReAction:IterateBars() do | 
| flickerstreak@52 | 474       local d, e, opp = GetClosestFrameEdge(f,b:GetFrame(),a) | 
| flickerstreak@52 | 475       if d and (not r or d < r) then | 
| flickerstreak@52 | 476         r, o, e1, e2 = d, b:GetFrame(), e, opp | 
| flickerstreak@52 | 477       end | 
| flickerstreak@52 | 478     end | 
| flickerstreak@52 | 479     for f2, a2 in pairs(globalSnapTargets) do | 
| flickerstreak@52 | 480       local d, e, opp = GetClosestFrameEdge(f,f2,a2) | 
| flickerstreak@52 | 481       if d and (not r or d < r) then | 
| flickerstreak@52 | 482         r, o, e1, e2, a = d, f2, e, opp, a2 | 
| flickerstreak@52 | 483       end | 
| flickerstreak@52 | 484     end | 
| flickerstreak@52 | 485     return o, e1, e2, a | 
| flickerstreak@52 | 486   end | 
| flickerstreak@52 | 487 | 
| flickerstreak@52 | 488   local function GetClosestVisiblePoint(f1) | 
| flickerstreak@52 | 489     local f2, e1, e2, a = GetClosestVisibleEdge(f1) | 
| flickerstreak@52 | 490     if f2 then | 
| flickerstreak@52 | 491       local rsq, p, rp, x, y | 
| flickerstreak@52 | 492       -- iterate pointsOnEdge in order and use < to prefer edge centers to corners | 
| flickerstreak@52 | 493       for _, p1 in ipairs(pointsOnEdge[e1]) do | 
| flickerstreak@52 | 494         if CheckPointEdgeOverlap(f1,p1,f2,e2) then | 
| flickerstreak@52 | 495           for _, p2 in pairs(snapPoints[a][p1]) do | 
| flickerstreak@52 | 496             local x1, y1 = GetPointCoords(f1,p1) | 
| flickerstreak@52 | 497             local x2, y2 = GetPointCoords(f2,p2) | 
| flickerstreak@52 | 498             local dx = x1 - x2 | 
| flickerstreak@52 | 499             local dy = y1 - y2 | 
| flickerstreak@52 | 500             local rsq2 = dx*dx + dy*dy | 
| flickerstreak@52 | 501             if not rsq or rsq2 < rsq then | 
| flickerstreak@52 | 502               rsq, p, rp, x, y = rsq2, p1, p2, dx, dy | 
| flickerstreak@52 | 503             end | 
| flickerstreak@52 | 504           end | 
| flickerstreak@52 | 505         end | 
| flickerstreak@52 | 506       end | 
| flickerstreak@52 | 507       return f2, p, rp, x, y | 
| flickerstreak@52 | 508     end | 
| flickerstreak@52 | 509   end | 
| flickerstreak@52 | 510 | 
| flickerstreak@52 | 511   local function GetClosestPointSnapped(f1, rx, ry, xOff, yOff) | 
| flickerstreak@52 | 512     local o, p, rp, x, y = GetClosestVisiblePoint(f1) | 
| flickerstreak@52 | 513     local s = false | 
| flickerstreak@52 | 514 | 
| flickerstreak@52 | 515     local sx, sy = insidePointOffsetFuncs[p](xOff or 0, yOff or 0) | 
| flickerstreak@52 | 516     local xx, yy = pointCoordFuncs[p](f1) | 
| flickerstreak@52 | 517     if xx and yy then | 
| flickerstreak@52 | 518       if math.abs(x) <= rx then | 
| flickerstreak@52 | 519         x = sx | 
| flickerstreak@52 | 520         s = true | 
| flickerstreak@52 | 521       end | 
| flickerstreak@52 | 522       if math.abs(y) <= ry then | 
| flickerstreak@52 | 523         y = sy | 
| flickerstreak@52 | 524         s = true | 
| flickerstreak@52 | 525       end | 
| flickerstreak@52 | 526     elseif xx then | 
| flickerstreak@52 | 527       if math.abs(x) <= rx then | 
| flickerstreak@52 | 528         x = sx | 
| flickerstreak@52 | 529         s = true | 
| flickerstreak@52 | 530         if math.abs(y) <= ry then | 
| flickerstreak@52 | 531           y = sy | 
| flickerstreak@52 | 532         end | 
| flickerstreak@52 | 533       end | 
| flickerstreak@52 | 534     elseif yy then | 
| flickerstreak@52 | 535       if math.abs(y) <= ry then | 
| flickerstreak@52 | 536         y = sy | 
| flickerstreak@52 | 537         s = true | 
| flickerstreak@52 | 538         if math.abs(x) <= rx then | 
| flickerstreak@52 | 539           x = sx | 
| flickerstreak@52 | 540         end | 
| flickerstreak@52 | 541       end | 
| flickerstreak@52 | 542     end | 
| flickerstreak@52 | 543 | 
| flickerstreak@52 | 544     if x == -0 then x = 0 end | 
| flickerstreak@52 | 545     if y == -0 then y = 0 end | 
| flickerstreak@52 | 546 | 
| flickerstreak@52 | 547     if s then | 
| flickerstreak@52 | 548       return o, p, rp, math.floor(x), math.floor(y) | 
| flickerstreak@52 | 549     end | 
| flickerstreak@52 | 550   end | 
| flickerstreak@52 | 551 | 
| flickerstreak@52 | 552   local function CreateSnapIndicator() | 
| flickerstreak@52 | 553     local si = CreateFrame("Frame",nil,UIParent) | 
| flickerstreak@52 | 554     si:SetFrameStrata("HIGH") | 
| flickerstreak@52 | 555     si:SetHeight(8) | 
| flickerstreak@52 | 556     si:SetWidth(8) | 
| flickerstreak@52 | 557     local tex = si:CreateTexture() | 
| flickerstreak@52 | 558     tex:SetAllPoints() | 
| flickerstreak@52 | 559     tex:SetTexture(1.0, 0.82, 0, 0.8) | 
| flickerstreak@52 | 560     tex:SetBlendMode("ADD") | 
| flickerstreak@52 | 561     tex:SetDrawLayer("OVERLAY") | 
| flickerstreak@52 | 562     return si | 
| flickerstreak@52 | 563   end | 
| flickerstreak@52 | 564 | 
| flickerstreak@52 | 565   local si1 = CreateSnapIndicator() | 
| flickerstreak@52 | 566   local si2 = CreateSnapIndicator() | 
| flickerstreak@52 | 567 | 
| flickerstreak@52 | 568   local function DisplaySnapIndicator( f, rx, ry, xOff, yOff ) | 
| flickerstreak@52 | 569     local o, p, rp, x, y, snap = GetClosestPointSnapped(f, rx, ry, xOff, yOff) | 
| flickerstreak@52 | 570     if o then | 
| flickerstreak@52 | 571       si1:ClearAllPoints() | 
| flickerstreak@52 | 572       si2:ClearAllPoints() | 
| flickerstreak@52 | 573       si1:SetPoint("CENTER", f, p, 0, 0) | 
| flickerstreak@52 | 574       local xx, yy = pointCoordFuncs[rp](o) | 
| flickerstreak@52 | 575       x = math.abs(x) <=rx and xx and 0 or x | 
| flickerstreak@52 | 576       y = math.abs(y) <=ry and yy and 0 or y | 
| flickerstreak@52 | 577       si2:SetPoint("CENTER", o, rp, x, y) | 
| flickerstreak@52 | 578       si1:Show() | 
| flickerstreak@52 | 579       si2:Show() | 
| flickerstreak@52 | 580     else | 
| flickerstreak@52 | 581       if si1:IsVisible() then | 
| flickerstreak@52 | 582         si1:Hide() | 
| flickerstreak@52 | 583         si2:Hide() | 
| flickerstreak@52 | 584       end | 
| flickerstreak@52 | 585     end | 
| flickerstreak@52 | 586   end | 
| flickerstreak@52 | 587 | 
| flickerstreak@52 | 588   local function HideSnapIndicator() | 
| flickerstreak@52 | 589     if si1:IsVisible() then | 
| flickerstreak@52 | 590       si1:Hide() | 
| flickerstreak@52 | 591       si2:Hide() | 
| flickerstreak@52 | 592     end | 
| flickerstreak@52 | 593   end | 
| flickerstreak@52 | 594 | 
| flickerstreak@33 | 595   CreateControls = function(bar) | 
| flickerstreak@33 | 596     local f = bar.frame | 
| flickerstreak@33 | 597 | 
| flickerstreak@33 | 598     f:SetMovable(true) | 
| flickerstreak@33 | 599     f:SetResizable(true) | 
| flickerstreak@33 | 600     f:SetClampedToScreen(true) | 
| flickerstreak@33 | 601 | 
| flickerstreak@33 | 602     -- buttons on the bar should be direct children of the bar frame. | 
| flickerstreak@33 | 603     -- The control elements need to float on top of this, which we could | 
| flickerstreak@33 | 604     -- do with SetFrameLevel() or Raise(), but it's more reliable to do it | 
| flickerstreak@33 | 605     -- via frame nesting, hence good old foo's appearance here. | 
| flickerstreak@33 | 606     local foo = CreateFrame("Frame",nil,f) | 
| flickerstreak@33 | 607     foo:SetAllPoints() | 
| flickerstreak@51 | 608     foo:SetClampedToScreen(true) | 
| flickerstreak@33 | 609 | 
| flickerstreak@33 | 610     local control = CreateFrame("Button", nil, foo) | 
| flickerstreak@33 | 611     control:EnableMouse(true) | 
| flickerstreak@33 | 612     control:SetToplevel(true) | 
| flickerstreak@33 | 613     control:SetPoint("TOPLEFT", -4, 4) | 
| flickerstreak@33 | 614     control:SetPoint("BOTTOMRIGHT", 4, -4) | 
| flickerstreak@33 | 615     control:SetBackdrop({ | 
| flickerstreak@33 | 616       edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", | 
| flickerstreak@33 | 617       tile = true, | 
| flickerstreak@33 | 618       tileSize = 16, | 
| flickerstreak@33 | 619       edgeSize = 16, | 
| flickerstreak@33 | 620       insets = { left = 0, right = 0, top = 0, bottom = 0 }, | 
| flickerstreak@33 | 621     }) | 
| flickerstreak@33 | 622 | 
| flickerstreak@33 | 623     -- textures | 
| flickerstreak@33 | 624     local bgTex = control:CreateTexture(nil,"BACKGROUND") | 
| flickerstreak@33 | 625     bgTex:SetTexture(0.7,0.7,1.0,0.2) | 
| flickerstreak@33 | 626     bgTex:SetPoint("TOPLEFT",4,-4) | 
| flickerstreak@33 | 627     bgTex:SetPoint("BOTTOMRIGHT",-4,4) | 
| flickerstreak@33 | 628     local hTex = control:CreateTexture(nil,"HIGHLIGHT") | 
| flickerstreak@33 | 629     hTex:SetTexture(0.7,0.7,1.0,0.2) | 
| flickerstreak@33 | 630     hTex:SetPoint("TOPLEFT",4,-4) | 
| flickerstreak@33 | 631     hTex:SetPoint("BOTTOMRIGHT",-4,4) | 
| flickerstreak@33 | 632     hTex:SetBlendMode("ADD") | 
| flickerstreak@33 | 633 | 
| flickerstreak@33 | 634     -- label | 
| flickerstreak@33 | 635     local label = control:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") | 
| flickerstreak@33 | 636     label:SetAllPoints() | 
| flickerstreak@33 | 637     label:SetJustifyH("CENTER") | 
| flickerstreak@33 | 638     label:SetShadowColor(0,0,0,1) | 
| flickerstreak@33 | 639     label:SetShadowOffset(2,-2) | 
| flickerstreak@33 | 640     label:SetTextColor(1,1,1,1) | 
| flickerstreak@33 | 641     label:SetText(bar:GetName()) | 
| flickerstreak@33 | 642     label:Show() | 
| flickerstreak@33 | 643     bar.controlLabelString = label  -- so that bar:SetName() can update it | 
| flickerstreak@33 | 644 | 
| flickerstreak@33 | 645     local StopResize = function() | 
| flickerstreak@33 | 646       f:StopMovingOrSizing() | 
| flickerstreak@33 | 647       f.isMoving = false | 
| flickerstreak@33 | 648       f:SetScript("OnUpdate",nil) | 
| flickerstreak@52 | 649       StoreSize(bar) | 
| flickerstreak@33 | 650       ClampToButtons(bar) | 
| flickerstreak@33 | 651       ApplyAnchor(bar) | 
| flickerstreak@63 | 652       ReAction:RefreshOptions() | 
| flickerstreak@33 | 653     end | 
| flickerstreak@33 | 654 | 
| flickerstreak@33 | 655     -- edge drag handles | 
| flickerstreak@33 | 656     for _, point in pairs({"LEFT","TOP","RIGHT","BOTTOM"}) do | 
| flickerstreak@33 | 657       local edge = CreateFrame("Frame",nil,control) | 
| flickerstreak@33 | 658       edge:EnableMouse(true) | 
| flickerstreak@33 | 659       edge:SetWidth(8) | 
| flickerstreak@33 | 660       edge:SetHeight(8) | 
| flickerstreak@33 | 661       if point == "TOP" or point == "BOTTOM" then | 
| flickerstreak@33 | 662         edge:SetPoint(point.."LEFT") | 
| flickerstreak@33 | 663         edge:SetPoint(point.."RIGHT") | 
| flickerstreak@33 | 664       else | 
| flickerstreak@33 | 665         edge:SetPoint("TOP"..point) | 
| flickerstreak@33 | 666         edge:SetPoint("BOTTOM"..point) | 
| flickerstreak@33 | 667       end | 
| flickerstreak@33 | 668       local tex = edge:CreateTexture(nil,"HIGHLIGHT") | 
| flickerstreak@33 | 669       tex:SetTexture(1.0,0.82,0,0.7) | 
| flickerstreak@33 | 670       tex:SetBlendMode("ADD") | 
| flickerstreak@33 | 671       tex:SetAllPoints() | 
| flickerstreak@33 | 672       edge:RegisterForDrag("LeftButton") | 
| flickerstreak@33 | 673       edge:SetScript("OnMouseDown", | 
| flickerstreak@33 | 674         function() | 
| flickerstreak@33 | 675           local bw, bh = GetButtonSize(bar) | 
| flickerstreak@33 | 676           local r, c, s = GetButtonGrid(bar) | 
| flickerstreak@33 | 677           f:SetMinResize( bw+s+1, bh+s+1 ) | 
| flickerstreak@33 | 678           f:StartSizing(point) | 
| flickerstreak@33 | 679           f:SetScript("OnUpdate", | 
| flickerstreak@33 | 680             function() | 
| flickerstreak@33 | 681               RecomputeGrid(bar) | 
| flickerstreak@33 | 682               bar:RefreshLayout() | 
| flickerstreak@33 | 683             end | 
| flickerstreak@33 | 684           ) | 
| flickerstreak@33 | 685         end | 
| flickerstreak@33 | 686       ) | 
| flickerstreak@33 | 687       edge:SetScript("OnMouseUp", StopResize) | 
| flickerstreak@33 | 688       edge:SetScript("OnEnter", | 
| flickerstreak@33 | 689         function() | 
| flickerstreak@33 | 690           GameTooltip:SetOwner(f, "ANCHOR_"..point) | 
| flickerstreak@33 | 691           GameTooltip:AddLine(L["Drag to add/remove buttons"]) | 
| flickerstreak@33 | 692           GameTooltip:Show() | 
| flickerstreak@33 | 693         end | 
| flickerstreak@33 | 694       ) | 
| flickerstreak@33 | 695       edge:SetScript("OnLeave", HideGameTooltip) | 
| flickerstreak@33 | 696       edge:Show() | 
| flickerstreak@33 | 697     end | 
| flickerstreak@33 | 698 | 
| flickerstreak@33 | 699     -- corner drag handles, again nested in an anonymous frame so that they are on top | 
| flickerstreak@33 | 700     local foo2 = CreateFrame("Frame",nil,control) | 
| flickerstreak@33 | 701     foo2:SetAllPoints(true) | 
| flickerstreak@33 | 702     for _, point in pairs({"BOTTOMLEFT","TOPLEFT","BOTTOMRIGHT","TOPRIGHT"}) do | 
| flickerstreak@33 | 703       local corner = CreateFrame("Frame",nil,foo2) | 
| flickerstreak@33 | 704       corner:EnableMouse(true) | 
| flickerstreak@33 | 705       corner:SetWidth(12) | 
| flickerstreak@33 | 706       corner:SetHeight(12) | 
| flickerstreak@33 | 707       corner:SetPoint(point) | 
| flickerstreak@33 | 708       local tex = corner:CreateTexture(nil,"HIGHLIGHT") | 
| flickerstreak@33 | 709       tex:SetTexture(1.0,0.82,0,0.7) | 
| flickerstreak@33 | 710       tex:SetBlendMode("ADD") | 
| flickerstreak@33 | 711       tex:SetAllPoints() | 
| flickerstreak@33 | 712       corner:RegisterForDrag("LeftButton","RightButton") | 
| flickerstreak@33 | 713       local updateTooltip = function() | 
| flickerstreak@33 | 714         local size, size2 = bar:GetButtonSize() | 
| flickerstreak@33 | 715         local rows, cols, spacing = bar:GetButtonGrid() | 
| flickerstreak@33 | 716         size = (size == size2) and tostring(size) or format("%dx%d",size,size2) | 
| flickerstreak@33 | 717         GameTooltipTextRight4:SetText(size) | 
| flickerstreak@33 | 718         GameTooltipTextRight5:SetText(tostring(spacing)) | 
| flickerstreak@33 | 719       end | 
| flickerstreak@33 | 720       corner:SetScript("OnMouseDown", | 
| flickerstreak@33 | 721         function(_,btn) | 
| flickerstreak@33 | 722           local bw, bh = GetButtonSize(bar) | 
| flickerstreak@33 | 723           local r, c, s = GetButtonGrid(bar) | 
| flickerstreak@33 | 724           if btn == "LeftButton" then -- button resize | 
| flickerstreak@33 | 725             f:SetMinResize( (s+12)*c+1, (s+12)*r+1 ) | 
| flickerstreak@33 | 726             f:SetScript("OnUpdate", | 
| flickerstreak@33 | 727               function() | 
| flickerstreak@33 | 728                 RecomputeButtonSize(bar) | 
| flickerstreak@33 | 729                 bar:RefreshLayout() | 
| flickerstreak@33 | 730                 updateTooltip() | 
| flickerstreak@33 | 731               end | 
| flickerstreak@33 | 732             ) | 
| flickerstreak@33 | 733           elseif btn == "RightButton" then -- spacing resize | 
| flickerstreak@33 | 734             f:SetMinResize( bw*c, bh*r ) | 
| flickerstreak@33 | 735             f:SetScript("OnUpdate", | 
| flickerstreak@33 | 736               function() | 
| flickerstreak@33 | 737                 RecomputeButtonSpacing(bar) | 
| flickerstreak@33 | 738                 bar:RefreshLayout() | 
| flickerstreak@33 | 739                 updateTooltip() | 
| flickerstreak@33 | 740               end | 
| flickerstreak@33 | 741             ) | 
| flickerstreak@33 | 742           end | 
| flickerstreak@33 | 743           f:StartSizing(point) | 
| flickerstreak@33 | 744         end | 
| flickerstreak@33 | 745       ) | 
| flickerstreak@33 | 746       corner:SetScript("OnMouseUp",StopResize) | 
| flickerstreak@33 | 747       corner:SetScript("OnEnter", | 
| flickerstreak@33 | 748         function() | 
| flickerstreak@33 | 749           GameTooltip:SetOwner(f, "ANCHOR_"..point) | 
| flickerstreak@33 | 750           GameTooltip:AddLine(L["Drag to resize buttons"]) | 
| flickerstreak@33 | 751           GameTooltip:AddLine(L["Right-click-drag"]) | 
| flickerstreak@33 | 752           GameTooltip:AddLine(L["to change spacing"]) | 
| flickerstreak@33 | 753           local size, size2 = bar:GetButtonSize() | 
| flickerstreak@33 | 754           local rows, cols, spacing = bar:GetButtonGrid() | 
| flickerstreak@33 | 755           size = (size == size2) and tostring(size) or format("%dx%d",size,size2) | 
| flickerstreak@33 | 756           GameTooltip:AddDoubleLine(L["Size:"], size) | 
| flickerstreak@33 | 757           GameTooltip:AddDoubleLine(L["Spacing:"], tostring(spacing)) | 
| flickerstreak@33 | 758           GameTooltip:Show() | 
| flickerstreak@33 | 759         end | 
| flickerstreak@33 | 760       ) | 
| flickerstreak@33 | 761       corner:SetScript("OnLeave", | 
| flickerstreak@33 | 762         function() | 
| flickerstreak@33 | 763           GameTooltip:Hide() | 
| flickerstreak@33 | 764           f:SetScript("OnUpdate",nil) | 
| flickerstreak@33 | 765         end | 
| flickerstreak@33 | 766       ) | 
| flickerstreak@33 | 767 | 
| flickerstreak@33 | 768     end | 
| flickerstreak@33 | 769 | 
| flickerstreak@33 | 770     control:RegisterForDrag("LeftButton") | 
| flickerstreak@33 | 771     control:RegisterForClicks("RightButtonDown") | 
| flickerstreak@33 | 772 | 
| flickerstreak@33 | 773     control:SetScript("OnDragStart", | 
| flickerstreak@33 | 774       function() | 
| flickerstreak@33 | 775         f:StartMoving() | 
| flickerstreak@33 | 776         f.isMoving = true | 
| flickerstreak@52 | 777         local w,h = bar:GetButtonSize() | 
| flickerstreak@52 | 778         f:ClearAllPoints() | 
| flickerstreak@52 | 779         f:SetScript("OnUpdate", function() | 
| flickerstreak@52 | 780             if IsShiftKeyDown() then | 
| flickerstreak@52 | 781               DisplaySnapIndicator(f,w,h) | 
| flickerstreak@52 | 782             else | 
| flickerstreak@52 | 783               HideSnapIndicator() | 
| flickerstreak@52 | 784             end | 
| flickerstreak@52 | 785           end) | 
| flickerstreak@33 | 786       end | 
| flickerstreak@33 | 787     ) | 
| flickerstreak@33 | 788 | 
| flickerstreak@52 | 789     local function updateDragTooltip() | 
| flickerstreak@52 | 790       GameTooltip:SetOwner(f, "ANCHOR_TOPRIGHT") | 
| flickerstreak@52 | 791       GameTooltip:AddLine(bar.name) | 
| flickerstreak@52 | 792       GameTooltip:AddLine(L["Drag to move"]) | 
| flickerstreak@52 | 793       GameTooltip:AddLine(("|cff00ff00%s|r %s"):format(L["Shift-drag"],L["to anchor to nearby frames"])) | 
| flickerstreak@52 | 794       GameTooltip:AddLine(("|cff00cccc%s|r %s"):format(L["Right-click"],L["for options"])) | 
| flickerstreak@52 | 795       local _, a = bar:GetAnchor() | 
| flickerstreak@52 | 796       if a and a ~= "UIParent" then | 
| flickerstreak@52 | 797         GameTooltip:AddLine(L["Currently anchored to <%s>"]:format(a)) | 
| flickerstreak@52 | 798       end | 
| flickerstreak@52 | 799       GameTooltip:Show() | 
| flickerstreak@52 | 800     end | 
| flickerstreak@52 | 801 | 
| flickerstreak@33 | 802     control:SetScript("OnDragStop", | 
| flickerstreak@33 | 803       function() | 
| flickerstreak@33 | 804         f:StopMovingOrSizing() | 
| flickerstreak@33 | 805         f.isMoving = false | 
| flickerstreak@33 | 806         f:SetScript("OnUpdate",nil) | 
| flickerstreak@52 | 807 | 
| flickerstreak@52 | 808         if IsShiftKeyDown() then | 
| flickerstreak@52 | 809           local w, h = bar:GetButtonSize() | 
| flickerstreak@52 | 810           local a, p, rp, x, y = GetClosestPointSnapped(f,w,h) | 
| flickerstreak@52 | 811           if a then | 
| flickerstreak@52 | 812             f:ClearAllPoints() | 
| flickerstreak@52 | 813             f:SetPoint(p,a,rp,x,y) | 
| flickerstreak@52 | 814           end | 
| flickerstreak@52 | 815           HideSnapIndicator() | 
| flickerstreak@52 | 816         end | 
| flickerstreak@52 | 817 | 
| flickerstreak@33 | 818         StoreExtents(bar) | 
| flickerstreak@63 | 819         ReAction:RefreshOptions() | 
| flickerstreak@52 | 820         updateDragTooltip() | 
| flickerstreak@33 | 821       end | 
| flickerstreak@33 | 822     ) | 
| flickerstreak@33 | 823 | 
| flickerstreak@33 | 824     control:SetScript("OnEnter", | 
| flickerstreak@33 | 825       function() | 
| flickerstreak@63 | 826         -- TODO: add bar type and status information to name | 
| flickerstreak@63 | 827         --[[ | 
| flickerstreak@33 | 828         local name = bar.name | 
| flickerstreak@33 | 829         for _, m in ReAction:IterateModules() do | 
| flickerstreak@33 | 830           local suffix = safecall(m,"GetBarNameModifier",bar) | 
| flickerstreak@33 | 831           if suffix then | 
| flickerstreak@33 | 832             name = ("%s %s"):format(name,suffix) | 
| flickerstreak@33 | 833           end | 
| flickerstreak@33 | 834         end | 
| flickerstreak@63 | 835         ]]-- | 
| flickerstreak@52 | 836 | 
| flickerstreak@52 | 837         updateDragTooltip() | 
| flickerstreak@33 | 838       end | 
| flickerstreak@33 | 839     ) | 
| flickerstreak@33 | 840 | 
| flickerstreak@33 | 841     control:SetScript("OnLeave", HideGameTooltip) | 
| flickerstreak@33 | 842 | 
| flickerstreak@33 | 843     control:SetScript("OnClick", | 
| flickerstreak@33 | 844       function() | 
| flickerstreak@33 | 845         bar:ShowMenu() | 
| flickerstreak@33 | 846       end | 
| flickerstreak@33 | 847     ) | 
| flickerstreak@33 | 848 | 
| flickerstreak@33 | 849     return control | 
| flickerstreak@33 | 850   end | 
| flickerstreak@33 | 851 end | 
| flickerstreak@33 | 852 | 
| flickerstreak@33 | 853 | 
| flickerstreak@33 | 854 local OpenMenu, CloseMenu | 
| flickerstreak@33 | 855 do | 
| flickerstreak@33 | 856   -- Looking for a lightweight AceConfig3-struct-compatible | 
| flickerstreak@33 | 857   -- replacement for Dewdrop, encapsulate here | 
| flickerstreak@33 | 858   -- Considering Blizzard's EasyMenu/UIDropDownMenu, but that's | 
| flickerstreak@33 | 859   -- a bit tricky to convert from AceConfig3-struct | 
| flickerstreak@33 | 860   local Dewdrop = AceLibrary("Dewdrop-2.0") | 
| flickerstreak@33 | 861   OpenMenu = function(frame, opts) | 
| flickerstreak@33 | 862     Dewdrop:Open(frame, "children", opts, "cursorX", true, "cursorY", true) | 
| flickerstreak@33 | 863   end | 
| flickerstreak@33 | 864   CloseMenu = function(frame) | 
| flickerstreak@33 | 865     if Dewdrop:GetOpenedParent() == frame then | 
| flickerstreak@33 | 866       Dewdrop:Close() | 
| flickerstreak@33 | 867     end | 
| flickerstreak@33 | 868   end | 
| flickerstreak@33 | 869 end | 
| flickerstreak@33 | 870 | 
| flickerstreak@33 | 871 | 
| flickerstreak@33 | 872 function Bar:ShowControls(show) | 
| flickerstreak@33 | 873   if show then | 
| flickerstreak@33 | 874     if not self.controlFrame then | 
| flickerstreak@33 | 875       self.controlFrame = CreateControls(self) | 
| flickerstreak@33 | 876     end | 
| flickerstreak@33 | 877     self.controlFrame:Show() | 
| flickerstreak@33 | 878   elseif self.controlFrame then | 
| flickerstreak@33 | 879     CloseMenu(self.controlFrame) | 
| flickerstreak@33 | 880     self.controlFrame:Hide() | 
| flickerstreak@33 | 881   end | 
| flickerstreak@33 | 882 end | 
| flickerstreak@33 | 883 | 
| flickerstreak@33 | 884 function Bar:ShowMenu() | 
| flickerstreak@33 | 885   if not self.menuOpts then | 
| flickerstreak@33 | 886     self.menuOpts = { | 
| flickerstreak@33 | 887       type = "group", | 
| flickerstreak@33 | 888       args = { | 
| flickerstreak@33 | 889         openConfig = { | 
| flickerstreak@33 | 890           type = "execute", | 
| flickerstreak@58 | 891           name = L["Settings..."], | 
| flickerstreak@58 | 892           desc = L["Open the editor for this bar"], | 
| flickerstreak@63 | 893           func = function() CloseMenu(self.controlFrame); ReAction:ShowEditor(self) end, | 
| flickerstreak@33 | 894           disabled = InCombatLockdown, | 
| flickerstreak@33 | 895           order = 1 | 
| flickerstreak@33 | 896         }, | 
| flickerstreak@33 | 897         delete = { | 
| flickerstreak@33 | 898           type = "execute", | 
| flickerstreak@33 | 899           name = L["Delete Bar"], | 
| flickerstreak@33 | 900           desc = L["Remove the bar from the current profile"], | 
| flickerstreak@50 | 901           confirm = L["Are you sure you want to remove this bar?"], | 
| flickerstreak@33 | 902           func = function() ReAction:EraseBar(self) end, | 
| flickerstreak@33 | 903           order = 2 | 
| flickerstreak@33 | 904         }, | 
| flickerstreak@33 | 905       } | 
| flickerstreak@33 | 906     } | 
| flickerstreak@33 | 907   end | 
| flickerstreak@33 | 908   OpenMenu(self.controlFrame, self.menuOpts) | 
| flickerstreak@33 | 909 end | 
| flickerstreak@33 | 910 | 
| flickerstreak@33 | 911 | 
| flickerstreak@33 | 912 | 
| flickerstreak@28 | 913 ------ Export as a class-factory ------ | 
| flickerstreak@28 | 914 ReAction.Bar = { | 
| flickerstreak@28 | 915   new = function(self, ...) | 
| flickerstreak@28 | 916     local x = { } | 
| flickerstreak@28 | 917     for k,v in pairs(Bar) do | 
| flickerstreak@28 | 918       x[k] = v | 
| flickerstreak@28 | 919     end | 
| flickerstreak@28 | 920     Constructor(x, ...) | 
| flickerstreak@28 | 921     return x | 
| flickerstreak@28 | 922   end | 
| flickerstreak@28 | 923 } |