Mercurial > wow > reaction
view classes/Overlay.lua @ 208:443d2ea4be86
ReAction.lua cleanup: Fixed calls to DestroyBar()
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Thu, 18 Nov 2010 13:06:14 -0800 |
parents | 2e7a322e0195 |
children | c24ac8ee1e45 |
line wrap: on
line source
local addonName, addonTable = ... local ReAction = addonTable.ReAction local L = ReAction.L local LKB = ReAction.LKB local CreateFrame = CreateFrame local InCombatLockdown = InCombatLockdown local floor = math.floor local min = math.min local format = string.format local GameTooltip = GameTooltip local Bar = ReAction.Bar local GetSize = Bar.GetSize local SetSize = Bar.SetSize local GetButtonSize = Bar.GetButtonSize local GetButtonGrid = Bar.GetButtonGrid local SetButtonSize = Bar.SetButtonSize local SetButtonGrid = Bar.SetButtonGrid local ApplyAnchor = Bar.ApplyAnchor local GameTooltipTextRight1 = GameTooltipTextRight1 local GameTooltipTextRight2 = GameTooltipTextRight2 local GameTooltipTextRight3 = GameTooltipTextRight3 -- -- Wrap some of the bar manipulators to make them state-aware -- local function SetAnchor( bar, point, frame, relPoint, x, y ) local state = bar:GetSecureState() if state then local anchorstate = bar:GetStateProperty(state, "anchorEnable") if anchorstate then bar:SetStateProperty(state, "anchorFrame", frame) bar:SetStateProperty(state, "anchorPoint", point) bar:SetStateProperty(state, "anchorRelPoint", relPoint) bar:SetStateProperty(state, "anchorX", x or 0) bar:SetStateProperty(state, "anchorY", y or 0) bar:SetAnchor(bar:GetAnchor()) return end end bar:SetAnchor(point, frame, relPoint, x, y) end local function GetStateScale( bar ) local state = bar:GetSecureState() if state and bar:GetStateProperty(state, "enableScale") then return bar:GetStateProperty(state, "scale") end end local function SetStateScale( bar, scale ) local state = bar:GetSecureState() if state and bar:GetStateProperty(state, "enableScale") then bar:SetStateProperty(state, "scale", scale) end end -- -- Bar config overlay -- local function GetNormalTextColor() return 1.0, 1.0, 1.0, 1.0 end local function GetAnchoredTextColor() return 1.0, 1.0, 1.0, 1.0 end local function GetNormalBgColor() return 0.7, 0.7, 1.0, 0.3 end local function GetAnchoredBgColor() return 0.9, 0.2, 0.7, 0.3 end local function StoreSize(bar) local f = bar:GetFrame() SetSize( bar, f:GetWidth(), f:GetHeight() ) end local function StoreExtents(bar) local f = bar:GetFrame() local p, fr, rp, x, y = f:GetPoint(1) fr = fr and fr:GetName() or "UIParent" SetAnchor( bar, p, fr, rp, x, y ) SetSize( bar, f:GetWidth(), f:GetHeight() ) end local function RecomputeButtonSize(bar) local w, h = GetSize(bar) local bw, bh = GetButtonSize(bar) local r, c, s = GetButtonGrid(bar) local scaleW = (floor(w/c) - s) / bw local scaleH = (floor(h/r) - s) / bh local scale = min(scaleW, scaleH) SetButtonSize(bar, scale * bw, scale * bh, s) end local function ComputeBarScale(bar, overlay) local w, h = overlay:GetWidth() - 8, overlay:GetHeight() - 8 local bw, bh = GetButtonSize(bar) local r, c, s = GetButtonGrid(bar) local scaleW = w / (c*(bw+s)) local scaleH = h / (r*(bh+s)) local scale = min(scaleW, scaleH) if scale > 2.5 then scale = 2.5 elseif scale < 0.25 then scale = 0.25 end return scale end local function RecomputeButtonSpacing(bar) local w, h = GetSize(bar) local bw, bh = GetButtonSize(bar) local r, c, s = GetButtonGrid(bar) SetButtonGrid(bar,r,c,min(floor(w/c) - bw, floor(h/r) - bh)) end local function RecomputeGrid(bar) local w, h = GetSize(bar) local bw, bh = GetButtonSize(bar) local r, c, s = GetButtonGrid(bar) SetButtonGrid(bar, floor(h/(bh+s)), floor(w/(bw+s)), s) end local function ClampToButtons(bar) local bw, bh = GetButtonSize(bar) local r, c, s = GetButtonGrid(bar) SetSize(bar, (bw+s)*c + 1, (bh+s)*r + 1) end local function HideGameTooltip() GameTooltip:Hide() end local anchorInside = { inside = true } local anchorOutside = { outside = true } local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" } local oppositeEdges = { TOP = "BOTTOM", BOTTOM = "TOP", LEFT = "RIGHT", RIGHT = "LEFT" } 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 snapPoints = { [anchorOutside] = { BOTTOMLEFT = {"BOTTOMRIGHT","TOPLEFT","TOPRIGHT"}, BOTTOM = {"TOP"}, BOTTOMRIGHT = {"BOTTOMLEFT","TOPRIGHT","TOPLEFT"}, RIGHT = {"LEFT"}, TOPRIGHT = {"TOPLEFT","BOTTOMRIGHT","BOTTOMLEFT"}, TOP = {"BOTTOM"}, TOPLEFT = {"TOPRIGHT","BOTTOMLEFT","BOTTOMRIGHT"}, LEFT = {"RIGHT"}, CENTER = {"CENTER"} }, [anchorInside] = { BOTTOMLEFT = {"BOTTOMLEFT"}, BOTTOM = {"BOTTOM"}, BOTTOMRIGHT = {"BOTTOMRIGHT"}, RIGHT = {"RIGHT"}, TOPRIGHT = {"TOPRIGHT"}, TOP = {"TOP"}, TOPLEFT = {"TOPLEFT"}, LEFT = {"LEFT"}, 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 x, y 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 cornerTexCoords = { -- ULx, ULy, LLx, LLy, URx, URy, LRx, LRy TOPLEFT = { 1, 1, 1, 0, 0, 1, 0, 0 }, TOPRIGHT = { 1, 0, 0, 0, 1, 1, 0, 1 }, BOTTOMLEFT = { 0, 1, 1, 1, 0, 0, 1, 0 }, BOTTOMRIGHT = { 0, 0, 0, 1, 1, 0, 1, 1 }, } -- 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) if not f then f = f2:GetParent() end 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 local globalSnapTargets = { [UIParent] = anchorInside } local function GetClosestFrameEdge(f1,f2,a) local dist, edge, opp if f2:IsVisible() and CheckAnchorable(f1,f2) then for _, e in pairs(edges) do local o = a.inside and e or oppositeEdges[e] if CheckEdgeOverlap(f1,f2,e) then local d = GetEdgeDistance(f1, f2, e, o) if not dist or (d < dist) then dist, edge, opp = d, e, o end end end end return dist, edge, opp end local function GetClosestVisibleEdge( f ) local r, o, e1, e2 local a = anchorOutside for _, b in ReAction:IterateBars() do local d, e, opp = GetClosestFrameEdge(f,b:GetFrame(),a) if d and (not r or d < r) then r, o, e1, e2 = d, b:GetFrame(), e, opp end end for f2, a2 in pairs(globalSnapTargets) do local d, e, opp = GetClosestFrameEdge(f,f2,a2) if d and (not r or d < r) then r, o, e1, e2, a = d, f2, e, opp, a2 end end return o, e1, e2, a end local function GetClosestVisiblePoint(f1) local f2, e1, e2, a = GetClosestVisibleEdge(f1) if f2 then 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 for _, p2 in pairs(snapPoints[a][p1]) do 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 end return f2, p, rp, x, y end end local function GetClosestPointSnapped(f1, rx, ry, xOff, yOff) local o, p, rp, x, y = GetClosestVisiblePoint(f1) local s = false local insideOffsetFunc = p and insidePointOffsetFuncs[p] local coordFunc = p and pointCoordFuncs[p] if not insideOffsetFunc or not coordFunc then return end local sx, sy = insideOffsetFunc(xOff or 0, yOff or 0) local xx, yy = coordFunc(f1) if xx and yy then if math.abs(x) <= rx then if math.abs(y) <= ry then x = sx y = sy s = true elseif CheckEdgeOverlap(f1,o,"LEFT") then x = sx s = true end elseif math.abs(y) <= ry and CheckEdgeOverlap(f1,o,"TOP") then y = sy s = true end elseif xx then if math.abs(x) <= rx then x = sx s = true if math.abs(y) <= ry then y = sy end end elseif yy then if math.abs(y) <= ry then y = sy s = true if math.abs(x) <= rx then x = sx end end end -- correct for some Lua oddities with doubles if x == -0 then x = 0 end if y == -0 then y = 0 end if s then return o, p, rp, math.floor(x), math.floor(y) end end local function CreateSnapIndicator() local si = CreateFrame("Frame",nil,UIParent) si:SetFrameStrata("HIGH") si:SetHeight(16) si:SetWidth(16) local tex = si:CreateTexture() tex:SetAllPoints() tex:SetTexture("Interface\\AddOns\\ReAction\\img\\lock") tex:SetBlendMode("ADD") tex:SetDrawLayer("OVERLAY") return si end local si1 = CreateSnapIndicator() local si2 = CreateSnapIndicator() local function DisplaySnapIndicator( f, rx, ry, xOff, yOff ) local o, p, rp, x, y, snap = GetClosestPointSnapped(f, rx, ry, xOff, yOff) if o then si1:ClearAllPoints() si2:ClearAllPoints() si1:SetPoint("CENTER", f, p, 0, 0) local xx, yy = pointCoordFuncs[rp](o) x = math.abs(x) <=rx and xx and 0 or x y = math.abs(y) <=ry and yy and 0 or y si2:SetPoint("CENTER", o, rp, x, y) si1:Show() si2:Show() else if si1:IsVisible() then si1:Hide() si2:Hide() end end return o, p end local function HideSnapIndicator() if si1:IsVisible() then si1:Hide() si2:Hide() end end local function UpdateLabelString(overlay) local label = overlay.labelString if label then local name = overlay.labelName if name and overlay.labelSubtext then name = format("%s (%s)", name, overlay.labelSubtext) end label:SetText(name or "") end end local function CreateControls(bar) local f = bar:GetFrame() f:SetMovable(true) f:SetResizable(true) -- child of UIParent so that alpha and scale doesn't propagate to it local overlay = CreateFrame("Button", nil, UIParent) overlay:EnableMouse(true) overlay:SetFrameLevel(10) -- set it above the buttons overlay:SetPoint("TOPLEFT", f, "TOPLEFT", -4, 4) overlay:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 4, -4) overlay:SetBackdrop({ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 0, right = 0, top = 0, bottom = 0 }, }) -- textures local bgTex = overlay:CreateTexture(nil,"BACKGROUND") bgTex:SetTexture(0.7,0.7,1.0,0.2) bgTex:SetPoint("TOPLEFT",4,-4) bgTex:SetPoint("BOTTOMRIGHT",-4,4) local hTex = overlay:CreateTexture(nil,"HIGHLIGHT") hTex:SetTexture(0.7,0.7,1.0,0.2) hTex:SetPoint("TOPLEFT",4,-4) hTex:SetPoint("BOTTOMRIGHT",-4,4) hTex:SetBlendMode("ADD") local aTex = overlay:CreateTexture(nil,"ARTWORK") aTex:SetTexture("Interface\\AddOns\\ReAction\\img\\lock") aTex:SetWidth(16) aTex:SetHeight(16) aTex:Hide() -- label local label = overlay:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") label:SetAllPoints() label:SetJustifyH("CENTER") label:SetShadowColor(0,0,0,1) label:SetShadowOffset(3,-3) label:SetTextColor(GetNormalTextColor()) label:SetText(bar:GetName()) label:Show() overlay.labelString = label overlay.labelName = bar:GetName() local function UpdateAnchorDecoration() local point, anchor, relPoint, x, y = f:GetPoint(1) if point then local ofsx, ofsy = insidePointOffsetFuncs[point](x,y) if (anchor and anchor ~= UIParent) or (ofsx == 0 and ofsy == 0) then bgTex:SetTexture( GetAnchoredBgColor() ) hTex:SetTexture( GetAnchoredBgColor() ) label:SetTextColor( GetAnchoredTextColor() ) aTex:ClearAllPoints() aTex:SetPoint(point) aTex:Show() return end end bgTex:SetTexture( GetNormalBgColor() ) hTex:SetTexture( GetNormalBgColor() ) label:SetTextColor( GetNormalTextColor() ) aTex:Hide() end local function StopResize() f:StopMovingOrSizing() f.isMoving = false f:SetScript("OnUpdate",nil) StoreSize(bar) ClampToButtons(bar) ReAction:RefreshEditor() end local function CornerUpdate() local bw, bh = GetButtonSize(bar) local r, c, s = GetButtonGrid(bar) local ss = GetStateScale(bar) if IsShiftKeyDown() then if ss then f:SetMinResize( ((s+bw)*c*0.25)/ss, ((s+bh)*r*0.25)/ss ) f:SetMaxResize( ((s+bw)*c*2.5 + 1)/ss, ((s+bh)*r*2.5 + 1)/ss ) scale = ComputeBarScale(bar, overlay) else f:SetMinResize( (s+12)*c+1, (s+12)*r+1 ) f:SetMaxResize( (s+128)*c+1, (s+128)*r+1 ) RecomputeButtonSize(bar) end elseif not ss and IsAltKeyDown() then f:SetMinResize( bw*c, bh*r ) f:SetMaxResize( 2*bw*c, 2*bh*r ) RecomputeButtonSpacing(bar) else f:SetMinResize( bw+s+1, bh+s+1 ) f:SetMaxResize( 50*(bw+s)+1, 50*(bh+s)+1 ) RecomputeGrid(bar) end GameTooltipTextRight2:SetText(format("%d x %d",r,c)) local ss = GetStateScale(bar) if ss then GameTooltipTextRight4:SetText(format("%d%%", scale*100)) else local size = (bw == bh) and tostring(bw) or format("%d x %d",bw,bh) GameTooltipTextRight3:SetText(size) GameTooltipTextRight4:SetText(tostring(s)) end end -- corner drag handles for _, point in pairs({"BOTTOMLEFT","TOPLEFT","BOTTOMRIGHT","TOPRIGHT"}) do local corner = CreateFrame("Frame",nil,overlay) corner:EnableMouse(true) corner:SetWidth(16) corner:SetHeight(16) corner:SetPoint(point) local tex = corner:CreateTexture(nil,"HIGHLIGHT") tex:SetTexture("Interface\\AddOns\\ReAction\\img\\corner") tex:SetTexCoord(unpack(cornerTexCoords[point])) tex:SetBlendMode("ADD") tex:SetAlpha(0.6) tex:SetAllPoints() corner:SetScript("OnMouseDown", function(_,btn) f:SetScript("OnUpdate", CornerUpdate) f:StartSizing(point) end ) corner:SetScript("OnMouseUp", function() local ss = GetStateScale(bar) if ss then SetStateScale(bar, ComputeBarScale(bar, overlay)) end StopResize() end) corner:SetScript("OnEnter", function() local bw, bh = GetButtonSize(bar) local r, c, s = bar:GetButtonGrid() local size = (bw == bh) and tostring(bw) or format("%d x %d",bw,bh) local ss = GetStateScale(bar) local state = bar:GetSecureState() GameTooltip:SetOwner(f, "ANCHOR_"..point) if ss then GameTooltip:AddLine(format("%s (%s: %s)", bar:GetName(), L["State"], state)) else GameTooltip:AddLine(bar:GetName()) end GameTooltip:AddDoubleLine(format("|cffcccccc%s|r %s",L["Drag"],L["to add/remove buttons:"]), format("%d x %d",r,c)) if ss then GameTooltip:AddLine(L["State Scale Override"]) GameTooltip:AddDoubleLine(format("|cff00ff00%s|r %s",L["Hold Shift"],L["to change scale:"]), format("%d%%", bar:GetStateProperty(state,"scale")*100)) else GameTooltip:AddDoubleLine(format("|cff00ff00%s|r %s",L["Hold Shift"],L["to resize buttons:"]), tostring(floor(size))) GameTooltip:AddDoubleLine(format("|cff0033cc%s|r %s",L["Hold Alt"], L["to change spacing:"]), tostring(floor(s))) end GameTooltip:Show() end ) corner:SetScript("OnLeave", function() GameTooltip:Hide() f:SetScript("OnUpdate",nil) end ) end overlay:RegisterForDrag("LeftButton") overlay:RegisterForClicks("RightButtonUp") overlay:SetScript("OnDragStart", function() f:StartMoving() f.isMoving = true local w,h = bar:GetButtonSize() f:ClearAllPoints() UpdateAnchorDecoration() f:SetScript("OnUpdate", function() if IsShiftKeyDown() then local f, p = DisplaySnapIndicator(f,w,h) else HideSnapIndicator() end end) end ) local function UpdateDragTooltip() GameTooltip:SetOwner(f, "ANCHOR_TOPRIGHT") local ss = GetStateScale(bar) local state = bar:GetSecureState() if ss then GameTooltip:AddLine(format("%s (%s: %s)", bar:GetName(), L["State"], state)) else GameTooltip:AddLine(bar:GetName()) end GameTooltip:AddLine(format("|cffcccccc%s|r %s",L["Drag"],L["to move"])) GameTooltip:AddLine(format("|cff00ff00%s|r %s",L["Hold Shift"],L["to anchor to nearby frames"])) GameTooltip:AddLine(format("|cff00cccc%s|r %s",L["Right-click"],L["for options..."])) local point, frame, relpoint, x, y = bar:GetFrame():GetPoint(1) if point then local ofsx, ofsy = insidePointOffsetFuncs[point](x,y) if (frame and frame ~= UIParent) or (ofsx == 0 and ofsy == 0) then frame = frame or UIParent GameTooltip:AddLine(format("%s <%s>",L["Currently anchored to"],frame:GetName())) end end GameTooltip:Show() end overlay:SetScript("OnDragStop", function() f:StopMovingOrSizing() f.isMoving = false f:SetScript("OnUpdate",nil) if IsShiftKeyDown() then local w, h = bar:GetButtonSize() local a, p, rp, x, y = GetClosestPointSnapped(f,w,h) if a then f:ClearAllPoints() f:SetPoint(p,a,rp,x,y) end HideSnapIndicator() end StoreExtents(bar) ReAction:RefreshEditor() UpdateDragTooltip() UpdateAnchorDecoration() end ) overlay:SetScript("OnEnter", function() UpdateDragTooltip() end ) overlay:SetScript("OnLeave", HideGameTooltip) overlay:SetScript("OnClick", function() ReAction:ShowEditor(bar) end ) function overlay:LIBKEYBOUND_ENABLED(evt) self:SetFrameLevel(1) end function overlay:LIBKEYBOUND_DISABLED(evt) self:SetFrameLevel(10) end function overlay:RefreshControls() UpdateAnchorDecoration() end overlay:SetScript("OnShow", overlay.RefreshControls) LKB.RegisterCallback(overlay,"LIBKEYBOUND_ENABLED") LKB.RegisterCallback(overlay,"LIBKEYBOUND_DISABLED") if ReAction:GetKeybindMode() then overlay:SetFrameLevel(1) end UpdateLabelString(overlay) UpdateAnchorDecoration() return overlay end -- export methods to the Bar prototype Bar.Overlay = { } function Bar.Overlay:New( bar ) return setmetatable( {frame = CreateControls(bar)}, {__index=self} ) end function Bar.Overlay:SetLabel(name) self.frame.labelName = name UpdateLabelString(self.frame) end function Bar.Overlay:SetLabelSubtext(text) self.frame.labelSubtext = text UpdateLabelString(self.frame) end function Bar.Overlay:Show() self.frame:Show() end function Bar.Overlay:Hide() self.frame:Hide() end function Bar.Overlay:IsShown() return self.frame:IsShown() end function Bar.Overlay:RefreshControls() self.frame:RefreshControls() end