view ObjectiveTracker/ObjectiveFrame.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 4b3da1b221de
children
line wrap: on
line source
--- ${PACKAGE_NAME}
-- @file-author@
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 3/30/2016 12:49 AM
local B = select(2,...).frame
local T = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
local _G, ipairs, max, min, unpack, floor, pairs, tostring, type, band = _G, ipairs, max, min, unpack, floor, pairs, tostring, type, bit.band
local IsResting, UnitXP, UnitXPMax, GetXPExhaustion = IsResting, UnitXP, UnitXPMax, GetXPExhaustion
local UnitLevel, IsQuestWatched, UIParent = UnitLevel, IsQuestWatched, UIParent
local GetAutoQuestPopUp, GetQuestLogCompletionText = GetAutoQuestPopUp, GetQuestLogCompletionText
local PERCENTAGE_STRING, GetQuestProgressBarPercent = PERCENTAGE_STRING, GetQuestProgressBarPercent
local Default, AutoQuest, Quest, Bonus, Cheevs = T.DefaultHandler, T.AutoQuest, T.Quest, T.Bonus, T.Cheevs
local InCombatLockdown, format, lshift, CreateFrame = InCombatLockdown, format, bit.lshift, CreateFrame
local print = B.print('Tracker')
local unitLevel = 1
local OBJECTIVE_TRACKER_UPDATE_REASON = OBJECTIVE_TRACKER_UPDATE_REASON
local debug = false
--------------------------------------------------------------------
--- Global frame layout
--------------------------------------------------------------------

--- Upvalues
local Wrapper = _G.VeneerObjectiveWrapper
local Scroller = Wrapper.scrollArea
local Scroll = _G.VeneerObjectiveScroll
local orderedHandlers = T.orderedHandlers
local orderedNames = T.orderedNames

--- Temp values set during updates
local wrapperWidth, wrapperHeight
local scrollWidth, scrollHeight
local previousBlock
local currentBlock
--- todo: source these from config
local itemButtonSize, itemButtonSpacing =  36, 1

local headerHeight, headerColor, headerSpacing = 16, {1,1,1,1}, 2
local headerbg = {'VERTICAL', 1, 1, 0.5, 0.5, 1, 1, 0.5, 0}
local headerFont, headerSize, headerOutline = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Bold.ttf]], 14, 'OUTLINE'

local titlebg = {'HORIZONTAL', 1, 0, .7, .25, 1, 0, .7, .125}
local titlebg_daily = {'HORIZONTAL', 0, .7, 1, .25, 0, 1, .7, .125 }
local titlebg_account = {'HORIZONTAL', 0, .45, 1, .25, 0,  .45, 1, .125}
local titleFont, titleSize, titleOutline = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Bold.ttf]], 16, 'OUTLINE'

local textbg =  {'HORIZONTAL', 0, 0, 0, 0.4, 0, 0, 0, 0 }
local textbg_daily = {'HORIZONTAL', 0, .7, 1, .1, 0, 1, .7, .075 }
local textbg_account = {'HORIZONTAL', 0, .45, 1, 0.4, 0,  .41, 1, .085 }
local textFont, textSize, textOutline = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Regular.ttf]], 16, 'OUTLINE'


local selectionbg = {'HORIZONTAL', 1, 1, 1, 0, 1, 1, 1, 0.225}
local titleSpacing, textSpacing, blockSpacing = 3, 3, 1
local titleIndent, textIndent,selectionIndent = 2, 5, 50

local wrapperMaxWidth, wrapperMaxHeight = 270, 490 -- these are the hard bounds, actual *Height variables are changed
local wrapperHeadFont, wrapperHeadSize, wrapperHeadOutline = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Bold.ttf]], 16, 'NONE'
local wrapperPosition = {'RIGHT', UIParent, 'RIGHT', -84, 0 }
local rewardSize = 32
local oprint = B.print('Objectives')
local bprint = B.print('Block')
local tprint = B.print('Tracker')
local lprint = B.print('Line')

local currentPosition, anchorFrame, anchorPoint



T.AddBlock = function(handler, block)
  local print = bprint
end

