view Overlay.lua @ 100:9715174ff220

removed unimplemented stuff
author Flick <flickerstreak@gmail.com>
date Fri, 24 Oct 2008 23:37:24 +0000
parents f200bcb193d6
children 890e4c4ab143
line wrap: on
line source
local ReAction               = ReAction
local L                      = ReAction.L
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 GetButtonSize          = Bar.GetButtonSize
local GetButtonGrid          = Bar.GetButtonGrid
local SetSize                = Bar.SetSize
local SetAnchor              = Bar.SetAnchor
local SetButtonSize          = Bar.SetButtonSize
local SetButtonGrid          = Bar.SetButtonGrid
local ApplyAnchor            = Bar.ApplyAnchor
local GameTooltipTextRight1  = GameTooltipTextRight1
local GameTooltipTextRight2  = GameTooltipTextRight2
local GameTooltipTextRight3  = GameTooltipTextRight3

local KB = LibStub("LibKeyBound-1.0")

ReAction:UpdateRevision("$Revision$")

--
-- 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 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 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(bar)
  local label = bar.controlLabelString
  if label then
    local name = bar.labelName
    if name and bar.labelSubtext then
      name = format("%s (%s)", name, bar.labelSubtext)
    end
    label:SetText(name or "")
  end
end

local function CreateControls(bar)
  local f = bar:GetFrame()

  f:SetMovable(true)
  f:SetResizable(true)

  local overlay = CreateFrame("Button", nil, f)
  overlay:EnableMouse(true)
  overlay:SetFrameLevel(3) -- set it above the buttons
  overlay:SetPoint("TOPLEFT", -4, 4)
  overlay:SetPoint("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()
  bar.controlLabelString = label  -- so that bar:SetLabel() can update it

  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)
    --ApplyAnchor(bar)
    ReAction:RefreshOptions()
  end

  local function CornerUpdate()
    local bw, bh = GetButtonSize(bar)
    local r, c, s = GetButtonGrid(bar)
    if IsShiftKeyDown() then
      f:SetMinResize( (s+12)*c+1, (s+12)*r+1 )
      RecomputeButtonSize(bar)
    elseif IsAltKeyDown() then
      f:SetMinResize( bw*c, bh*r )
      RecomputeButtonSpacing(bar)
    else
      f:SetMinResize( bw+s+1, bh+s+1 )
      RecomputeGrid(bar)
    end
    local size = (bw == bh) and tostring(bw) or format("%d x %d",bw,bh)
    GameTooltipTextRight1:SetText(format("%d x %d",r,c))
    GameTooltipTextRight2:SetText(size)
    GameTooltipTextRight3:SetText(tostring(s))
  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:RegisterForDrag("LeftButton")
    
    corner:SetScript("OnMouseDown",
      function(_,btn)
        f:SetScript("OnUpdate", CornerUpdate)
        f:StartSizing(point)
      end
    )
    corner:SetScript("OnMouseUp",StopResize)
    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)
        GameTooltip:SetOwner(f, "ANCHOR_"..point)
        GameTooltip:AddDoubleLine(format("|cffcccccc%s|r %s",L["Drag"],L["to add/remove buttons:"]), format("%d x %d",r,c))
        GameTooltip:AddDoubleLine(format("|cff00ff00%s|r %s",L["Hold Shift"],L["to resize buttons:"]), size)
        GameTooltip:AddDoubleLine(format("|cff0033cc%s|r %s",L["Hold Alt"],L["to change spacing:"]), tostring(s))
        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")
    GameTooltip:AddLine(bar.name)
    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, anchor, relpoint, x, y = bar:GetAnchor()
    if point then
      local ofsx, ofsy = insidePointOffsetFuncs[point](x,y)
      if (anchor and anchor ~= "UIParent") or (ofsx == 0 and ofsy == 0) then
        --anchor = anchor or "UIParent"
        GameTooltip:AddLine(format("%s <%s>",L["Currently anchored to"],anchor))
      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:RefreshOptions()
      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(3)
  end

  KB.RegisterCallback(overlay,"LIBKEYBOUND_ENABLED")
  KB.RegisterCallback(overlay,"LIBKEYBOUND_DISABLED")

  if ReAction:GetKeybindMode() then
    overlay:SetFrameLevel(1)
  end

  bar:SetLabel(bar:GetName())
  UpdateLabelString(bar)
  UpdateAnchorDecoration()

  return overlay
end


-- export methods to the Bar prototype

function Bar:ShowControls(show)
  local f = self.controlFrame
  if show then
    if not f then
      f = CreateControls(self)
      self.controlFrame = f
    end
    f:Show()
  elseif f then
    f:Hide()
  end
end

function Bar:SetLabel(name)
  self.labelName = name
  UpdateLabelString(self)
end

function Bar:SetLabelSubtext(text)
  self.labelSubtext = text
  UpdateLabelString(self)
end