view Veneer.lua @ 88:b107b4df7eb6

- core:DynamicReanchor - top-down evaluation of clustered frames - core:InternalReanchor(module) - bottom-up evaluation of target and frames anchored to it
author Nenue
date Thu, 20 Oct 2016 04:08:11 -0400
parents 27db212af783
children 74e714637d6a
line wrap: on
line source
-- Veneer Custom Interface Framework
-- 1. vn OnLoad
-- 2. OnEvent where IsLoggedIn() == true
-- 3. Setup() where (not self.initialized)
-- 4. Update()
-- 5. Reanchor()

SLASH_VENEER1 = "/veneer"
SLASH_VENEER2 = "/vn"

SlashCmdList.VENEER = function(cmd)
end

VeneerCore = {
  Frames = {},
  ConfigLayers = {},
  FrameClusters = {},
  parserDepth = 0,
  pendingCalls = {},
}
VeneerHandlerMixin = {

  anchorPoint = 'CENTER', -- indicates the initial cluster group point
  --anchorPath = 'BOTTOM', -- indicates the point from which the frame is anchored in a cluster arrangement
  OnHide = function()
    Veneer:DynamicReanchor()
  end,
  OnShow = function(self)
    self:Reanchor()
    Veneer:StaticReanchor(self)
  end
}
VeneerAnimationMixin = {}
local print = DEVIAN_WORKSPACE and function(...) print('Veneer', ...) end or nop
local wipe = table.wipe

local defaults = {
  enableAll = true,
  enableModule = {
    BuffFrame = true,
  },
  BuffFrame = {
    width = 48,
    height = 48,
  }
}

local configMode
local anonID = 0
local tostring = tostring
local IsFrameHandle = IsFrameHandle
local GetAnonymousName = function(key)
  if not key then
    anonID = anonID + 1
    key = anonID
  end
  return 'VN' .. key
end
local GetTableName = function(table)
  return (IsFrameHandle(table) and table:GetName()) or tostring(table)
end

local OFFSET_PARALLELS = {
  TOP = {'LEFT', 'RIGHT', 'SetHeight'},
  BOTTOM = {'LEFT', 'RIGHT', 'SetHeight'},
  LEFT = {'TOP', 'BOTTOM', 'SetWidth'},
  RIGHT = {'TOP', 'BOTTOM', 'SetWidth'},
}
local ANCHOR_OFFSET_POINT = {
  TOP = 'BOTTOM',
  TOPLEFT = 'BOTTOMRIGHT',
  TOPRIGHT = 'BOTTOMLEFT',
  LEFT = 'RIGHT',
  RIGHT = 'LEFT',
  CENTER = 'CENTER',
  BOTTOM = 'TOP',
  BOTTOMRIGHT = 'TOPLEFT',
  BOTTOMLEFT = 'TOPRIGHT',
}
local ANCHOR_INSET_DELTA = {
  TOP = {0, -1},
  TOPLEFT = {1, -1},
  TOPRIGHT = {-1,-1},
  LEFT = {1, 0},
  BOTTOMLEFT = {1, 1},
  BOTTOM = {0, 1},
  BOTTOMRIGHT = {-1, 1},
  RIGHT = {-1, 0},
  CENTER = {0, 0},
}

function VeneerCore:print(...)
  local txt = '|cFFFFFF00Veneer|r:'
  for i = 1, select('#', ...) do
    txt = txt .. ' '.. tostring(select(i, ...))
  end

  DEFAULT_CHAT_FRAME:AddMessage(txt)
end

function VeneerCore:OnLoad()
  print('|cFFFFFF00Veneer!|r')
  self:RegisterEvent('ADDON_LOADED')
  self:RegisterEvent('PLAYER_LOGIN')

  self.DEVIAN_PNAME = 'Veneer'
  self:RegisterForDrag('LeftButton')


end

function VeneerCore:OnEvent(event, ...)
  if event == 'ADDON_LOADED' or event == 'PLAYER_LOGIN' then
    if IsLoggedIn() and not self.intialized then
      self:Setup()
      self:UnregisterEvent('ADDON_LOADED')
      self:UnregisterEvent('PLAYER_LOGIN')
      self:Reanchor()
      self:Update()
    end
  end
