annotate classes/ReAnchor.lua @ 7:f920db5fc6b1

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