view ObjectiveTracker/Widgets.lua @ 37:e84d645c8ab8

- revised the tracker update function to build its complete data list up front and use the values as points of comparison for determining possible out of place blocks, which will be iterated over afterward to remove what wasn't re-used - also entailed revising the exact role of global event handlers and function hooks, limiting their directions of communication so one doesn't end up calling the other multiple or inifinity times - schema handling polish
author Nenue
date Mon, 18 Apr 2016 07:56:23 -0400
parents 69d03f8e293e
children 1f8f9cc3d956
line wrap: on
line source
local B = select(2,...).frame
local T = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
local print = B.print('WidgetFactory')
local _G, UIParent = _G, UIParent
local GetQuestLogSpecialItemInfo, IsQuestLogSpecialItemInRange, GetQuestLogSpecialItemCooldown = GetQuestLogSpecialItemInfo, IsQuestLogSpecialItemInRange, GetQuestLogSpecialItemCooldown
local CooldownFrame_SetTimer, SetItemButtonTextureVertexColor, CreateFrame, VeneerObjectiveScroll = CooldownFrame_SetTimer, SetItemButtonTextureVertexColor, CreateFrame, VeneerObjectiveScroll
local tremove, tContains, pairs, ipairs, setmetatable, floor = tremove, tContains, pairs, ipairs, setmetatable, floor
local SetItemButtonTexture, SetItemButtonCount = SetItemButtonTexture, SetItemButtonCount
local ToggleWorldMap, GetTrackedAchievements, GetTasksTable = ToggleWorldMap, GetTrackedAchievements, GetTasksTable
-- GLOBALS: Veneer_QuestObjectiveItem_UpdateCooldown, Veneer_QuestObjectiveItem_OnUpdate
--- frame refs
local Wrapper = _G.VeneerObjectiveWrapper
local Scroller = Wrapper.scrollArea
local CloseButton = Wrapper.CloseButton
local QuestMapButton = Wrapper.QuestMapButton
local Scroll = _G.VeneerObjectiveScroll
local usedButtons = T.Quest.itemButtons
local freeButtons = T.Quest.freeButtons

T.buttons = {
  CloseButton = {
    closedSwatch = {
      [[Interface\Buttons\UI-Panel-QuestHideButton]],
      [[Interface\Buttons\UI-Panel-QuestHideButton]],
      0, 0.5, 0.5, 1,
      0.5, 1, 0.5, 1,
    },
    openSwatch = {
      [[Interface\Buttons\UI-Panel-QuestHideButton]],
      [[Interface\Buttons\UI-Panel-QuestHideButton]],
      0.5, 1, 0.5, 1,
      0, 0.5, 0.5, 1,
    },
    parent = 'VeneerObjectiveWrapper'
  },
  QuestMapButton = {
    closedSwatch = {
      [[Interface\QUESTFRAME\UI-QUESTMAP_BUTTON]],
      [[Interface\QUESTFRAME\UI-QUESTMAP_BUTTON]],
      0, 1, 0.5, 1,
      0, 1, 0, 0.5,
    },
    openSwatch = {
      [[Interface\QUESTFRAME\UI-QUESTMAP_BUTTON]],
      [[Interface\QUESTFRAME\UI-QUESTMAP_BUTTON]],
      0, 1, 0, 0.5,
      0, 1, 0.5, 1,
    }
  }
}

local Scroller_OnShow = function()
  Wrapper.watchMoneyReasons = 0;
  --T:Update()
  --T:OnInitialize()
  for i, region in ipairs(Wrapper.headerComplex) do
    region:Show()
  end
end

local Scroller_OnHide = function()
  local self = Wrapper
  Wrapper:UnregisterAllEvents()
  Wrapper:SetScript('OnEvent', nil)
  for i, region in ipairs(Wrapper.headerComplex) do
    region:Hide()
  end
end

local Scroller_OnMouseWheel = function(self, delta)
  local r = Scroll:GetHeight() - Scroller:GetHeight()
  local s = B.Conf.ObjectiveScroll - delta * floor(r/5+.5)
  local from = self:GetVerticalScroll()
  print('|cFF00FF00OnMouseWheel', 'scroll =', s)
  if s >= r then
    s = r
  elseif s < 1 then
    s = 0
  end
  self:SetVerticalScroll(s)
  B.Conf.ObjectiveScroll = s
  print('|cFF00FF00OnMouseWheel', 'from = ', from, 'scroll =', s, ' range =', r, 'current =', self:GetVerticalScroll())

  T.UpdateActionButtons('SCROLLING')
end