end

function VeneerCore:OnDragStart()
  self:StartMoving()
end


function VeneerCore:OnDragStop()
  self:StopMovingOrSizing()
end

function VeneerCore:Setup ()
  self.initialized = true
  if (not VeneerData) or (not VeneerData.version) then
    VeneerData = defaults
  end
  self.data = VeneerData
  self:ExecuteOnClusters(nil, 'Setup')
end


function VeneerCore:GetClusterFromArgs (...)
  local primaryAnchor
  local insertPosition
  local clusterTable = self.FrameClusters
  for i = 1, select('#', ...) do
    local arg = select(i, ...)
    local argType = type(arg)
    if argType == 'string' then
      if not primaryAnchor then
        primaryAnchor = arg
      end
      clusterTable[arg] = clusterTable[arg] or {}
      clusterTable = clusterTable[arg]
      print(string.rep(' ', i)..'anchor cluster', i, arg)
    elseif argType == 'boolean' then
      insertPosition = 1
    end
  end
  if not primaryAnchor then
    primaryAnchor = 'TOPLEFT'
  end
  if not insertPosition then
    insertPosition = #clusterTable + 1
  end


  return primaryAnchor, clusterTable, insertPosition
end

function VeneerCore:AddHandler(handler, ...)
  print('*** Adding handler:', handler.moduleName or handler:GetName())

  local anchorGroup, clusterTable, clusterIndex = self:GetClusterFromArgs(...)
  if clusterIndex == 1 then
    for i, frame in ipairs(clusterTable) do
      frame.clusterIndex = i + 1
    end
  end
  tinsert(clusterTable, clusterIndex, handler)
  print('cluster', anchorGroup, 'table', clusterTable, 'position', clusterIndex)


  handler.anchorCluster = clusterTable
  handler.anchorIndex = clusterIndex
  for k,v in pairs(VeneerHandlerMixin) do
    if not handler[k] then
      print(' * from mixin:', k)
      handler[k] = v
    end
  end
  if self.initialized then
    print('  -- doing initialization')
    if handler.Setup and not handler.initialized then
      handler:Setup()
      handler.initialized = true
    end
    self:InternalReanchor(handler)

  end
end

function VeneerCore:Reanchor()
  self:ExecuteOnClusters(nil, 'Reanchor')
  self:DynamicReanchor(self)
end

function VeneerCore:Update()
  self:ExecuteOnClusters(nil, 'Update')
  self:Reanchor()
end

-- updates anchor relations to and from the target handler
function VeneerCore:GetAnchor(...)

end

-- Evaluates frames visibility and chains them accordingly

function VeneerCore:DynamicReanchor(parent)
  parent = parent or self
  print('|cFF88FF00DynamicReanchor()')
  for anchorPoint, cluster in pairs(parent.FrameClusters) do
    local lastFrame
    for index, frame in ipairs(cluster) do
      print('  |cFF00FF00'..index, frame:GetName(), frame:IsVisible())
      if frame:IsVisible() then
        anchorPoint = frame.anchorPoint


        frame:ClearAllPoints()
        if lastFrame then
          frame:SetPoint(anchorPoint, lastFrame, ANCHOR_OFFSET_POINT[anchorPoint], 0, 0)
        else
          frame:SetPoint(anchorPoint, UIParent, anchorPoint, 0, 0)
        end
        lastFrame = frame
      end

    end
  end
end

