Mercurial > wow > reaction
comparison classes/ReAnchor.lua @ 7:f920db5fc6b1
version 0.3
| author | Flick <flickerstreak@gmail.com> |
|---|---|
| date | Tue, 20 Mar 2007 21:25:29 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 6:2da5089ab7ff | 7:f920db5fc6b1 |
|---|---|
| 1 -- | |
| 2 -- ReAnchor.lua | |
| 3 -- | |
| 4 -- Provides drag-placement facilities for frames. | |
| 5 -- | |
| 6 | |
| 7 -- local constants | |
| 8 local AceOO = AceLibrary("AceOO-2.0") | |
| 9 | |
| 10 local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" } | |
| 11 | |
| 12 local pointsOnEdge = { | |
| 13 BOTTOM = { "BOTTOM", "BOTTOMLEFT", "BOTTOMRIGHT", }, | |
| 14 TOP = { "TOP", "TOPLEFT", "TOPRIGHT", }, | |
| 15 RIGHT = { "RIGHT", "BOTTOMRIGHT", "TOPRIGHT", }, | |
| 16 LEFT = { "LEFT", "BOTTOMLEFT", "TOPLEFT", }, | |
| 17 } | |
| 18 | |
| 19 local edgeSelector = { | |
| 20 BOTTOM = 1, -- select x of x,y | |
| 21 TOP = 1, -- select x of x,y | |
| 22 LEFT = 2, -- select y of x,y | |
| 23 RIGHT = 2, -- select y of x,y | |
| 24 } | |
| 25 | |
| 26 local oppositePoints = { | |
| 27 BOTTOMLEFT = "TOPRIGHT", | |
| 28 BOTTOM = "TOP", | |
| 29 BOTTOMRIGHT = "TOPLEFT", | |
| 30 RIGHT = "LEFT", | |
| 31 TOPRIGHT = "BOTTOMLEFT", | |
| 32 TOP = "BOTTOM", | |
| 33 TOPLEFT = "BOTTOMRIGHT", | |
| 34 LEFT = "RIGHT", | |
| 35 CENTER = "CENTER", | |
| 36 } | |
| 37 | |
| 38 local insidePointOffsetFuncs = { | |
| 39 BOTTOMLEFT = function(x, y) return x, y end, | |
| 40 BOTTOM = function(x, y) return 0, y end, | |
| 41 BOTTOMRIGHT = function(x, y) return -x, y end, | |
| 42 RIGHT = function(x, y) return -x, 0 end, | |
| 43 TOPRIGHT = function(x, y) return -x, -y end, | |
| 44 TOP = function(x, y) return 0, -y end, | |
| 45 TOPLEFT = function(x, y) return x, -y end, | |
| 46 LEFT = function(x, y) return x, 0 end, | |
| 47 CENTER = function(x, y) return 0, 0 end, | |
| 48 } | |
| 49 | |
| 50 local pointCoordFuncs = { | |
| 51 BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end, | |
| 52 BOTTOM = function(f) return nil, f:GetBottom() end, | |
| 53 BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end, | |
| 54 RIGHT = function(f) return f:GetRight(), nil end, | |
| 55 TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end, | |
| 56 TOP = function(f) return nil, f:GetTop() end, | |
| 57 TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end, | |
| 58 LEFT = function(f) return f:GetLeft(), nil end, | |
| 59 CENTER = function(f) return f:GetCenter() end, | |
| 60 } | |
| 61 | |
| 62 local edgeBoundsFuncs = { | |
| 63 BOTTOM = function(f) return f:GetLeft(), f:GetRight() end, | |
| 64 LEFT = function(f) return f:GetBottom(), f:GetTop() end | |
| 65 } | |
| 66 edgeBoundsFuncs.TOP = edgeBoundsFuncs.BOTTOM | |
| 67 edgeBoundsFuncs.RIGHT = edgeBoundsFuncs.LEFT | |
| 68 | |
| 69 | |
| 70 -- local utility functions | |
| 71 | |
| 72 -- Returns absolute coordinates x,y of the named point 'p' of frame 'f' | |
| 73 local function GetPointCoords( f, p ) | |
| 74 local x, y = pointCoordFuncs[p](f) | |
| 75 if not(x and y) then | |
| 76 local cx, cy = f:GetCenter() | |
| 77 x = x or cx | |
| 78 y = y or cy | |
| 79 end | |
| 80 return x, y | |
| 81 end | |
| 82 | |
| 83 | |
| 84 -- Returns true if frame 'f1' can be anchored to frame 'f2' | |
| 85 local function CheckAnchorable( f1, f2 ) | |
| 86 -- can't anchor a frame to itself or to nil | |
| 87 if f1 == f2 or f2 == nil then | |
| 88 return false | |
| 89 end | |
| 90 | |
| 91 -- can always anchor to UIParent | |
| 92 if f2 == UIParent then | |
| 93 return true | |
| 94 end | |
| 95 | |
| 96 -- also can't do circular anchoring of frames | |
| 97 -- walk the anchor chain, which generally shouldn't be that expensive | |
| 98 -- (who nests draggables that deep anyway?) | |
| 99 for i = 1, f2:GetNumPoints() do | |
| 100 local _, f = f2:GetPoint(i) | |
| 101 return CheckAnchorable(f1,f) | |
| 102 end | |
| 103 | |
| 104 return true | |
| 105 end | |
| 106 | |
| 107 -- Returns true if frames f1 and f2 specified edges overlap | |
| 108 local function CheckEdgeOverlap( f1, f2, e ) | |
| 109 local l1, u1 = edgeBoundsFuncs[e](f1) | |
| 110 local l2, u2 = edgeBoundsFuncs[e](f2) | |
| 111 return l1 <= l2 and l2 <= u1 or l2 <= l1 and l1 <= u2 | |
| 112 end | |
| 113 | |
| 114 -- Returns true if point p1 on frame f1 overlaps edge e2 on frame f2 | |
| 115 local function CheckPointEdgeOverlap( f1, p1, f2, e2 ) | |
| 116 local l, u = edgeBoundsFuncs[e2](f2) | |
| 117 local x, y = GetPointCoords(f1,p1) | |
| 118 x = select(edgeSelector[e2], x, y) | |
| 119 return l <= x and x <= u | |
| 120 end | |
| 121 | |
| 122 -- Returns the distance between corresponding edges. It is | |
| 123 -- assumed that the passed in edges e1 and e2 are the same or opposites | |
| 124 local function GetEdgeDistance( f1, f2, e1, e2 ) | |
| 125 local x1, y1 = pointCoordFuncs[e1](f1) | |
| 126 local x2, y2 = pointCoordFuncs[e2](f2) | |
| 127 return math.abs((x1 or y1) - (x2 or y2)) | |
| 128 end | |
| 129 | |
| 130 -- Returns interior offsets (specified absolutely) from a point | |
| 131 local function GetInteriorOffsetsToPoint(p, x, y) | |
| 132 return insidePointOffsetFuncs[p](x,y) | |
| 133 end | |
| 134 | |
| 135 | |
| 136 | |
| 137 | |
| 138 | |
| 139 -- ReAnchor is a Mixin which provides some anchoring and | |
| 140 -- placement methods for frames. | |
| 141 -- An object with the ReAnchor mixin must support the | |
| 142 -- IAnchorable interface (implicitly or explicitly). | |
| 143 -- The mixin methods also require arguments to support | |
| 144 -- that interface. | |
| 145 | |
| 146 -- In the method prototypes, 'IRObjs' is used to refer to a | |
| 147 -- table of objects which support the IAnchorable interface. | |
| 148 | |
| 149 | |
| 150 ReAnchor = AceOO.Mixin { | |
| 151 "GetClosestVisibleEdge", | |
| 152 "GetClosestVisiblePoint", | |
| 153 "GetClosestPointSnapped", | |
| 154 "DisplaySnapIndicator", | |
| 155 "HideSnapIndicator", | |
| 156 } | |
| 157 | |
| 158 --------------------------------------------------------- | |
| 159 -- Constants and classes that are not exported via mixin | |
| 160 --------------------------------------------------------- | |
| 161 ReAnchor.IAnchorable = AceOO.Interface { | |
| 162 GetFrame = "function", | |
| 163 GetAnchorage = "function", -- return ReAnchor.anchorInside or .anchorOutside | |
| 164 } | |
| 165 | |
| 166 ReAnchor.anchorInside = { inside = true } | |
| 167 ReAnchor.anchorOutside = { outside = true } | |
| 168 | |
| 169 ReAnchor.snapIndicator1 = CreateFrame("Frame",nil,UIParent,"ReAnchorSnapIndicatorTemplate") | |
| 170 ReAnchor.snapIndicator2 = CreateFrame("Frame",nil,UIParent,"ReAnchorSnapIndicatorTemplate") | |
| 171 | |
| 172 | |
| 173 | |
| 174 | |
| 175 -------------------- | |
| 176 -- Mixin methods | |
| 177 -------------------- | |
| 178 | |
| 179 -- returns: | |
| 180 -- (1) o : the closest IRObj | |
| 181 -- (2) e1 : the point (edge) on self:GetFrame() | |
| 182 -- (3) e2 : the point (edge) on o:GetFrame() | |
| 183 function ReAnchor:GetClosestVisibleEdge( IRObjs ) | |
| 184 local f1 = self:GetFrame() | |
| 185 local r, o, e1, e2 | |
| 186 for _, o2 in pairs(IRObjs) do | |
| 187 local f2 = o2:GetFrame() | |
| 188 local a = o2:GetAnchorage() | |
| 189 if f2:IsVisible() and CheckAnchorable(f1,f2) then | |
| 190 for _, e in pairs(edges) do | |
| 191 local opp = a.inside and e or oppositePoints[e] | |
| 192 if CheckEdgeOverlap(f1,f2,e) then | |
| 193 local d = GetEdgeDistance(f1, f2, e, opp) | |
| 194 if not r or d < r then | |
| 195 r, o, e1, e2 = d, o2, e, opp | |
| 196 end | |
| 197 end | |
| 198 end | |
| 199 end | |
| 200 end | |
| 201 return o, e1, e2 | |
| 202 end | |
| 203 | |
| 204 -- returns: | |
| 205 -- (1) o: the closest IRObj | |
| 206 -- (1) p: the point on self:GetFrame() | |
| 207 -- (2) rp: the relativePoint on o:GetFrame() | |
| 208 -- (3) x: x offset | |
| 209 -- (4) y: y offset | |
| 210 -- such that self:GetFrame():SetPoint(p,o:GetFrame(),rp,x,y) preserves the current location | |
| 211 function ReAnchor:GetClosestVisiblePoint( IRObjs ) | |
| 212 local f1 = self:GetFrame() | |
| 213 local o, e1, e2 = self:GetClosestVisibleEdge( IRObjs ) | |
| 214 local f2 = o:GetFrame() | |
| 215 local rsq, p, rp, x, y | |
| 216 -- iterate pointsOnEdge in order and use < to prefer edge centers to corners | |
| 217 for _, p1 in ipairs(pointsOnEdge[e1]) do | |
| 218 if CheckPointEdgeOverlap(f1,p1,f2,e2) then | |
| 219 local p2 = o:GetAnchorage().outside and oppositePoints[p1] or p1 | |
| 220 local x1, y1 = GetPointCoords(f1,p1) | |
| 221 local x2, y2 = GetPointCoords(f2,p2) | |
| 222 local dx = x1 - x2 | |
| 223 local dy = y1 - y2 | |
| 224 local rsq2 = dx*dx + dy*dy | |
| 225 if not rsq or rsq2 < rsq then | |
| 226 rsq, p, rp, x, y = rsq2, p1, p2, dx, dy | |
| 227 end | |
| 228 end | |
| 229 end | |
| 230 return o, p, rp, x, y | |
| 231 end | |
| 232 | |
| 233 | |
| 234 -- Calls self:GetClosestVisiblePoint() and then snaps to the specified | |
| 235 -- offsets if within the given range in x and y (as appropriate) | |
| 236 -- Return semantic is the same as GetClosestVisiblePoint(). Returns nil | |
| 237 -- if no snap can be done. | |
| 238 function ReAnchor:GetClosestPointSnapped(IRObjs, r, xOff, yOff) | |
| 239 local f1 = self:GetFrame() | |
| 240 local o, p, rp, x, y = self:GetClosestVisiblePoint(IRObjs) | |
| 241 local s = false | |
| 242 | |
| 243 if r then | |
| 244 local sx, sy = GetInteriorOffsetsToPoint(p, xOff or 0, yOff or 0) | |
| 245 local xx, yy = pointCoordFuncs[p](f1) | |
| 246 if xx and yy then | |
| 247 if math.abs(x) <= r then | |
| 248 x = sx | |
| 249 s = true | |
| 250 end | |
| 251 if math.abs(y) <= r then | |
| 252 y = sy | |
| 253 s = true | |
| 254 end | |
| 255 elseif xx then | |
| 256 if math.abs(x) <= r then | |
| 257 x = sx | |
| 258 s = true | |
| 259 if math.abs(y) <= r then | |
| 260 y = sy | |
| 261 end | |
| 262 end | |
| 263 elseif yy then | |
| 264 if math.abs(y) <= r then | |
| 265 y = sy | |
| 266 s = true | |
| 267 if math.abs(x) <= r then | |
| 268 x = sx | |
| 269 end | |
| 270 end | |
| 271 end | |
| 272 end | |
| 273 | |
| 274 if s then | |
| 275 return o, p, rp, x, y | |
| 276 end | |
| 277 end | |
| 278 | |
| 279 | |
| 280 | |
| 281 -- shows anchor-indicators on the associated frame and the target frame | |
| 282 -- when a snap is warranted. | |
| 283 function ReAnchor:DisplaySnapIndicator( IRObjs, r, xOff, yOff ) | |
| 284 local o, p, rp, x, y, snap = self:GetClosestPointSnapped(IRObjs, r, xOff, yOff) | |
| 285 local si1 = ReAnchor.snapIndicator1 | |
| 286 local si2 = ReAnchor.snapIndicator2 | |
| 287 if o then | |
| 288 si1:ClearAllPoints() | |
| 289 si2:ClearAllPoints() | |
| 290 si1:SetPoint("CENTER", self:GetFrame(), p, 0, 0) | |
| 291 local xx, yy = pointCoordFuncs[rp](o:GetFrame()) | |
| 292 x = math.abs(x) <=r and xx and 0 or x | |
| 293 y = math.abs(y) <=r and yy and 0 or y | |
| 294 si2:SetPoint("CENTER", o:GetFrame(), rp, x, y) | |
| 295 si1:Show() | |
| 296 si2:Show() | |
| 297 else | |
| 298 if si1:IsVisible() then | |
| 299 si1:Hide() | |
| 300 si2:Hide() | |
| 301 end | |
| 302 end | |
| 303 end | |
| 304 | |
| 305 | |
| 306 function ReAnchor:HideSnapIndicator() | |
| 307 local si1 = ReAnchor.snapIndicator1 | |
| 308 local si2 = ReAnchor.snapIndicator2 | |
| 309 if si1:IsVisible() then | |
| 310 si1:Hide() | |
| 311 si2:Hide() | |
| 312 end | |
| 313 end | |
| 314 |
