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