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