view Config.lua @ 7:5301c68f28d8

TrackerBlock - use IsModifiedClick function to determine appropriate OnClick actions - handle 'CHATLINK' modifier - handle 'TOGGLEQUESTWATCH' modifier TrackerBlockObjectives - use a generic framework to manage frame creation for various criteria tracker types: - ProgressBar when Blizzard flag data indicates so - skip when Blizzard flag data indicates so - DynamicText otherwise - events related to the criteria are registered in the criteria frame, and unregistered when the frame is hidden, either by destruction of its parent or completion
author Nenue
date Fri, 01 Apr 2016 12:27:05 -0400
parents 3dbcad2b387d
children 66b927b46776
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 B, _G = select(2,...).frame, _G
local M = B:RegisterModule("Options")
local tostring, tonumber, floor, format = tostring, tonumber, floor, string.format
local unpack, select, pairs, ipairs, type, wipe = unpack, select, pairs, ipairs, type, table.wipe
local CreateFrame, IsControlKeyDown = _G.CreateFrame, _G.IsControlKeyDown
local max = math.max
local OpacitySliderFrame, ColorPickerFrame = _G.OpacitySliderFrame, _G.ColorPickerFrame
local print = B.print('Cfgl')
local function round(number, decimals)
  if floor(number) == number then
    return ('%d'):format(number)
  end

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

--- STATE VARIABLES
local configInit
--- Dummies for addon table upvalues
local configFrames = {} -- actual frame objects
local displays = B.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'
local configInteger = function(group, key)
  return function(self ,display)
    return floor(tonumber(self:GetValue()) + 0.5)
  end, (B.Conf[group ..key] or B.Conf[defaultGroup..key])
end
local configPercent = function(group, key)
  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, (B.Conf[group ..key] or B.Conf[defaultGroup..key])
end
local configColor = function(group, key)
  -- 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, (B.Conf[group ..key] or B.Conf[defaultGroup..key])
end
local configCheck = function(group, key)
  return function(self) return self:GetChecked() end, B.Conf[group ..key] or B.Conf[defaultGroup..key]
end
-- initializes the corresponding type of config field
local frameTypeConv = {
  Color = 'Button',
  Font = 'Frame',
}
local configTypeParams = {
  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,
  CheckButton = function(frame, optionInfo)
    frame.SetValue = function(self, ...)
      self:SetChecked(...)
      B.Conf[self.OptName] = self:GetChecked()
      print(self.OptTab)
      B.UpdateAll()
    end
    frame:SetScript("OnClick",function(self)
      B.Conf[self.OptName] = self:GetChecked()
      print(B.Conf[self.OptName], self:GetChecked())
      B.UpdateAll()
    end)
  end,
  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})
      B.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
      B.Conf[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', ...
local configDialog = {
  {'BuffButton', 1,

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

  },
  { 'ConsolidatedBuff', 2,
    'Size'
  },
  { 'Raid', 3,
    {'ShowMissing', 'Verbose missing raid buffs', configCheck, '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 = B: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 t, taboptions in ipairs(configDialog) 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, valueFuncGenerator, configType = unpack(optionInfo)

      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, fullkey, B, frameTemplate)
        local f = configFrames[t][row]
        f.OptKey = key
        f.OptTab = group
        f.OptName = fullkey
        local valueFunc, initialValue = valueFuncGenerator(group, key)
        print('  value getter', fullkey,'->', valueFunc,initialValue)
        configTypeParams[configType](f, optionInfo)
        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 ~= B.Conf[fullkey] then
            print(newValue, fullkey)
            f.fieldvalue:SetText(valueFunc(self, true))
            B.Conf[fullkey] = valueFunc(self)
            -- prepare to update
            wipe(B.drawn[f.OptTab])
            B.UpdateBuffs(self.OptTab)
            B.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 = B.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

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

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

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

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

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

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

  B.UpdateConfigLayers()
end

M.OnEnable = function()
  M.Command(B.Conf.ConfigMode)
end

M.OnInitialize = function()
  DEFAULT_CHAT_FRAME:AddMessage("|cFF22D822Veneer|r")
  SLASH_BUFFALO1, SLASH_BUFFALO2 = "/buffalo", "/buff"
  SlashCmdList.BUFFALO = M.Command

end