view Config/Config.lua @ 59:07ef62fe201f

Re-write of BuffFrame module: - uses secure hooks on blizzard BuffFrame.lua functions to determine needed action - make use of built-in table behavior to reduce unnecessary frame updates
author Nenue
date Thu, 28 Jul 2016 18:27:56 -0400
parents 16465f3fd919
children
line wrap: on
line source
--- All the control GUI stuff, including chat command functions
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 3/12/2016 12:49 AM
local vn, _G = select(2,...).frame, _G
local M = vn:RegisterModule("Options")
local tostring, tonumber, floor, max, assert = tostring, tonumber, floor, math.max, assert
local unpack, setmetatable, pairs, ipairs, type, wipe = unpack, setmetatable, pairs, ipairs, type, table.wipe
local CreateFrame, IsControlKeyDown = _G.CreateFrame, _G.IsControlKeyDown
local OpacitySliderFrame, ColorPickerFrame = _G.OpacitySliderFrame, _G.ColorPickerFrame
local print = vn.print('Cfgl')
local function round(number, decimals)
  if floor(number) == number then
    return ('%d'):format(number)
  end

  return (("%%.%df"):format(decimals)):format(number)
end

--- Set up this way to ensure that all the necessary data exists before things domino into something inscrutable
M.prototypes = {
  value = setmetatable({}, {__call = function(self, frame)
    assert(frame, 'Expected table (received '..type(frame.OptInfo)..')')
    assert(frame.OptRoot, 'Invalid config table for frame '.. frame:GetName().. '')
    return self[frame.ValueType](frame.OptTab, frame.OptKey, frame.OptRoot)
  end}),
  reset = setmetatable({}, {__call = function(self, frame)
    assert(frame.GetName, 'Invalid frame reference (received '..type(frame.OptInfo)..')')
    assert(frame.OptInfo, 'Expecting a table (received '..type(frame.OptInfo)..')')
    return self[frame.OptType](frame, frame.OptInfo)
  end})
}
M.config = {}
M.defaults = {
  enable = true
}
local GetValue = M.prototypes.value
local ResetField = M.prototypes.reset
--- STATE VARIABLES
local configInit
--- Dummies for addon table upvalues
local configFrames = {} -- actual frame objects
local displays = vn.displays     -- anchor objects dummy


--- Returns a value retreival function and the current value stored in config
-- @paramsig value, previousValue = configInteger(key)
-- @param key Name of the config field being represented.
local defaultGroup = 'BuffButton'

GetValue.Integer = function(group, key, parent)
  return function(self)
    return floor(tonumber(self:GetValue()) + 0.5)
  end, (parent[group ..key] or parent[defaultGroup..key])
end
GetValue.Percent = function(group, key, parent)
  return function(self, display)
    local value = self:GetValue()
    if display then
      return tostring(floor(value*100+0.5))..' %'
    else
      return floor((value*100+0.5))/100
    end
  end, (parent[group ..key] or parent[defaultGroup..key])
end
GetValue.Color = function(group, key, parent)
  -- table for config, color value list for text
  return function(self, display)
    if display then
      return "|cFFFF4444" .. round(self.rgba[1], 1) .. "|r, |cFF44FF44" .. round(self.rgba[2], 1) .. "|r, |cFF4488FF" ..
          round(self.rgba[3], 1) .. "|r, " .. round(self.rgba[4], 1)
    else
      return self.rgba
    end
  end, (parent[group ..key] or parent[defaultGroup..key])
end
GetValue.Check = function(group, key, parent)
  return function(self) return self:GetChecked() end, parent[group ..key] or vn.Conf[defaultGroup..key]
end
-- initializes the corresponding type of config field
local frameTypeConv = {
  Color = 'Button',
  Font = 'Frame',
}


ResetField.Slider = function(frame, optionInfo)
    frame:SetMinMaxValues(optionInfo[5], optionInfo[6])
    frame:SetValueStep(optionInfo[7])
    frame:SetStepsPerPage(optionInfo[8])
    print(frame.OptName, '\n  {', optionInfo[5], optionInfo[6], optionInfo[7], optionInfo[8], '}')
  end