-- Evaluates the current visibility state and re-anchors adjacent blocks accordingly
function VeneerCore:InternalReanchor(handler, printFunc)
  print('|cFF00FFFFVeneer:InternalReanchor('..handler:GetName()..')')
  local anchorPoint = handler.anchorPath or handler.anchorPoint
  local anchorParent, anchorTo = UIParent, anchorPoint
  local subPoint, subTo
  local nextFrame
  for index, frame in ipairs(handler.anchorCluster) do

    print('  |cFF00FF00'..index, frame:GetName(), frame:IsVisible())
    if frame:IsVisible() then
      if frame ~= handler then
        anchorParent = frame
        anchorTo = ANCHOR_OFFSET_POINT[anchorPoint]

      else
        nextFrame = handler.anchorCluster[index+1]
        if nextFrame then

          subPoint = nextFrame.anchorPath or nextFrame.anchorPoint
          subTo = ANCHOR_OFFSET_POINT[subPoint]
          nextFrame:ClearAllPoints()
          nextFrame:SetPoint(subPoint, handler, subTo, 0, 0)
          print(' -- pushing '..nextFrame:GetName()..' down the anchor chain', subPoint, subTo)
        end
        break
      end
    end
  end

  if handler:IsVisible() then
    handler:SetPoint(anchorPoint, anchorParent, anchorTo, 0, 0)
  else
    if anchorParent and nextFrame then
      nextFrame:SetPoint(subPoint, handler, subTo, 0, 0)
    end
  end


  print(handler.anchorPoint, anchorParent, anchorTo)
  if printFunc then
    printFunc('|cFF88FF00'..handler:GetName()..':SetPoint(', handler.anchorPoint, anchorParent, anchorTo)
  end
end

function VeneerCore:SlideBlock(frame, ...)

  local anchorPoint, parent, anchorTo, pX,pY = ...
  print('  |cFF0088FFSlide:|r', frame, 'to', parent, pX,pY)
  local qX, qY = pX, pY
  local bX, bY
  local dX, dY = 0, 0
  local aX, aY = frame:GetLeft(), frame:GetTop()
  local str = ''
  if not aX then
    dY = ((anchorPoint == 'TOP') and frame:GetHeight()) or (((anchorPoint == 'BOTTOM') and -frame:GetHeight()) or 0)
    dX = ((anchorPoint == 'LEFT') and -frame:GetWidth()) or (((anchorPoint == 'RIGHT') and frame:GetWidth()) or 0)
    qX = pX + dX
    qY = pY + dY
    aX, aY = qX, qY
    bX, bY = pX, pY
    str = '|cFFFFFF00relative|r'
  else
    frame:ClearAllPoints()

    bX, bY = frame:GetLeft(), frame:GetTop()
    dX, dY = (bX-aX), (bY-aY)

    str = '|cFFFFFF00existing|r'
  end

  if ((dX ~= 0) or (dY ~= 0)) and frame.BlockSlide then
    print('  |cFF00FF88Slide result:|r',str, dX, dY)

    frame:ClearAllPoints()
    frame:SetPoint(anchorPoint, parent, anchorTo, qX, qY)
    frame.BlockSlide.dX = dX
    frame.BlockSlide.dY = dY
    frame.BlockSlide.sourcePoint = {anchorPoint, parent, anchorTo, qX, qY}
    frame.BlockSlide.destPoint = {anchorPoint, parent, anchorTo, pX,pY}
    frame.BlockSlide.translation:SetOffset(dX, dY)
    frame.BlockSlide:Play()
    return
  end
end


