Mercurial > wow > reaction
diff classes/ReAnchor.lua @ 7:f920db5fc6b1
version 0.3
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Tue, 20 Mar 2007 21:25:29 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/classes/ReAnchor.lua Tue Mar 20 21:25:29 2007 +0000 @@ -0,0 +1,314 @@ +-- +-- ReAnchor.lua +-- +-- Provides drag-placement facilities for frames. +-- + +-- local constants +local AceOO = AceLibrary("AceOO-2.0") + +local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" } + +local pointsOnEdge = { + BOTTOM = { "BOTTOM", "BOTTOMLEFT", "BOTTOMRIGHT", }, + TOP = { "TOP", "TOPLEFT", "TOPRIGHT", }, + RIGHT = { "RIGHT", "BOTTOMRIGHT", "TOPRIGHT", }, + LEFT = { "LEFT", "BOTTOMLEFT", "TOPLEFT", }, +} + +local edgeSelector = { + BOTTOM = 1, -- select x of x,y + TOP = 1, -- select x of x,y + LEFT = 2, -- select y of x,y + RIGHT = 2, -- select y of x,y +} + +local oppositePoints = { + BOTTOMLEFT = "TOPRIGHT", + BOTTOM = "TOP", + BOTTOMRIGHT = "TOPLEFT", + RIGHT = "LEFT", + TOPRIGHT = "BOTTOMLEFT", + TOP = "BOTTOM", + TOPLEFT = "BOTTOMRIGHT", + LEFT = "RIGHT", + CENTER = "CENTER", +} + +local insidePointOffsetFuncs = { + BOTTOMLEFT = function(x, y) return x, y end, + BOTTOM = function(x, y) return 0, y end, + BOTTOMRIGHT = function(x, y) return -x, y end, + RIGHT = function(x, y) return -x, 0 end, + TOPRIGHT = function(x, y) return -x, -y end, + TOP = function(x, y) return 0, -y end, + TOPLEFT = function(x, y) return x, -y end, + LEFT = function(x, y) return x, 0 end, + CENTER = function(x, y) return 0, 0 end, +} + +local pointCoordFuncs = { + BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end, + BOTTOM = function(f) return nil, f:GetBottom() end, + BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end, + RIGHT = function(f) return f:GetRight(), nil end, + TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end, + TOP = function(f) return nil, f:GetTop() end, + TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end, + LEFT = function(f) return f:GetLeft(), nil end, + CENTER = function(f) return f:GetCenter() end, +} + +local edgeBoundsFuncs = { + BOTTOM = function(f) return f:GetLeft(), f:GetRight() end, + LEFT = function(f) return f:GetBottom(), f:GetTop() end +} +edgeBoundsFuncs.TOP = edgeBoundsFuncs.BOTTOM +edgeBoundsFuncs.RIGHT = edgeBoundsFuncs.LEFT + + +-- local utility functions + +-- Returns absolute coordinates x,y of the named point 'p' of frame 'f' +local function GetPointCoords( f, p ) + local x, y = pointCoordFuncs[p](f) + if not(x and y) then + local cx, cy = f:GetCenter() + x = x or cx + y = y or cy + end + return x, y +end + + +-- Returns true if frame 'f1' can be anchored to frame 'f2' +local function CheckAnchorable( f1, f2 ) + -- can't anchor a frame to itself or to nil + if f1 == f2 or f2 == nil then + return false + end + + -- can always anchor to UIParent + if f2 == UIParent then + return true + end + + -- also can't do circular anchoring of frames + -- walk the anchor chain, which generally shouldn't be that expensive + -- (who nests draggables that deep anyway?) + for i = 1, f2:GetNumPoints() do + local _, f = f2:GetPoint(i) + return CheckAnchorable(f1,f) + end + + return true +end + +-- Returns true if frames f1 and f2 specified edges overlap +local function CheckEdgeOverlap( f1, f2, e ) + local l1, u1 = edgeBoundsFuncs[e](f1) + local l2, u2 = edgeBoundsFuncs[e](f2) + return l1 <= l2 and l2 <= u1 or l2 <= l1 and l1 <= u2 +end + +-- Returns true if point p1 on frame f1 overlaps edge e2 on frame f2 +local function CheckPointEdgeOverlap( f1, p1, f2, e2 ) + local l, u = edgeBoundsFuncs[e2](f2) + local x, y = GetPointCoords(f1,p1) + x = select(edgeSelector[e2], x, y) + return l <= x and x <= u +end + +-- Returns the distance between corresponding edges. It is +-- assumed that the passed in edges e1 and e2 are the same or opposites +local function GetEdgeDistance( f1, f2, e1, e2 ) + local x1, y1 = pointCoordFuncs[e1](f1) + local x2, y2 = pointCoordFuncs[e2](f2) + return math.abs((x1 or y1) - (x2 or y2)) +end + +-- Returns interior offsets (specified absolutely) from a point +local function GetInteriorOffsetsToPoint(p, x, y) + return insidePointOffsetFuncs[p](x,y) +end + + + + + +-- ReAnchor is a Mixin which provides some anchoring and +-- placement methods for frames. +-- An object with the ReAnchor mixin must support the +-- IAnchorable interface (implicitly or explicitly). +-- The mixin methods also require arguments to support +-- that interface. + +-- In the method prototypes, 'IRObjs' is used to refer to a +-- table of objects which support the IAnchorable interface. + + +ReAnchor = AceOO.Mixin { + "GetClosestVisibleEdge", + "GetClosestVisiblePoint", + "GetClosestPointSnapped", + "DisplaySnapIndicator", + "HideSnapIndicator", +} + +--------------------------------------------------------- +-- Constants and classes that are not exported via mixin +--------------------------------------------------------- +ReAnchor.IAnchorable = AceOO.Interface { + GetFrame = "function", + GetAnchorage = "function", -- return ReAnchor.anchorInside or .anchorOutside +} + +ReAnchor.anchorInside = { inside = true } +ReAnchor.anchorOutside = { outside = true } + +ReAnchor.snapIndicator1 = CreateFrame("Frame",nil,UIParent,"ReAnchorSnapIndicatorTemplate") +ReAnchor.snapIndicator2 = CreateFrame("Frame",nil,UIParent,"ReAnchorSnapIndicatorTemplate") + + + + +-------------------- +-- Mixin methods +-------------------- + +-- returns: +-- (1) o : the closest IRObj +-- (2) e1 : the point (edge) on self:GetFrame() +-- (3) e2 : the point (edge) on o:GetFrame() +function ReAnchor:GetClosestVisibleEdge( IRObjs ) + local f1 = self:GetFrame() + local r, o, e1, e2 + for _, o2 in pairs(IRObjs) do + local f2 = o2:GetFrame() + local a = o2:GetAnchorage() + if f2:IsVisible() and CheckAnchorable(f1,f2) then + for _, e in pairs(edges) do + local opp = a.inside and e or oppositePoints[e] + if CheckEdgeOverlap(f1,f2,e) then + local d = GetEdgeDistance(f1, f2, e, opp) + if not r or d < r then + r, o, e1, e2 = d, o2, e, opp + end + end + end + end + end + return o, e1, e2 +end + +-- returns: +-- (1) o: the closest IRObj +-- (1) p: the point on self:GetFrame() +-- (2) rp: the relativePoint on o:GetFrame() +-- (3) x: x offset +-- (4) y: y offset +-- such that self:GetFrame():SetPoint(p,o:GetFrame(),rp,x,y) preserves the current location +function ReAnchor:GetClosestVisiblePoint( IRObjs ) + local f1 = self:GetFrame() + local o, e1, e2 = self:GetClosestVisibleEdge( IRObjs ) + local f2 = o:GetFrame() + local rsq, p, rp, x, y + -- iterate pointsOnEdge in order and use < to prefer edge centers to corners + for _, p1 in ipairs(pointsOnEdge[e1]) do + if CheckPointEdgeOverlap(f1,p1,f2,e2) then + local p2 = o:GetAnchorage().outside and oppositePoints[p1] or p1 + local x1, y1 = GetPointCoords(f1,p1) + local x2, y2 = GetPointCoords(f2,p2) + local dx = x1 - x2 + local dy = y1 - y2 + local rsq2 = dx*dx + dy*dy + if not rsq or rsq2 < rsq then + rsq, p, rp, x, y = rsq2, p1, p2, dx, dy + end + end + end + return o, p, rp, x, y +end + + +-- Calls self:GetClosestVisiblePoint() and then snaps to the specified +-- offsets if within the given range in x and y (as appropriate) +-- Return semantic is the same as GetClosestVisiblePoint(). Returns nil +-- if no snap can be done. +function ReAnchor:GetClosestPointSnapped(IRObjs, r, xOff, yOff) + local f1 = self:GetFrame() + local o, p, rp, x, y = self:GetClosestVisiblePoint(IRObjs) + local s = false + + if r then + local sx, sy = GetInteriorOffsetsToPoint(p, xOff or 0, yOff or 0) + local xx, yy = pointCoordFuncs[p](f1) + if xx and yy then + if math.abs(x) <= r then + x = sx + s = true + end + if math.abs(y) <= r then + y = sy + s = true + end + elseif xx then + if math.abs(x) <= r then + x = sx + s = true + if math.abs(y) <= r then + y = sy + end + end + elseif yy then + if math.abs(y) <= r then + y = sy + s = true + if math.abs(x) <= r then + x = sx + end + end + end + end + + if s then + return o, p, rp, x, y + end +end + + + +-- shows anchor-indicators on the associated frame and the target frame +-- when a snap is warranted. +function ReAnchor:DisplaySnapIndicator( IRObjs, r, xOff, yOff ) + local o, p, rp, x, y, snap = self:GetClosestPointSnapped(IRObjs, r, xOff, yOff) + local si1 = ReAnchor.snapIndicator1 + local si2 = ReAnchor.snapIndicator2 + if o then + si1:ClearAllPoints() + si2:ClearAllPoints() + si1:SetPoint("CENTER", self:GetFrame(), p, 0, 0) + local xx, yy = pointCoordFuncs[rp](o:GetFrame()) + x = math.abs(x) <=r and xx and 0 or x + y = math.abs(y) <=r and yy and 0 or y + si2:SetPoint("CENTER", o:GetFrame(), rp, x, y) + si1:Show() + si2:Show() + else + if si1:IsVisible() then + si1:Hide() + si2:Hide() + end + end +end + + +function ReAnchor:HideSnapIndicator() + local si1 = ReAnchor.snapIndicator1 + local si2 = ReAnchor.snapIndicator2 + if si1:IsVisible() then + si1:Hide() + si2:Hide() + end +end +