ResetField.CheckButton = function(frame, optionInfo)
    frame.SetValue = function(self, ...)
      self:SetChecked(...)
      self.OptRoot[self.OptName] = self:GetChecked()
      print(self.OptTab)
      vn.UpdateAll()
    end
    frame:SetScript("OnClick",function(self)
      self.OptRoot[self.OptName] = self:GetChecked()
      print(self.OptRoot[self.OptName], self:GetChecked())
      vn.UpdateAll()
    end)
  end
ResetField.Color = function(frame, optionInfo)
  frame.rgba = { frame.current:GetVertexColor() }
  local colorPickerCallback = function(restore)
    local newR, newG, newB, newA
    if restore then
      newR, newG, newB, newA = unpack(restore)
    else
      newA, newR, newG, newB = OpacitySliderFrame:GetValue(), ColorPickerFrame:GetColorRGB()
      print('not cancel', newA, newR, newB, newG)
    end
    frame:SetValue({newR, newG, newB, newA})
    vn.UpdateBuffs(frame.OptTab)
  end
  frame:SetScript("OnClick", function(self)
    print('got a click')
    local r, g, b, a = frame.current:GetVertexColor()
    ColorPickerFrame:SetColorRGB(r, g, b)
    ColorPickerFrame.hasOpacity =  (a ~= nil)
    ColorPickerFrame.opacity = a
    ColorPickerFrame.previousValues = {r,g,b,a}
    ColorPickerFrame.func, ColorPickerFrame.opacityFunc, ColorPickerFrame.cancelFunc =
    colorPickerCallback, colorPickerCallback,colorPickerCallback
    ColorPickerFrame:Hide()
    ColorPickerFrame:Show()
  end)
  frame.SetValue = function(self, rgba)
    print(rgba)
    frame.rgba = rgba
    self.OptRoot[self.OptName] = rgba
    frame.current:SetVertexColor(unpack(rgba))
    frame.fieldvalue:SetText(frame.OptValue(frame, true))
  end
end

--- configDialog
-- @usage tinsert(configDialog, {prefix, row, [...] })
-- Each top level member defines a group of config value handlers, structured as an iterative table where the
-- first member is a key prefix, the second member is an integer row value, and all following members are treated
-- as a widget resource, defined initially as a complete sub-table, which can be re-used further down by passing
-- the string literal widget suffix.
-- widget table: ... {'suffix', 'description', valueCallback, 'template', [widget parameters]}
-- widget copy: ... 'suffix', ...
M.config.BuffFrame = {
  {'BuffButton', 1,

    {'Max', 'Max', 'Integer', 'Slider',
      1, _G.BUFF_MAX_DISPLAY, 1, 1}, -- valueMin, valueMax, valueStep, stepsPerPage
    {'PerRow', 'Per Row', 'Integer', 'Slider',
      1, _G.BUFF_MAX_DISPLAY, 1, 1}, -- valueMin, valueMax, valueStep, stepsPerPage,
    {'Size', 'Icon Size', 'Integer', 'Slider',
      1, 256, 1, 1},
    {'Spacing', 'Icon Spacing', 'Integer', 'Slider',
      1, 50, 1, 1},
    {'DurationSize', 'Duration Text Height', 'Integer', 'Slider',
      1, 72, 1, 1},
    {'Zoom', 'Icon Zoom', 'Integer', 'Slider',
      0, 100, 1, 1},
    {'Border', 'Border', 'Integer', 'Slider',
      1, 16, 1, 1},
    {'Color', 'Default Border', 'Color', 'Color'},
    {'RaidColor', 'RaidBuff Border', 'Color', 'Color'},
    {'PlayerColor', 'Player Buffs', 'Color', 'Color'},
    {'BossColor', 'Encounter Buffs', 'Color', 'Color'},
    {'ShowSelfCast', 'Show name for self-casts', 'Check', 'CheckButton'}
  },
  { 'DebuffButton', 1,
    {'Max', 'Max', 'Integer', 'Slider',
      1, _G.DEBUFF_MAX_DISPLAY, 1, 1 }
    ,
    {'PerRow', 'Per Row', 'Integer', 'Slider',
      1, _G.DEBUFF_MAX_DISPLAY, 1, 1 },
    'Size', 'Spacing', 'DurationSize', 'Zoom', 'Border',
    'Color', 'RaidColor', 'PlayerColor', 'BossColor',
  },
  { 'TempEnchant', 1,
    {'Max', 'Max', 'Integer', 'Slider',
      1, _G.NUM_TEMP_ENCHANT_FRAMES, 1, 1 },
    {'PerRow', 'Per Row', 'Integer', 'Slider',
      1, _G.NUM_TEMP_ENCHANT_FRAMES, 1, 1},
    'Size', 'Spacing', 'DurationSize', 'Zoom', 'Border',
    'Color', 'RaidColor', 'PlayerColor', 'BossColor',
  },
  { 'ConsolidatedBuff', 2,
    {'Position', 'Slot Position', 'Integer', 'Slider',
    1, _G.BUFF_MAX_DISPLAY, 1, 1 }

  },
  { 'ConsolidatedBuff', 2,
    'Size'
  },
  { 'Raid', 3,
    {'ShowMissing', 'Verbose missing raid buffs', 'Check', 'CheckButton'}
  }
}




