view Veneer.lua @ 56:159855c2e9ae

Added tag v1.0-release for changeset d7655c4e6e06
author Nenue
date Fri, 10 Jun 2016 20:53:22 -0400
parents dd9b5f59632c
children f253baf6022d
line wrap: on
line source
--------------------------------------------
-- Veneer
-- Core
-- author: Krakyn
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 4/27/2016 1:02 AM
--------------------------------------------
--- Implemented methods
-- OnInitialize
-- OnUpdate
-- OnEnable     -- runs as soon as GetSpecialization() returns valid data

local ADDON, A = ...
local wipe, min, max, random, tinsert, tremove = table.wipe, math.min, math.max, math.random, table.insert, table.remove
local pairs, ipairs, select, unpack, _G = pairs, ipairs, select, unpack, _G
local type, tostring, format = type, tostring, string.format

--- Establish presence
Veneer = Veneer or CreateFrame('Frame', 'Veneer', UIParent)
local V = Veneer
A.frame = V

--- Work variables
local modules = {}        -- module collector
local queuedModules = {}  -- indicates modules that were encountered out of dependency order
local checkForConfig = {} -- indicates frames created from XML that use their own namespace for position control
local moduleStack = {}    -- dictates the order in which module methods are fired
local initOnced           -- internal check for doing bottom-up SV retrieval

--- Utilities
V.wipeall = function (...)
  for i = 1, select('#', ...) do
    wipe(select(i, ...))
  end
end

--- Various region categories
V.displays = {}
V.configLayers = {}
V.configLayersRef = {}


--- Returns a debug hook for adding generic module information to each message
-- @usage func = V.print(sig)
-- @param sig channel name or number
local debugstack = _G.debugstack
local Devian = _G.Devian
local printfuncs = {}
V.print = function(pref, ...)
  if Devian and Devian.InWorkspace() then
      printfuncs[pref] = printfuncs[pref] or function(...) print(pref,  ...) end

    return printfuncs[pref]
  else
   return function () end
  end
end

--@debug@
local rgb = {}
local getcolor = function()
  local n, p = 0, 4
  for i = 1, 3 do
    rgb[i] = min(random(n,p) * 64, 255)
    if rgb[i] == 255 then
      p = 4
    elseif rgb[i] > 0 then
      n = 2
    end
  end
  return unpack(rgb)
end

local color = {}
local fprints = {}
--- Attempts to generate a debug printer based on the local scope. Results vary by where the originator was invoked.
V.fprint = function()
  if not (Devian and Devian.InWorkspace()) then
    return function() end
  end

  local sig = debugstack(2,1)
  if fprints[sig] then
    return fprints[sig]
  end

  local func = sig:match("%`(%a+)%'")
  if not func then
    func = sig:match("<(.-)>")
  end
  func = func:gsub('(%l+)(%u)', function(a, b) return a:sub(0,2) .. b  end, 1)
  func = func:gsub('^.+%\\', '')
  if not func then
    func = 'noname'
  end

  local r, g, b = getcolor()
  color[sig] = color[sig] or format('|cFF%02X%02X%02X%s|r', r, g, b, func)

  --print(color[func] .. ' ( ' .. table.concat(args, ', ')..' )' )
  func = V.print(func)
  fprints[sig] = func
  return func
end

--@end-debug@
--[=[@non-debug@
V.print = function() end
--@end-non-debug@]=]

-- for the Mikk script
-- GLOBALS: NUM_LE_RAID_BUFF_TYPES
-- GLOBALS: BUFF_FLASH_TIME_ON, BUFF_FLASH_TIME_OFF, BUFF_MIN_ALPHA, BUFF_WARNING_TIME, BUFF_DURATION_WARNING_TIME
-- GLOBALS: BUFFS_PER_ROW, BUFF_MAX_DISPLAY, BUFF_ACTUAL_DISPLAY, DEBUFF_MAX_DISPLAY, DEBUFF_ACTUAL_DISPLAY, BUFF_ROW_SPACING
-- GLOBALS: CONSOLIDATED_BUFFS_PER_ROW, CONSOLIDATED_BUFF_ROW_HEIGHT, NUM_TEMP_ENCHANT_FRAMES
-- GLOBALS: BUFF_BUTTON_HEIGHT, BUFF_FRAME_BASE_EXTENT, BUFF_HORIZ_SPACING