--- Used as an iterator of sorts for cascaded tag icon placements (the daily/faction/account icons)
T.AddTag = function (block, tagName, tagPoint, tagAnchor, tagRelative)
  local print = bprint
  local tag = block[tagName]
  if block.info[tagName] and tag then
    tag:SetTexCoord(unpack(block.info[tagName]))
    tag:Show()
    tag:SetPoint(tagPoint, tagAnchor, tagRelative, 0, 0)
    tagPoint, tagAnchor, tagRelative = 'TOPRIGHT', tag, 'TOPLEFT'
  else
    block[tagName]:Hide()
  end
  return tagPoint, tagAnchor, tagRelative
end

--- Adds the given line to the current content and advances the anchor pointer to that new line for the following call.
T.AddLine = function(block, line)
  local print = lprint
  line:ClearAllPoints()
  line:SetPoint('LEFT', block, 'LEFT', 0, 0)
  line:SetPoint('TOP', block.endPoint, 'BOTTOM', 0, -textSpacing)
  line:SetPoint('RIGHT', block, 'RIGHT', 0, 0)
  line:SetHeight(line.height)
  line:Show()

  block.endIndex = line.index
  block.numLines = block.numLines + 1
  block.attachmentHeight = block.attachmentHeight + (line.height + textSpacing)
  print('     |cFF0088FFsetting line #'..block.numLines..' for|r', block.info.title, "\n            |cFF0088FFsize:|r", line.height,
    "|cFF0088FFpoint:|r", line:GetPoint(1), "|cFF0088FFwidget:|r", (line.widget and 'Y' or 'N'))
  block.endPoint = line
end

--- Creates or retrieves a complete line data object
T.GetLine = function(handler, block, lineIndex)
  local print = lprint
  local blockIndex = block.index
  if not block.lines then
    block.lines = {}
  end
  local lines = block.lines
  if not lines[lineIndex] then
    print('     |cFF00FF88created line #'..lineIndex..' from for '..handler.name..' block #'..blockIndex)
    lines[lineIndex] = CreateFrame('Frame', 'Vn'..handler.name .. blockIndex..'ObjectiveLine'..lineIndex, block, 'VeneerTrackerObjective')
    local line = lines[lineIndex]
    line.index = lineIndex
    line.height = 0
    line.status:SetSpacing(textSpacing)
    line.status:SetPoint('LEFT', line, 'LEFT', textIndent, 0)
    B.SetConfigLayers(line)

    if lines[lineIndex+1] then
      lines[lineIndex+1]:ClearAllPoints()
    end

    if debug then
      for _, region in ipairs(lines[lineIndex].debug) do
        region:Show()
      end
    end

  end
  return lines[lineIndex]
end