function VeneerCore:ExecuteOnClusters(layer, method)
  self.parserDepth = self.parserDepth + 1
  if not layer then
    if self.parserDepth > 1 then
      tinsert(self.pendingCalls, method)
      print('delaying walk for', method)
      return
    end
    print('|cFF00FF00Veneer:ExecuteOnClusters|r('..tostring(layer)..', '..method..')')
  else
    print(' Level '..self.parserDepth)
  end

  layer = layer or self.FrameClusters
  for anchor, cluster in pairs(layer) do
    for index, frame in ipairs(cluster) do
      print(' '..anchor..'.'..index..' = '..frame:GetName())
      if frame[method] then
        print('  |cFF00FF00'..frame:GetName())
        frame[method](frame, true)
      end
    end
    if cluster.FrameClusters then
      self:ExecuteOnClusters(cluster.FrameClusters, method)
    end
  end
  self.parserDepth = self.parserDepth - 1

  if (self.parserDepth == 0) and (#self.pendingCalls >= 1) then
    local delayedMethod = tremove(self.pendingCalls, 1)
    print('starting delayed walk for', delayedMethod)
    self:ExecuteOnClusters(nil, delayedMethod)
  end
end

local VeneerButton_OnDragStart = function(self)
  self.startingLeft = self:GetLeft()
  self.startingBottom = self:GetBottom()
  self.anchors = self.anchors or {}
  table.wipe(self.anchors)

  local frame = self:GetParent()
  local n = frame:GetNumPoints()
  for i = 1, n do
    local anchor, parent, relative, x, y = frame:GetPoint(i)
    self.anchors[i] = {
      anchor = anchor,
      parent = parent,
      relative = relative,
      x = x,
      y = y
    }
  end

  print(self:GetName(), 'start moving', self.startingLeft, self.startingBottom)
  self:StartMoving()
end

local VeneerButton_OnDragStop =  function(self)
  self:StopMovingOrSizing()
  if self.OnDragStop then
    self.OnDragStop(self)
  else
    local frame = self:GetParent()
    local dx = self:GetLeft() - self.startingLeft
    local dy = self:GetBottom() - self.startingBottom

    frame:ClearAllPoints()
    for i, point in ipairs(self.anchors) do
      frame:SetPoint(point.anchor, point.parent, point.relative, point.x + dx, point.y + dy)
      print('adjusting anchor', point.anchor, point.parent, point.relative, point.x + dx, point.y + dy)
    end
  end
end

local Veneer_FixMovers = function()
  for frame, veneer in pairs(veneers) do
    if veneer:IsMoving() then
      VeneerButton_OnDragStop(veneer)
    end
  end
end

local VeneerButton_Update = function(self)
  if configMode then
    self:SetScript('OnDragStart', VeneerButton_OnDragStart)
    self:SetScript('OnDragStop', VeneerButton_OnDragStop)
    self:SetMovable(true)
    self:EnableMouse(true)
    self:RegisterForDrag('LeftButton')

    self.bg:SetColorTexture(0,1,0,0.5)
    for i, region in ipairs(self.configLayers) do
      region:Show()
    end
    self:Show()
  else

    self:SetScript('OnDragStart', self.StartMoving)
    self:SetScript('OnDragStop', self.StopMovingOrSizing)
    self:SetMovable(false)
    self:EnableMouse(false)

    self.bg:SetColorTexture(0,1,0,0)
    for i, region in ipairs(self.configLayers) do
      region:Hide()
    end
    if self.isHidden then
      self:Hide()
    end

  end
end

local ToggleVeneerConfig = function()
  if configMode then
    configMode = false
    Veneer:print('Config mode off.')
  else
    configMode = true
    Veneer:print('Config mode on.')
  end

  for frame, veneer in pairs(veneers) do
    VeneerButton_Update(veneer)
  end
end

local VeneerButton_OnShow = function(self)
  VeneerButton_Update(self)
end

-- Takes frame handle and assigns a block to it
function VeneerCore:Acquire (frame, template)
  if not frame then
    print('|cFFFF4400Unable to acquire frame...|r')
    return
  end
  local veneer = self.Frames[frame]
  if not veneer then
    local name = type(frame) == 'table' and GetTableName(frame) or GetAnonymousName()
    veneer = CreateFrame('Frame', name, frame, template or 'VeneerTemplate')
    print('+veneer', name)

    veneer:SetAllPoints(frame)
    veneer:SetParent(frame)
    veneer.label:SetText(name)
    veneer.bg:SetColorTexture(0,0,0,0)
    veneer:Hide()
    veneer:EnableMouse(false)

    veneer:SetScript('OnShow', VeneerButton_OnShow)

    -- find current X/Y
    veneer.currentLeft = frame:GetLeft()
    veneer.currentTop = frame:GetTop()
    self.Frames[frame] = veneer
  end

  return veneer
end

function VeneerHandlerMixin:Reanchor (anchorAll)
  if not anchorAll then
    Veneer:InternalReanchor(self)
  end

end

function VeneerAnimationMixin:OnPlay()
  PlaySoundKitID(229)
  print('|cFF00FF00Anim:OnPlay|r @', unpack(self.sourcePoint))
end
function VeneerAnimationMixin:OnStop()
  PlaySoundKitID(229)
end
function VeneerAnimationMixin:OnFinished()
  PlaySoundKitID(229)
  print('|cFF00FF00Anim:OnFinish|r @', unpack(self.destPoint))
  self:GetParent():ClearAllPoints()
  self:GetParent():SetPoint(unpack(self.destPoint))
end