local print = V.print('Bfl')

--- Template for making perpendicular traversals of the displays structure; also makes sure the table is there
local setmetatable = setmetatable
V.Abstract = function(dest, key, table)
  if table then
    for _, v in pairs(dest) do
      v[key] = {}
    end
  end
  V[key] = setmetatable({}, {
    __index = function(t, k)
      return dest[k][key]
    end,
    __newindex = function(_, k, v)
      print('abstract write ('..key..'):', k)
      dest[k][key] = v
    end,
    __tostring = function() return 'Abstract:'..key..'' end
  })


  return V[key]
end


--- internal
local ModulesCall = function(func, flag)
  local n = 0
  for i = 1, #moduleStack do
    print('calling level '..i)
    local stackset = moduleStack[i]
    for name, module in pairs(stackset) do
      n = n + 1
      if module[func] then
        -- nil = pass
        if not flag or (module.Conf and module.Conf[flag]) then
          if (flag) then
            print('  check', flag, '=', module.Conf[flag])
          end

          print(' ',n..'  '..name..'.'..func..'()')
          module[func](module, module.Conf)
        end
      end
    end
  end
end


local Enable = function()
end

--- The things that happen repeatedly
local Init = function ()
end


local layers, refs, displays = V.configLayers, V.configLayersRef, V.displays
--- Things that happen immediately upon entering world
local InitOnce = function()
  print('entering world first time')
  local defaults = {}
  print('|cFFFFFF00Veneer|r')
  if not VeneerData then
    VeneerData = {}
    for k,v in pairs(defaults) do
      VeneerData[k] = v
    end
    print('Veneer defaults being used.')
  end
  V.Conf = setmetatable(VeneerData, {__index = function(_, k) return defaults[k] end})

  -- To ensure that modules are run in controlled order, walk the dependency list; if the dep shows up
  -- in the loaded manifest, remove the value. If the dep list isn't empty, move that module to the next
  -- layer.
  local loaded = {}
  local stackLevels = #moduleStack
  local i = 1
  moduleStack[1] = modules
  repeat
    print('setting init level '.. i)
    local queue = moduleStack[i]
    for name, module in pairs(queue) do

      if queuedModules[name] and #queuedModules[name] > 0 then
        local p = #queuedModules[name]
        for j = 1, p do
          local dep = queuedModules[name][j]

          if loaded[dep] then
            print( '    ' .. dep .. ' OK')
            queuedModules[name][j] = nil
            for k = j, p do
              print('   shift ' .. (k+1)  .. ' ('..tostring(queuedModules[name][k+1])..') to ' .. k ..'')
              queuedModules[name][k] = queuedModules[name][k+1]
            end
          end
        end

        if #queuedModules[name] == 0 then
          queuedModules[name] = nil
          print('  |cFF00FFFF'.. name ..'|r deps OK')
          loaded[name] = true
        else

          print('  |cFFFF8800' .. name ..'|r pending')
          local next = i+1
          if not  moduleStack[next] then
            moduleStack[next] = {}
          end
          stackLevels = next
          moduleStack[next][name] = module
          queue[name] = nil
        end

      else
        print('  |cFF00FF00'.. name ..'|r no deps')
        loaded[name] = true
      end
   end
    i = i + 1
  until i > stackLevels

  for level, batch in ipairs(moduleStack) do
    print('config level', level)
    for name, module in pairs(batch) do
      if not VeneerData[name] then
        VeneerData[name] = {}
      end

      if module.defaults then
        print('setting defaults for module', name)
        --[===[@non-debug@
        if not VeneerData[name] then
        --@end-non-debug@]===]
          VeneerData[name] = {}
        --[===[@non-debug@
        end
        --@end-non-debug@]===]
        for k,v in pairs(module.defaults) do
          VeneerData[name][k] = v
        end
        module.Conf = VeneerData[name]
      end

      if VeneerData[name].enabled == nil then
        VeneerData[name].enabled = true
      end

    end
  end

  --- Pull in any XML templates
  if #checkForConfig >= 1 then
    local queuedFrame = tremove(checkForConfig)
    while queuedFrame do
      V.SetConfigLayers(queuedFrame)
      V.UpdateXMLFrame(queuedFrame)
      queuedFrame = tremove(checkForConfig)
    end
  end