local UpdatePanelButton = function (self, state)
  state = state or true
  local swatch = (state == true) and self.openSwatch or self.closedSwatch
  print((state ~= true) and 'closedSwatch' or 'openSwatch')
  self:SetNormalTexture(swatch[1])
  self:SetPushedTexture(swatch[2])
  if #swatch >= 6 then
    self:GetNormalTexture():SetTexCoord(swatch[3], swatch[4], swatch[5], swatch[6])
  end
  if #swatch == 10 then
    self:GetPushedTexture():SetTexCoord(swatch[7], swatch[8], swatch[9], swatch[10])
  end

end

local OnClick = {}
OnClick.CloseButton = function(self)
  T:SetEnabled()
  if T.Conf.enabled then
    T:Update()
  end

  UpdatePanelButton(self, T.Conf.enabled)
end

OnClick.QuestMapButton = function()
  ToggleWorldMap()
end


T.InitializeWidgets = function()
  local panelButtons = T.buttons
  --- tracker scroll
  Scroller:SetScript('OnMouseWheel', Scroller_OnMouseWheel)
  Scroller:SetScript('OnShow', Scroller_OnShow)
  Scroller:SetScript('OnHide', Scroller_OnHide)
  for name, swatch in pairs(panelButtons) do
    local source = swatch and swatch or panelButtons.CloseButton
    local button = Wrapper[name]
    button.parent = swatch.parent
    button.openSwatch = source.openSwatch
    button.closedSwatch = source.closedSwatch
    if OnClick[name] then
      button:SetScript('OnClick', OnClick[name])
    end
    UpdatePanelButton(button, T.Conf.enabled)
  end
end