local configFrame
local optionTemplates = {}
local configPadding, configSpacing = 3, 3

--- Walks the structure table to generate a pretty config panel
local InitConfig = function()
  configInit = true
  local configWidth = vn:GetWidth()
  local optionWidth = (configWidth - configPadding) / 3 - configSpacing
  local configHeight = 0
  local bottom_extent = 0
  local clusterHeight = 0
  local clusterOffset = 0
  local lastCluster
  local cluster = 1
  local col = 0
  for moduleName, moduleOpts in pairs(M.config) do
    for t, taboptions in ipairs(moduleOpts) do
    local group = taboptions[1]
    cluster = taboptions[2]
    col = col + 1


    if not configFrames[t] then
      configFrames[t] = {}
    end


    if cluster ~= lastCluster then
      configHeight = configHeight + clusterHeight
      print('|cFFFF8800## new cluster|r, advancing offset from', clusterOffset, 'to', clusterOffset + clusterHeight)
      clusterOffset = clusterOffset + clusterHeight
      col = 1
      clusterHeight = 0
      lastCluster = cluster
    end

    print('processing tab', group)
    local row = 0
    for i = 3, #taboptions do
      row = row + 1
      local optionInfo = taboptions[i]
      if type(optionInfo) == 'string' then
        optionInfo = optionTemplates[optionInfo]
      end
      local key, fieldname, valueType, configType = unpack(optionInfo)
      assert(GetValue[valueType], 'Invalid valueType \''..tostring(valueType)..'\' ('..type(valueType)..')')
      assert(ResetField[configType], 'Invalid fieldType \''..tostring(configType)..'\' ('..type(configType)..')')

      if not optionTemplates[key] then
        optionTemplates[key] = optionInfo
      end

      local fullkey = group .. key
      print(fullkey, fieldname)

      if not configFrames[t][row] then
        print('building frame', t, group, row)
        local frameTemplate =  'VeneerConfig'..configType
        local frameType = frameTypeConv[configType] or configType
        configFrames[t][row] = CreateFrame(frameType, 'Vn_'.. moduleName ..'_'.. fullkey, vn, frameTemplate)
        local f = configFrames[t][row]
        f.ValueType = valueType
        f.OptType = configType
        f.FrameType = frameType
        f.OptKey = key
        f.OptRoot = vn.Conf[moduleName]
        f.OptTab = group
        f.OptName = fullkey
        f.OptInfo = optionInfo
        local valueFunc, initialValue = GetValue(f)
        print('  value getter', '|cFFFFFF00'..moduleName..'|r.|cFF00FFFF'.. tostring(fullkey),'|r->', valueFunc,initialValue)
        ResetField(f)
        f.OptValue = valueFunc

        --- Enclosing these to
        -- a) make the panel easy to bring up externally
        -- b) limit gameplay risk from config frame errors
        -- c) milk the iterator scope for all its worth
        f.OnChange = function(self)

          -- holding control; mirror this setting in other categories
          if IsControlKeyDown() and not (configInit) then
            configInit = true
            for optTab, opts in pairs(configFrames) do
              for _, opt in ipairs(opts) do
                if opt.OptKey == key then
                  if optTab ~= group then
                    print('mapping to', optTab, opt.OptKey)
                    opt:SetValue(self:GetValue())
                  end

                end
              end
            end
            configInit = nil
          end
          local newValue = valueFunc(self)
          if newValue ~= self.OptRoot[fullkey] then
            print(newValue, fullkey)
            f.fieldvalue:SetText(valueFunc(self, true))
            self.OptRoot[fullkey] = valueFunc(self)
            -- prepare to update
            vn[moduleName]:OnUpdate()
            vn.UpdateConfigLayers()
          end

        end

        f:SetValue(initialValue)
        local yBuffer = configPadding
        if f.fieldname then
          f.fieldname:SetText(fieldname)
          yBuffer = yBuffer + f.fieldname:GetHeight()
        end
        if f.fieldvalue then
          f.fieldvalue:SetText(f:OptValue(true))
        end

        local point, relative, x, y = 'TOPLEFT', 'BOTTOMLEFT', 0, -3

        local base
        if (row == 1) then
          bottom_extent = 0
          base = vn.header
          x = (col-1) * (optionWidth+configSpacing)
          y = -configPadding
        else
          base = configFrames[t][row-1]
        end

        print('|cFFFF0088'..cluster..'|r |cFF00FF00'.. row..'|r', col, base:GetName(), x, y - clusterOffset)

        if frameType ~= 'CheckButton' then
          f:SetWidth(optionWidth)
        end

        f:SetPoint(point, base, relative, x, y-yBuffer-clusterOffset)
        --print('creating', frameType, fieldname)
        f:Show()

        bottom_extent = bottom_extent + f:GetHeight() + yBuffer + configSpacing



        clusterHeight = max(clusterHeight, bottom_extent)
        --print('y', floor(yBuffer+0.5), 'f:H', floor(f:GetHeight()+0.5), 'hTally', floor(bottom_extent+0.5), 'hMax', floor(configHeight+0.5))
      end
    end
    end
  end


  -- grab the last cluster
  if lastCluster == cluster then
    print('|cFF00FF00##scooping up last cluster info')
    configHeight = configHeight + clusterHeight
  end

  if not vn.configFramesCreated then
    vn.configFramesCreated = true
    vn:SetHeight(vn.header:GetStringHeight() + configSpacing*3 + configHeight)
  end
  if configInit then configInit = nil end