end

--- Fires an update to all modules
local lastUpdate
function V.UpdateAll(...)
  lastUpdate = GetTime()
  ModulesCall('OnUpdate')
end

V:RegisterEvent('PLAYER_ENTERING_WORLD')
V:SetScript('OnEvent', function(self, event)
  if event == 'PLAYER_ENTERING_WORLD' then
    if not initOnced then
      InitOnce()
      ModulesCall('OnInitialize')
      initOnced = true
      C_Timer.After(1, function()
        if GetSpecialization() then
          print(GetSpecialization(), 'enabling')

          ModulesCall('OnEnable', 'enabled')
          V:SetScript('OnUpdate', nil)
        end
      end)
    end
  end

  V.UpdateAll()

  if event == 'PLAYER_ENTERING_WORLD' then
    V.UpdateConfigLayers()
  end

end)

--- Modulizer method
--
function V:RegisterModule (name, module, ...)
  if modules[name] then
    print('pulling modules[|cFFFF8800'.. tostring(name) ..'|r]')
    return modules[name]
  end

  print('new module |cFF00BBFF'.. tostring(name) ..'|r')
  if module then
    if modules[name] then
      error("Module table for '"..tostring(name).."' already exists.")
    end
  else
    module = CreateFrame('Frame', 'Veneer' .. tostring(name) .. 'Handler', V, 'VeneerHandlerTemplate')
  end
  modules[name] = module
  V[name] = module
  if select('#', ...) >= 1 then
    local numDeps = select('#', ...)
    print('  '..numDeps..' deps detected')
    for i = 1, numDeps do
      local dep = select(i, ...)
        -- means that init/enable funcs are ordered to run after deps do their things
      queuedModules[name] = queuedModules[name]  or {}
      tinsert(queuedModules[name], dep)
      print('  needs '..dep)
    end
  end
  return module
end


V.SetConfigLayers =  function(frame)
  local print = V.fprint()
  if not frame.config then
    --print(frame:GetName(), 'has no config layers')
    return
  end
  --print('Registering config layers from', frame:GetName())

  for i, subframe in ipairs(frame.config) do
    -- make sure there are no duplicates
    if not refs[subframe] then
      local key = #layers+1
      layers[key] = subframe
      refs[subframe] = key
    end
    --print(' ', i, subframe:GetName())
  end
end

V.RemoveConfigLayers = function(frame)

  local print = V.fprint()
  print('|cFFFF0000RemoveConfigLayers', frame:GetName())
  for i, subframe in pairs(layers) do
    if subframe:GetParent() == frame then
      print('|cFFFF8800  ', subframe:GetParent():GetName(), '|cFFFFFF00', subframe:GetName())
      layers[i]:Hide()
      layers[i] = nil
      refs[subframe] = nil
    end
  end
end