--- Creates or retrieves a complete block frame object
T.GetBlock = function(handler, blockIndex)
  local print = bprint
  local block = handler.usedBlocks[blockIndex]

  if not handler.usedBlocks[blockIndex] then
    if #handler.freeBlocks >= 1 then
      block = handler.freeBlocks[#handler.freeBlocks]
      handler.freeBlocks[#handler.freeBlocks] = nil
    else
      block = CreateFrame('Frame', 'Veneer'..tostring(handler)..'Block'..blockIndex, Scroll, 'VeneerTrackerBlock')



      local c = T.Conf.Wrapper
      block.index = blockIndex
      block.SetStyle = T.SetBlockStyle
      block:SetWidth(c.Width)

      block.title:SetSpacing(c.TitleSpacing)
      block.title:SetPoint('TOP', block, 'TOP', 0, -titleSpacing)

      block.titlebg:SetTexture(1,1,1,1)
      block.titlebg:SetGradientAlpha(unpack(T.colors.default.titlebg))
      block.titlebg:SetPoint('TOP', block, 'TOP', 0, 0)
      block.titlebg:SetPoint('BOTTOM', block.title, 'BOTTOM', 0, -titleSpacing)

      block.status:SetSpacing(c.TextSpacing)
      block.status:SetPoint('TOP', block.titlebg, 'BOTTOM', 0, -textSpacing)
      block.status:SetPoint('LEFT', block.titlebg, 'LEFT', textIndent, 0)

      block.statusbg:SetPoint('TOP', block.titlebg, 'BOTTOM', 0, 0)
      block.statusbg:SetPoint('BOTTOM', block, 'BOTTOM', 0, 0)
      block.statusbg:SetTexture(1,1,1,1)
      block.statusbg:SetGradientAlpha(unpack(T.colors.default.textbg))

      block.SelectionOverlay:SetGradientAlpha(unpack(T.colors.default.selectionbg))
      block.SelectionOverlay:SetPoint('TOPLEFT', selectionIndent, 0)
      block.SelectionOverlay:SetPoint('BOTTOMRIGHT')

      block.icon:SetSize(rewardSize, rewardSize)
      block.icon:SetPoint('TOPRIGHT', block, 'TOPRIGHT', -2, -2)


      --- methods for event handlers

      block.Select = handler.Select
      block.Open = handler.Open
      block.Remove = handler.Remove
      block.Link = handler.Link
      block.clickZone:SetScript('OnMouseUp', function(self, ...) handler.OnMouseUp(block, ...) end)
      block.clickZone:SetScript('OnMouseDown', function(self, ...) handler.OnMouseDown(block, ...) end)
      block.attachmentHeight = 0
      block:ClearAllPoints()

      B.SetConfigLayers(block)

      if debug then
        for _, region in ipairs(block.debug) do
          region:Show()
        end
      end
    end
    handler.usedBlocks[blockIndex] = block
  end
  return handler.usedBlocks[blockIndex]
end

--- Module-specific data wrangling that has to happen during UpdateBlock()
 -- Most of this is sculpting objectives data into a parsible list.
 -- Some of it is additional block manipulation that doesn't quite fit with the other modules..
Bonus.UpdateObjectives = function(handler, block)
  Default.UpdateObjectives(handler, block)
end


--- They are not exactly quests, but the aliases used to signal that a quest was pushed into your log by the environment.
 -- Once accepted they never appear again unless the quest is dropped (daily reset/abandon quest)
local CLICK_TO_COMPLETE = 'Click to Complete'
local CLICK_TO_ACCEPT = 'Click to Accept'
AutoQuest.UpdateObjectives = function(handler, block)
  local print = lprint
  if block.info.type == 'OFFER' then
    block.status:SetText(CLICK_TO_ACCEPT)
  end
end




--- Does the main iterations for populating block content.
 -- Hooked by corresponding handler functions where additional details need to be sorted.
Default.UpdateObjectives = function(handler, block)
  local print = lprint
  local info = block.info
  print('   |cFF00FF00default objectives routine', block:GetName())

  -- reset the starting positions
  block.attachmentHeight = 0
  block.endPoint = block.titlebg
  local completionScore, completionMax = 0, 0
  local displayObjectiveHeader = false


  --- The first line is going to be used no matter what, so it is hard-pulled.
   -- It also ensures that we're in the right position for cleaning up the leftover lines.
  local lineIndex = 1
  local line = T.GetLine(handler, block, lineIndex)

  block.numLines = 0
  block.attachmentHeight = 0

  if info.description and #info.description >= 1 then
    print('   |cFF00FFFF  header line:|r', info.description)
    line.status:SetText(info.description)
    line.height = floor(line.status:GetStringHeight()+.5) + textSpacing
    if line.widget then
      line.widget:Hide()
    end
    T.AddLine(block, line)

    lineIndex = lineIndex + 1
    line = T.GetLine(handler, block, lineIndex)
  end

  if (info.isComplete == true or info.isComplete == nil) and info.completionText then
    print('     overriding line #1 for completion text:', info.completionText)
    line.status:SetText(info.completionText)
    line.height = floor(line.status:GetStringHeight()+.5) + textSpacing
    if line.widget then
      line.widget:Hide()
    end
    T.AddLine(block, line)
  else
    if info.objectives then
      for i, data in ipairs(info.objectives) do
        print('     |cFF88FF00#', i, data.type, data.text)
        displayObjectiveHeader = true
        line.height = 0
        handler:UpdateLine(block, line, data)

        -- For progressbar and timer lines, status text may be used as the title heading
        if line.widget then
          local widgetPosition = 0
          --- WIDGET POSITION -------------------------------------------------
          line.widget:SetPoint('TOP', line, 'TOP', 0, -widgetPosition)
          line.widget:Show()
          line.height = line.widget:GetHeight() + textSpacing
          ---------------------------------------------------------------------
          if line.displayText and #line.displayText >= 1 then
            widgetPosition = line.status:GetHeight() + textSpacing
            line.status:SetText(line.displayText)
            line.height = floor(line.status:GetStringHeight()+.5) + textSpacing + line.widget.height
            print('      - progressbar has text, adjust')
          end
        elseif line.displayText then
          line.status:SetText(line.displayText)
          line.height = floor(line.status:GetStringHeight()+.5)
        end

        T.AddLine(block, line)

        --print('      sz', line:GetWidth(), line:GetHeight(), 'pt', line:GetPoint(1))
        --print('     |cFF44BBFF#', i, 'anchoring line, size:', line.height, 'current endpoint:', line.statusbg)

        lineIndex = lineIndex + 1
        line = T.GetLine(handler, block, lineIndex)
      end
    end
  end

  while (block.lines[lineIndex+1]) do
    print('      - hide |cFFFF0088'..lineIndex..'|r')
    block.lines[lineIndex]:Hide()
    lineIndex = lineIndex +1
  end



  if lineIndex > 0 then
    block.attachmentHeight = block.attachmentHeight + textSpacing * 2
    print('     |cFF00FF00attachment:', block.attachmentHeight)
  end


  --[[
  local lines = handler.lines[block.index]
  if lines and #lines > block.numLines then
    print('   |cFFFF008' .. (#lines - block.numLines) .. ' extra lines to hide.')
    for  i = block.numLines + 1, #lines do
      print('    hide', i, lines[i]:GetName())
      lines[i]:Hide()
    end
  end
  ]]


  if debug then
    for i, region in ipairs(block.debug) do
      for j = 1, region:GetNumPoints() do
        local _, target = region:GetPoint(j)
        if target:IsVisible() then
          region:Hide()
        else
          region:Show()
        end
      end
    end
  end

  block.completionScore = completionScore / completionMax
end


--- Module-specific display variables
-- * height   - height of whatever display widget is involved in conveying the task
-- * money    - boolean that determines listening for money events or not
-- * progress - number ranging 0 to 2 indicating none/partial/full completion respectively



Bonus.UpdateLine = function(handler, block, line, data)
  local info = block.info
  local print = lprint


  line.displayColor = 'FFFFFF'
  line.displayText = data.text
  line.progress = 0
  print('  ', data.objectiveIndex,'|cFFFF0088-|r', data.objectiveType, data.text)
  if data.objectiveType == 'progressbar' then
    line.widgetType = 'ProgressBar'
    print('    |cFFFF44DDpercent='..tostring(GetQuestProgressBarPercent(info.questID)))
    data.value = GetQuestProgressBarPercent(info.questID) or 0
    data.maxValue = 100
    if  data.value >= data.maxValue then
      line.progress = 1
    elseif data.value > 0 then
      line.progress = 2
    end
    line.format = PERCENTAGE_STRING
    local widget = T.SetWidget(line, data, 'ProgressBar', info.questID..'-'..data.objectiveIndex)
    print('    |cFFFF0022** text:|r', data.text, '|cFFFF0022value:|r', data.value, '|cFFFF0022max:|r', data.maxValue)
    widget:SetPoint('TOP', line, 'TOP', 0, 0)

    line.widget = widget
    line.height = widget.height
  else
    line.displayText = data.text
    line.widget = nil
  end
  return line
end

Cheevs.UpdateLine = function(handler, block, line, data)
  local print = B.print('CheevsLine')
  line.progress = 0
  print('  ', data.objectiveIndex,'|cFF0088FF-|r', data.objectiveType, data.text)
  if data.flags then
    if band(data.flags, 0x00000001) > 0 then
      line.format = "%d/%d"
      line.widget = T.SetWidget(line, data, 'ProgressBar', data.criteriaID)
      line.height = line.widget.height
    elseif band(data.flags, 0x00000002) then
      line.widget = nil
    else
      line.widget = nil
      line.displayColor = 'FFFFFF'
      line.displayText = line.text

    end
  else

      line.displayText = data.text
  end
  print('line.type =', data.type)
  print('  ** qtyStr:', data.quantityString, 'qty:', data.quantity, 'assetID:', data.assetID)
end
Default.UpdateLine = function(block, line)
  if line.finished then
    line.progress = 2
  elseif line.quantity > 0 then
    line.progress  = 1
  else
    line.progress = 0
  end
  return line
end


T.Quest.numButtons = 0
local usedButtons = T.Quest.itemButtons
local freeButtons = T.Quest.freeButtons
--[=[
T.UpdateWrapper = function(reason)
  print('|cFF00FFFFUpdateWrapper:|r', reason)
  unitLevel = UnitLevel('player')
  wrapperWidth = T.Conf.Wrapper.WrapperWidth
  scrollWidth = T.Conf.Wrapper.WrapperWidth
  local wrapperBlocks = 0
  -- Update scroll child vertical size
  scrollHeight = 0
  for i, handler in ipairs(orderedHandlers) do
    T.UpdateTracker(handler)
    local frame = handler.frame
    if handler.actualBlocks >= 1 then
      frame:SetParent(Scroll)
      frame:SetPoint('TOPLEFT', Scroll, 'TOPLEFT', 0, - scrollHeight)
      frame:SetSize(wrapperWidth, frame.height)
      print('|cFF00FFFF'..frame:GetName()..'|r h:|cFF00FF00', frame.height, '|r y:|cFF00FF00', -scrollHeight)
      scrollHeight = scrollHeight + frame.height
      frame:Show()
    else
      frame:Hide()
    end
    wrapperBlocks = wrapperBlocks + handler.actualBlocks
  end
  print('final scrollHeight:', scrollHeight)



  -- Update frame dimensions
  if scrollHeight > wrapperMaxHeight then
    print('  is larger than', wrapperMaxHeight)
    wrapperHeight = wrapperMaxHeight
  else
    wrapperHeight = scrollHeight
    B.Conf.ObjectiveScroll = 0
  end
  scrollWidth = floor(scrollWidth+.5)
  scrollHeight = floor(scrollHeight+.5)
  wrapperWidth = floor(wrapperWidth+.5)
  wrapperHeight = floor(wrapperHeight+.5)
  headerHeight = floor(headerHeight+.5)

  if wrapperBlocks >= 1 then
    for i, region in ipairs(Wrapper.headerComplex) do
      region:Show()
    end
  else
    for i, region in ipairs(Wrapper.headerComplex) do
      region:Hide()
    end
    return
  end
  --[[wrapperHeight = scrollHeight

  print('|cFFFFFF00params:|r scroller:', scrollWidth .. ',' .. scrollHeight, 'scroll:', scrollWidth .. ',' .. scrollHeight,
  'wrapper:', wrapperWidth .. ',' .. wrapperHeight,
  'header:', headerHeight)]]

  --Scroller:SetSize(wrapperWidth, wrapperHeight)
  Scroller:SetPoint('TOPLEFT', Wrapper, 'TOPLEFT', 0, 0)
  Scroller:SetPoint('BOTTOMRIGHT', Wrapper, 'BOTTOMRIGHT')


  Scroll:SetSize(scrollWidth, scrollHeight)
  Scroll:SetPoint('TOPLEFT', Scroller, 'TOPLEFT', 0, B.Conf.ObjectiveScroll or 0)
  Scroll:SetPoint('RIGHT', Scroller, 'RIGHT')

  --Scroller:UpdateScrollChildRect()
  Wrapper:SetSize(wrapperWidth, wrapperHeight)

  --[[ update action buttons
  print('|cFF00FF00'..Scroll:GetName()..'|r:', Scroll:GetWidth(), Scroll:GetHeight(),
    '|cFF00FF00'..Scroller:GetName()..'|r:', Scroller:GetWidth(), Scroller:GetHeight(),
    '|cFF00FF00'..Wrapper:GetName()..'|r:', Wrapper:GetWidth(), Wrapper:GetHeight(),
    '|cFF0088FFvScrollRange|r:', floor(Scroller:GetVerticalScrollRange()+.5)
  )
  --]]
  T.UpdateActionButtons()

end
--]=]


Default.Update = function (self, reason, ...)
  local print = tprint
  local tracker = self.frame
  local blockIndex = 0
  local trackerHeight = headerHeight

  tracker.title:SetFont(headerFont, headerSize, headerOutline)
  tracker.titlebg:SetHeight(headerHeight)
  tracker.title:SetTextColor(unpack(headerColor))
  --tracker.titlebg:SetGradientAlpha(unpack(headerbg))

  self.currentAnchor = tracker.titlebg
  local numWatched = self:GetNumWatched()
  local numBlocks = self.numBlocks
  local actualBlocks = 0
  for watchIndex = 1, 25 do
    blockIndex = blockIndex + 1
    if watchIndex <= numWatched then
      local info = self:GetInfo(watchIndex)
      if info then
        local currentBlock = self:UpdateBlock(blockIndex, info)
        currentBlock:SetPoint('TOPLEFT', self.currentAnchor, 'BOTTOMLEFT', 0, 0)
        currentBlock:SetPoint('RIGHT', tracker,'RIGHT', 0, 0)
        self.currentAnchor = currentBlock
        print('    |cFFFFFF00'..watchIndex..'|r', '|cFF00FF00'..currentBlock:GetName()..'|r', currentBlock.height, trackerHeight)
        trackerHeight = trackerHeight + currentBlock.height
        numBlocks = max(numBlocks, watchIndex)
        actualBlocks = actualBlocks + 1
      else
        print('    |cFFFF0000bad GetInfo data for #'..watchIndex)
      end

    elseif watchIndex <= numBlocks then
      local used = self.usedBlocks
      local free = self.freeBlocks
      print('clean up dead quest block')
      if used[blockIndex] then
        used[blockIndex]:Hide()
        used[blockIndex]:ClearAllPoints()
        free[#free+1]= used[blockIndex]
        used[blockIndex] = nil
      end
    else
      print('  |cFFFF9900END|r @', blockIndex)
      break -- done with quest stuff
    end
  end
  self.numWatched = numWatched
  self.numBlocks = numBlocks
  self.actualBlocks = actualBlocks

  tracker.previousHeight = tracker.height
  if numBlocks >= 1 then
    previousBlock = nil
    if tracker.isEmpty then
      tracker.headerFade:Play()
      tracker.isEmpty = nil
    end
    tracker.height = trackerHeight
    tracker:SetHeight(tracker.height)
    tracker:Show()
  else
    tracker.isEmpty = true
    tracker.height = 0
    tracker:Hide()
  end

  return tracker.numWatched, tracker.numAll
end

--- Updates the selected block frame to display the given info batch
-- If `previousBlock` is set, it will attempt to anchor to that
-- @param blockNum the ordered block to be updated, not a watchIndex value
-- @param info the reference returned by the GetXInfo functions
-- REMEMBER: t.info and questData[questID] are the same table
Default.UpdateBlock = function (handler, blockIndex, info)
  local print = bprint
  print('  Read list item |cFF00FFFF'..blockIndex..'|r')
  if not blockIndex or not info then
    return
  end
  local frame = handler.frame
  local block = T.GetBlock(handler, blockIndex)
  block.handler = handler
  block.info = info
  block.mainStyle = info.mainStyle or 'Normal'
  block.subStyle = info.subStyle

  info.blockIndex = blockIndex
  if info.questID then handler.QuestBlock[info.questID] = block end
  if info.questLogIndex then handler.LogBlock[info.questLogIndex] = block end
  if info.watchIndex then handler.WatchBlock[info.watchIndex] = block end
  handler.BlockInfo[blockIndex] = info

  block.endPoint = block.titlebg
  block.attachmentHeight = 0
  handler:UpdateObjectives(block)

  block.title:SetText(info.title)
  local titleHeight = floor(block.title:GetHeight()+.5)
  local statusHeight = floor(block.status:GetHeight()+.5)
  local attachmentHeight =floor(block.attachmentHeight + .5)
  local titlebgHeight = titleHeight + titleSpacing*2
  local statusbgHeight = statusHeight + textSpacing*2

  block.titlebg:SetHeight(titlebgHeight)


  print('    |cFF0088FFanchor to', handler.currentAnchor:GetName())

  print('    |cFF00FF00total sizes:')
  print('       attachment:', attachmentHeight)
  print('            title:', titlebgHeight, '('.. titleHeight..')')
  --block.titlebg:SetHeight(block.title:GetHeight() + T.Conf.Wrapper.TitleSpacing)
  block.height = titlebgHeight + attachmentHeight
  if statusHeight > 1 then
    block.height = block.height + statusbgHeight
    print('           status:', statusbgHeight, '('.. statusHeight..')')
  else
    print('      |cFFFF0088             skipped')
  end
  block:SetHeight(block.height)

  print('           |cFFFFFF00height|r:', block.height)
  print('  |cFF00FFFF)|r -> ', block, block:GetHeight())

  block:Show()

  if info.specialItem and not info.itemButton then
    print('    - |cFF00FFFFgenerating item button for info set')
    info.itemButton = T.SetItemButton(block, info)
  else
    --info.itemButton = nil
  end

  local tagPoint, tagAnchor, tagRelative = 'TOPRIGHT', block, 'TOPRIGHT'
  if info.rewardInfo then
    print('has immediate reward')
    if info.rewardInfo[1].type == 'currency' or info.rewardInfo[1].type == 'item' then
      block.icon:Show()
      block.iconLabel:SetText(info.rewardInfo[1].count)
      block.icon:SetPoint(tagPoint, tagAnchor, tagRelative, -2, -2)
      tagPoint, tagAnchor, tagRelative = 'TOPRIGHT', block.icon, 'TOPLEFT'
      block.icon:SetTexture(info.rewardInfo[1].texture)
    end
  else
    block.icon:Hide()
  end

  if info.selected then
    block.SelectionOverlay:Show()
  else
    block.SelectionOverlay:Hide()
  end
  -- workaround for scrollchild issue where layers fall out of sync: in this case, it's by 1 vertical pixel
  --block.highlight:SetPoint('TOPLEFT', block, 'TOPLEFT', 0, 1)
  --block.lowlight:SetPoint('BOTTOMLEFT', block, 'BOTTOMLEFT', 0, 1)

  tagPoint, tagAnchor, tagRelative = T.AddTag(block, 'frequencyTag', tagPoint, tagAnchor, tagRelative)
  tagPoint, tagAnchor, tagRelative = T.AddTag(block, 'typeTag', tagPoint, tagAnchor, tagRelative)
  tagPoint, tagAnchor, tagRelative = T.AddTag(block, 'completionTag', tagPoint, tagAnchor, tagRelative)

  return block
end
----------
--- Top level methods

local tick = 0
function T:Update (reason, ...)
  tick = tick + 1
  local print = tprint
  reason = reason or OBJECTIVE_TRACKER_UPDATE_REASON
  local updateWrapper = 0
  local hasStuff
  local insertingStuff

  print(format('%d |cFFFF%04X Update()', tick, lshift(reason, 4)), reason, ...)
  currentPosition = 0
  anchorPoint = 'TOP'
  anchorFrame = Scroll

  local wrapperHeight = 0
  for id, handler in pairs(T.orderedHandlers) do
    local frame = handler.frame

    print(format('|cFF00FFFF%s and(%04X vs %04x+%04x) = %04X|r', handler.name, reason, handler.updateReasonModule, handler.updateReasonEvents, band(reason, handler.updateReasonModule + handler.updateReasonEvents)))
    if band(reason, handler.updateReasonModule + handler.updateReasonEvents) > 0 then
      handler:Update(reason, ...)
      print(' |cFF00FF00'..id..'|r', handler.displayName, 'count:', handler.numWatched)
      insertingStuff = true
    else
      print(' |cFFFF0088'..id..'|r', 'no reason to update')
    end

    if handler.numWatched >= 1 then
      hasStuff = true
      currentPosition = currentPosition + 1
      frame:SetParent(Scroll)
      frame:SetPoint('TOP', anchorFrame, anchorPoint, 0, 0)
      print('  |cFF00BBFFpinning to', anchorFrame:GetName(), anchorPoint)
      anchorFrame = handler.frame
      anchorPoint = 'BOTTOM'

      print('current frame height:', frame.height)
      wrapperHeight = wrapperHeight + frame.height
      print('|cFFFF0088total height:', wrapperHeight)
    else
      handler.frame:Hide()
    end
  end


  if  hasStuff or insertingStuff then
    print('updating height to', wrapperHeight)
    Wrapper:SetHeight(wrapperHeight)
    Scroller:SetHeight(wrapperHeight)
    Scroll:SetHeight(wrapperHeight)
    Scroller:SetVerticalScroll(B.Conf.ObjectiveScroll or 0)
    print('|cFFFF8800Wrapper:', Wrapper:GetSize())
    for i = 1, Wrapper:GetNumPoints() do
      print(' ', Wrapper:GetPoint(i))
    end
    print('  |cFF00FFFFScroller:', Scroller:GetSize())
    for i = 1, Scroller:GetNumPoints() do
      print(' ', Scroller:GetPoint(i))
    end
    print('  |cFF00FFFFScroll:', Scroll:GetSize())
    for i = 1, Scroll:GetNumPoints() do
      print(' ', Scroll:GetPoint(i))
    end

    Wrapper:Show()
    Scroller:Show()
    Scroll:Show()
  end
  Quest.GetClosest()
  --T.UpdateActionButtons(reason)
end

--- Queue any active item buttons for update for that frame
local iprint = B.print('ItemButton')
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.questLogIndex) 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

T.UpdateBlockAction = function (block, itemButton)
  local print = iprint
  print('**|cFF0088FF'..itemButton:GetName(), '|r:Update()')
  if itemButton.questID ~= block.info.questID then
    print('** |cFFFF0088mismatched block assignment', itemButton.questID,'<~>', block.info.questID)
    -- something happened between this and last frame, go back and set new probes
    return T.UpdateActionButtons()
  end

  local previousItem = itemButton.previousItem
  local upper_bound = Scroller:GetTop() + Scroller.snap_upper
  local lower_bound = Scroller:GetBottom() + Scroller.snap_lower + itemButtonSize
  local point, anchor, relative

  if block:GetBottom() < lower_bound then
    print('** ',block:GetName() ,'|cFFFFFF00bottom =', floor(block:GetBottom()+.5), 'threschold =', floor(lower_bound+.5))
    if previousItem then
      print('adjusting', previousItem:GetName())
      previousItem:ClearAllPoints()
      previousItem:SetPoint('BOTTOM', itemButton, 'TOP', 0, itemButtonSpacing)
    end
    itemButton:ClearAllPoints()
    itemButton.x = Wrapper:GetLeft() -4
    itemButton.y = Wrapper:GetBottom()
    point, anchor, relative = 'BOTTOMRIGHT', UIParent, 'BOTTOMLEFT'
    Scroller.snap_lower = Scroller.snap_lower + itemButtonSize + itemButtonSpacing

  elseif block:GetTop() > upper_bound then
    print('** ',block:GetName() ,'|cFFFFFF00top =', floor(block:GetTop()+.5), 'threschold =', floor(upper_bound+.5))
    itemButton:ClearAllPoints()
    if previousItem then
      print('latch onto another piece')
      point, anchor, relative ='TOP', previousItem, 'BOTTOM'
      itemButton.x = 0
      itemButton.y = -itemButtonSpacing
    else
      print('latch at corner', Scroller:GetLeft() -itemButtonSpacing, Scroller:GetTop())
      point, anchor, relative = 'TOPRIGHT', UIParent, 'BOTTOMLEFT'
      itemButton.x = Scroller:GetLeft() -4
      itemButton.y = Scroller:GetTop()
    end
    itemButton:Show()
    Scroller.snap_upper = Scroller.snap_upper - (itemButtonSize + itemButtonSpacing)
  else
    print('** ',block:GetName() ,'|cFF00FF00span =', floor(block:GetBottom()+.5), floor(block:GetTop()+.5), 'threschold =', floor(lower_bound+.5))
    itemButton:ClearAllPoints()
    itemButton.x = block:GetLeft() - itemButtonSpacing
    itemButton.y = block:GetTop()
    point, anchor, relative = 'TOPRIGHT', UIParent, 'BOTTOMLEFT'
  end

  itemButton:SetPoint(point, anchor, relative, itemButton.x, itemButton.y)
  itemButton:Show()
end

T.UpdateItemButtonCooldown = function(button)

end

local unitLevel = UnitLevel('player')