----------------------------------------------------------------------------------------
--- modified version of the itemButton initializer used by Barjack's 'QuestKing 2' addon,
--- url: http://mods.curse.com/addons/wow/questking
----------------------------------------------------------------------------------------
T.SetItemButton = function(block, info)
  local itemInfo = info.specialItem
  if not itemInfo then
    return
  end

  local itemButton
  if not info.itemButton then
    if #freeButtons >= 1 then
      print('    |cFF00FFFFfound a free button')
      itemButton = freeButtons[#freeButtons]
      freeButtons[#freeButtons] = nil
      if itemButton.block then
        itemButton.block.itemButton = nil
        itemButton.block = nil
      end
    else
      local buttonIndex = T.Quest.numButtons + #freeButtons + 1
      itemButton = CreateFrame('Button', 'VeneerQuestItemButton' .. buttonIndex, UIParent, 'VeneerItemButtonTemplate')
      itemButton.buttonIndex = buttonIndex
      itemButton:SetSize(36, 36)
      itemButton:GetNormalTexture():SetSize(36 * (5/3), 36 * (5/3))
      print('    |cFFFF4400starting new button', itemButton:GetName())
    end
    T.Quest.numButtons = T.Quest.numButtons + 1
  else
    itemButton = info.itemButton
    print('    |cFF00FF00found assigned button', itemButton:GetName())

  end
  -- set values

  info.itemButton = itemButton
  usedButtons[info.questID] = itemButton
  print('      |cFF8800FFassigning|r', itemButton:GetName(), 'to quest|cFF00FF00', info.questID, '|rat|cFFFFFF00', block:GetName(),'|r')

  for k,v in pairs(usedButtons) do
    print('|cFFFF44DD'..k..'|r', v:GetName())
  end

  itemButton:SetAttribute("type", "item")
  itemButton:SetAttribute("item", itemInfo.link)

  itemButton.questID = info.questID
  itemButton.logIndex = info.logIndex
  itemButton.charges = itemInfo.charges
  itemButton.rangeTimer = -1
  itemButton.block = block

  SetItemButtonTexture(itemButton, itemInfo.icon)
  SetItemButtonCount(itemButton, itemInfo.charges)
  Veneer_QuestObjectiveItem_UpdateCooldown(itemButton);

  return itemButton
end
--- Clear an itemButton from the given block
T.FreeItemButtons = function(block)

  if block.itemButton then
    local itemButton = block.itemButton
    if itemButton.questID ~= block.info.questID then
      block.itemButton = nil
      itemButton.block = T.Quest.InfoBlock[itemButton.questID]
    else
      itemButton.block = nil
      itemButton:Hide()

      usedButtons[itemButton.questID] = nil
      freeButtons[#freeButtons + 1] = itemButton
      T.Quest.numButtons = T.Quest.numButtons - 1
      print('|cFFFF0088released', itemButton:GetName(),'and', block:GetName())
    end
  end
end

function Veneer_QuestObjectiveItem_OnUpdate (self, elapsed)
  -- Handle range indicator
  local rangeTimer = self.rangeTimer
  if (rangeTimer) then
    rangeTimer = rangeTimer - elapsed
    if (rangeTimer <= 0) then
      local link, item, charges, showItemWhenComplete = GetQuestLogSpecialItemInfo(self.logIndex)
      if ((not charges) or (charges ~= self.charges)) then
        T:Update()
        return
      end

      local count = self.HotKey
      local valid = IsQuestLogSpecialItemInRange(self.logIndex)
      if (valid == 0) then
        count:Show()
        count:SetVertexColor(1.0, 0.1, 0.1)
      elseif (valid == 1) then
        count:Show()
        count:SetVertexColor(0.6, 0.6, 0.6)
      else
        count:Hide()
      end
      rangeTimer = TOOLTIP_UPDATE_TIME
    end

    self.rangeTimer = rangeTimer
  end
end

function Veneer_QuestObjectiveItem_UpdateCooldown (itemButton)
  local start, duration, enable = GetQuestLogSpecialItemCooldown(itemButton.logIndex)
  if (start) then
    CooldownFrame_SetTimer(itemButton.Cooldown, start, duration, enable)
    if (duration > 0 and enable == 0) then
      SetItemButtonTextureVertexColor(itemButton, 0.4, 0.4, 0.4)
    else
      SetItemButtonTextureVertexColor(itemButton, 1, 1, 1)
    end
  end
end

-----------------------------------------
-- Criteria frames

--[[
      text = description,
      type = type,
      finished = completed,
      quantity = quantity,
      requiredQuantity = requiredQuantity,
      characterName = characterName,
      flags = flags,
      assetID = assetID,
      quantityString = quantityString,
      criteriaID = criteriaID,
]]
local newWidgetID = 0
T.WidgetRegistry = {}
local wr = T.WidgetRegistry

--- Get a usable widget for the given achievement criteria set.
-- Returns a frame object with dimensioning parameters needed to size the receiving tracker block
T.SetWidget = function(line, data, objectiveType, objectiveKey)
  local print = B.print('ObjectiveWidgets')
  local widgetType = objectiveType
  local widget
  if wr[widgetType] and wr[widgetType].used[objectiveKey] then
    widget = wr[widgetType].used[objectiveKey]
    print('|cFF00FF00Updating ('..objectiveKey..')', widget)
  elseif not wr[widgetType] or #wr[widgetType].free == 0 then
    widget = CreateFrame('Frame', 'VeneerObjective' .. widgetType .. (wr[widgetType] and (wr[widgetType].lastn+1) or (1)), VeneerObjectiveScroll, 'VeneerObjectiveCriteria' .. widgetType)

    print('|cFFFF0088Creating `'..widget:GetName()..'` id', wr[widgetType].lastn)
  else
    widget = tremove(wr[widgetType].free)
    print('|cFFFFFF00Acquiring released widget', widget:GetName())
  end


  wr[widgetType].used[objectiveKey] = widget
  widget.line = line
  widget.objective = data
  widget.key = objectiveKey
  T.InitializeWidget(widget)
  return widget
end

--- WidgetTemplate 'OnLoad'
T.RegisterWidget = function(frame)
  local print = B.print('ObjectiveWidgets')
  local widgetType = frame.widgetType
  if not wr[frame.widgetType] then
    print('|cFFFF4400[[WidgetTemplate]]|r', widgetType)
    wr[widgetType] = { lastn = 1, free = {}, used = {}, usedIndex = {}, freeIndex = {} }
  else
    print('|cFF0088FF+ [[WidgetTemplate]]r', widgetType, wr[widgetType].lastn)
    wr[widgetType].lastn = wr[widgetType].lastn + 1
  end
end
--- WidgetTemplate 'OnShow'
local wrapperWidth, textIndent
T.InitializeWidget = setmetatable({}, {
  __call = function(t, frame)
    -- todo: config pull
    if not wrapperWidth then
      wrapperWidth = T.Conf.Wrapper.Width
      textIndent = T.Conf.Wrapper.TextIndent
    end

    frame:SetWidth(wrapperWidth - textIndent * 2)
    frame:SetScript('OnEvent', T.UpdateWidget[frame.widgetType])
    frame:RegisterEvent('QUEST_LOG_UPDATE')
    frame:RegisterEvent('TRACKED_ACHIEVEMENT_UPDATE')
    frame:RegisterEvent('TRACKED_ACHIEVEMENT_LIST_CHANGED')
    frame:RegisterEvent('CRITERIA_UPDATE')
    frame:RegisterEvent('CRITERIA_COMPLETE')
    frame:RegisterEvent('CRITERIA_EARNED')
    t[frame.widgetType](frame)
    T.UpdateWidget[frame.widgetType](frame)
  end,
})

--- WidgetTemplate 'OnEvent'
T.UpdateWidget = setmetatable({}, {
  __call = function(t, frame)
    if not frame.widgetType then
      error('Invalid widget template, needs .widgetType')
      return
    end

    return t[frame.widgetType](frame)
  end
})

--- WidgetTemplate 'OnHide'
T.ReleaseWidget = function(frame)
  --[[
  local print = B.print('ObjectiveWidgets')
  local reg = wr[frame.widgetType]
  if reg and reg.used[frame.key] then
    reg.used[frame.key] = nil
    frame.line = nil
    frame.info = nil
    frame:UnregisterAllEvents()
    tinsert(reg.free, frame)
    print('|cFFBBBBBBreleased from service', frame:GetName())
  end
  ]]
end

--- RemoveTrackedAchievement post-hook
T.CleanWidgets = function()
  local print = B.print('ObjectiveWidgets')
  local tracked = {GetTrackedAchievements() }
  local tasks = GetTasksTable()
  for type, reg in pairs(T.WidgetRegistry) do
    print('collecting', type)
    for key, frame in pairs(reg.used) do
      if frame.objective.cheevID then
        local id = frame.objective.cheevID

        if id and not tContains(tracked, id) then

          print('  untracked achievement', id, 'associated with', key, frame:GetName())
          frame:Hide()
        end
      elseif frame.objective.questID then
        -- do something for quest task
      end
    end
  end
end



T.defaults.WidgetStyle = {

}

local progressHeight = 17
local progressBorder = 1
local progressFont = _G.VeneerCriteriaFontNormal

local lprint = B.print('Line')
T.InitializeWidget.ProgressBar = function(self)
  local print = lprint
  local c = T.Conf.Wrapper
  self.height = progressHeight + c.TextSpacing
  self.width = c.Width - c.TextSpacing
  self.value = self.value or 1
  self.maxValue = self.maxValue or 1

  self:SetHeight(progressHeight)
  self.bg:SetHeight(progressHeight)
  self.bg:SetWidth(self.width)
  self.fg:ClearAllPoints()
  self.fg:SetPoint('BOTTOMLEFT', self, 'BOTTOMLEFT', progressBorder, progressBorder)
  self.fg:SetHeight(progressHeight - progressBorder *  2)

  --self.status:SetFontObject(progressFont)
  self.status:SetText(self.objective.quantityString)
end

T.UpdateWidget.ProgressBar = function (self)
  local quantity, requiredQuantity = self.value, self.maxValue
  print('update vals:')
  for k,v in pairs(self.line) do
    print(k, v)
  end

  local progress = (quantity / requiredQuantity)
  if progress >= 1 then
    self.fg:Show()
    self.fg:SetWidth(self.width  - progressBorder * 2)
  elseif progress > 0 then
    self.fg:Show()
    print('color:', 1-progress*2 , progress*2 - 1,0,1)
    print('width:', (self.width  -progressBorder * 2) * progress)
    self.fg:SetTexture(1-progress*2 , progress*2,0,1)
    self.fg:SetWidth((self.width  -progressBorder * 2) * progress)
  else
    self.fg:Hide()
  end
end


T.InitializeWidget.Hidden = function (self)
  self.height = 0
end
T.UpdateWidget.Hidden = function (self)
  self.height=  0
end


--- Queue any active item buttons for update for that frame
local iprint = B.print('ItemButton')
local Quest = T.Quest
local IsQuestWatched, InCombatLockdown = IsQuestWatched, InCombatLockdown
T.UpdateActionButtons = function(updateReason)
  local print = iprint
  Scroller.snap_upper = 0
  Scroller.snap_lower = 0
  local print = B.print('ItemButton')
  if updateReason then
    print = B.print('IB_'..updateReason)
  end

  local previousItem
  for questID, itemButton in pairs(Quest.itemButtons) do
    local info= T.Quest.Info[questID]

    print('|cFF00FFFF'.. questID .. '|r', itemButton:GetName())
    local block = T.Quest.QuestBlock[questID]
    if block then
      -- Dispatch the probe
      if IsQuestWatched(info.logIndex) then
        itemButton.previousItem = previousItem
        print('  |cFFFFFF00probing', block:GetName())
        block:SetScript('OnUpdate', function()
          if block:GetBottom() and not InCombatLockdown() then
            print('  '..block:GetName()..' |cFF00FF00probe hit!')
            T.UpdateBlockAction(block, itemButton, itemButton.previousItem) -- needs to be previousItem from this scope
            block:SetScript('OnUpdate', nil)

          end
        end)
        previousItem = itemButton
      else
        print('hidden block or unwatched quest')
        itemButton.previousItem = nil
        itemButton:Hide()
      end
    elseif itemButton:IsVisible() then
      print('  |cFFFF0088hiding unwatched quest button', itemButton:GetName())
      itemButton.previousItem = nil
      itemButton:Hide()
    else
      print('  |cFFBBBBBBignoring hidden log quest button', itemButton:GetName())
    end
  end
end