V.ToggleGuideLayers = function()
  local print = V.fprint()
  local func = V.Conf.GuidesMode and 'Show' or 'Hide'
  local numAnchors = 0

  for id, region in pairs(layers) do
    --print(id, region:GetName(), func)
    region[func](region)
  end

  --print('['..func..'] updated', #layers, 'regions,', numAnchors, 'frames')
end
V.UpdateConfigLayers = function()
  print('|cFFFF0000', debugstack())
  V.ToggleGuideLayers()
end

local XMLFrame_Enable = function(self, value)
  local name = self:GetName()
  local print = V.print('XML')

  if not V.Conf[name] then
    V.Conf[name] = {
      enabled = true
    }
  end

  print()
  local enabled
  if value == nil then
    if V.Conf[name].enabled == nil then
      print('toggle based on visibility')
      enabled = (not self:IsVisible()) and true or false
    else
      print('toggle a config value =', V.Conf[name].enabled)
      enabled = V.Conf[name].enabled
    end

    enabled = (enabled ~= true) and true or false
  else
      print('use argument value', value)
      enabled = value
  end

  print('arg =', value, 'conf =', V.Conf[name].enabled, 'result=', enabled)

  V.Conf[name].enabled = enabled

  local stateFunc = enabled and 'Show' or 'Hide'
  local eventFunc = enabled and 'OnToggle' or 'OnToggle'
  --- taggled layers
  if self.toggled then
    for i, region in pairs(self.toggled) do
      region[stateFunc](region)
    end
  end
  --- toggle action
  if self.OnToggle then
    self:OnToggle(V.Conf[name].enabled)
  end
  --- do enable
  if V.Conf[name].enabled then
    if self.OnEnable then
      self:OnEnable()
    end
  else
    if self.OnDisable then
      self:OnDisable()
    end
  end
end
--- Generic handlers for keeping track of XML-defined frames
local print = V.print('XML')
local prototypes = {}
prototypes.OnDragStart = function(self)
  self.xA = self:GetLeft()
  self.yA = self:GetBottom()
  self.anchorTo, self.relativeTo, self.relativePoint, self.x, self.y = self:GetPoint(1)
  print('acquire anchor', self:GetPoint(1))
  print(self:GetName(), 'start moving ('..self.x..', '..self.y..')')
  self:StartMoving()
end

prototypes.OnDragStop = function(self)
  local name = self:GetName()
  print(name, 'stop moving ('..self:GetLeft()..', '..self:GetBottom()..')')
  local xB = self:GetLeft() - self.xA
  local yB = self:GetBottom() - self.yA
  print('storing anchor point', self.anchorTo, self.relativePoint, self.x + xB, self.y + yB)
  self:StopMovingOrSizing()
  V.Conf[name].position = {self.anchorTo, self.relativePoint, self.x + xB, self.y + yB}
  V.UpdateXMLFrame(self)
end


V.RegisterModuleFrame = function(self, moduleName)
  local name = self:GetName()
  tinsert(checkForConfig, self)
  self.Enable = XMLFrame_Enable
  self.moduleName = moduleName
  print('|cFF00FF00XML stuff related to '.. tostring(moduleName) .. ':', self:GetName())
  ------------------------------------------------------------------------------------------
  if not V[name] then
    return
  end

  local scriptTypes = {'OnUpdate', 'OnEvent', 'OnDragStart', 'OnDragStop'}
  for script in next(scriptTypes) do
    if V[name][script] then
      self:SetScript(script, V[name][script])
    end
  end

end
local XMLFrame_OnDragStart = function() end
local XMLFrame_OnDragStop = function() end

V.UpdateXMLFrame = function(self)
  local print = V.print('XML')

  local name = self:GetName()


  if self.drag then
    self:RegisterForDrag('LeftButton')
    self:SetScript('OnDragStart', prototypes.OnDragStart)
    if self.OnDragStop then
      self:SetScript('OnDragStop', function(self, ...)
        print('|cFFFF0088end of dragging').
        self:OnDragStop(self, ...)
        prototypes.OnDragStop(self, ...)
      end)
    else
      self:SetScript('OnDragStop', prototypes.OnDragStop)
    end
  else
    self:EnableMouse(false)
  end

  -- establish internal storage
  if not V.Conf[name] then
    V.Conf[name] = {
      enabled = self.enabled,
    }
  end
  local c = V.Conf[name]

  -- establish position data; if undefined, round the API values
  if not c.position then
    local anchor, _, point, x, y = self:GetPoint(1)
    x = floor(x+.5)
    y = floor(y+.5)
    print('obtained frame position', name, anchor, point, x, y)
    c.position = {anchor, point, x, y}
  else
    print('restoring frame position', name, unpack(c.position))
    self:ClearAllPoints()
    local anchorTo, relativePoint, x, y = unpack(c.position)
    self:SetPoint(anchorTo, UIParent, relativePoint, x, y)
  end
  self:Enable(c.enabled)


end