end

M.Command = function(enable, editbox)
  displays = vn.displays
  if type(enable) == 'boolean' then
    vn.Conf.ConfigMode = enable
  else
    vn.Conf.ConfigMode = (vn.Conf.ConfigMode == false) and true or false
  end

  print('/BUFF', vn.Conf.ConfigMode, type(vn.Conf.ConfigMode))
  if vn.Conf.ConfigMode  then
    if not vn.configFramesCreated then
      InitConfig()
    end
    print('Veneer config')
    vn:Show()
  else
    vn:Hide()
  end
  vn.UpdateAll()
  vn.UpdateConfigLayers()
end

vn.Close = function ()
  M.Command()
end

vn.ToggleGuides = function(_, self)
  vn.Conf.GuidesMode = (not vn.Conf.GuidesMode)
  if vn.Conf.GuidesMode then
    self:GetNormalTexture():SetColorTexture(0.94, 0.21, 0.21, 1)
  else
    self:GetNormalTexture():SetColorTexture(0, 0, 0, 1)
  end

  vn.UpdateConfigLayers()
end

M.OnEnable = function()
  print('|cFFFF0088config module', vn.Conf.ConfigMode)
  M.Command(vn.Conf.ConfigMode)
end

M.OnInitialize = function()
  DEFAULT_CHAT_FRAME:AddMessage("|cFF22D822Veneer|r confogulator loaded. Type |cFF00FFFF/vn|r to begin.")
  SLASH_VENEER1, SLASH_VENEER2 = "/veneer", "/vn"
  SlashCmdList.VENEER = M.Command

end