view ObjectiveFrame.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 589de8ea05b9
children 7923243ae972
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 ipairs, max, min, unpack, floor, pairs, tostring, type = ipairs, max, min, unpack, floor, pairs, tostring, type
local IsResting, UnitXP, UnitXPMax, GetXPExhaustion = IsResting, UnitXP, UnitXPMax, GetXPExhaustion
local UnitLevel, IsQuestWatched, UIParent = UnitLevel, IsQuestWatched, UIParent
local CreateFrame = CreateFrame
local mod = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
local print = B.print('Objectives')
--------------------------------------------------------------------
--- Global frame layout
--------------------------------------------------------------------

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

--- Temp values set during updates
local wrapperWidth, wrapperHeight
local scrollWidth, scrollHeight
local previousBlock
local currentBlock
--- todo: map these into config table when its sorted out
local itemButtonSize, itemButtonSpacing =  36, 1
local titleFont, textFont = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Bold.ttf]], [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Regular.ttf]]
local titleSize, textSize = 15, 15
local titleOutline, textOutline = "OUTLINE", "OUTLINE"
local titleSpacing, textSpacing = 4, 3
local textIndent = 5
local wrapperMaxWidth, wrapperMaxHeight = 280, 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 headerFont, headerSize, headerHeight = [[Interface\Addons\SharedMedia_MyMedia\font\ArchivoNarrow-Bold.ttf]], 18, 24
local headerOutline, headerColor, headerSpacing = 'OUTLINE', {1,1,1,1}, 2
local wrapperPosition = {'RIGHT', UIParent, 'RIGHT', -84, 0}

--- These are mostly aesthetic choices so it lives here
mod.defaults = {
  ObjectiveTrackerAnchor = {'BOTTOM', 'RIGHT'},
  ObjectiveTrackerParent = 'DebuffButton',
  ObjectiveTrackerSize = {250, 600},
  ObjectiveWrapperParent = '',
  WrapperStyle = {
    Header = {
      Background = {Left = '', Right = '', Tile = ''},
      BackgroundCrop = {Left = {0, 0.4, 0,1}, Right={0.6,1,0,1}, Tile = {0.4,.6,0,1,}},
      BackgroundScale = {Left = 100, Right = 100},
      Font = {wrapperHeadFont, wrapperHeadSize, wrapperHeadOutline}
    }
  },
  ObjectiveHeaderStyle = {
    Normal = {
      Gradient = {MinColor = {.05,.15,0.5,0.7}, MaxColor = {.05,.15,0.5,.35}},
      Font = {headerFont, headerSize, headerOutline}, Spacing = headerSpacing,
    }
  },
  ObjectiveTrackerStyle = {
    Normal = {
      Title = {
        Gradient = { MinColor = {0.2, .4, 1, 0.45}, MaxColor = {.7, 0, 0.9, .19}},
        Font = {titleFont, titleSize, titleOutline}, Spacing = titleSpacing,
      },
      Text = {
        Gradient = { MinColor = {0.2, .4, 1, 0.25}, MaxColor = {.7, 0, 0.9, .12}},
        Font = {textFont, textSize, textOutline}, Spacing = textSpacing,
      },
    },
    Super = {
      Title = {
        Gradient = { MinColor = {0, .7, .6, .8}, MaxColor = {0, .7, .6, 0.2}},
        Font = {titleFont, titleSize, titleOutline},
        Spacing = titleSpacing, BackgroundFullWidth = true
      },
      Text = {
        Gradient = { MinColor = {0, .7, .6, 0.5}, MaxColor = {0, .7, .6, 0.1} },
        Font = {textFont, textSize, textOutline}, Spacing = textSpacing,
      },
    },
    Active = {
      Title = {
        Gradient = {          MinColor = {0.2, .4, 1, 1}, MaxColor = {0.2, .4, 1, .4},        },
        Font = {titleFont, titleSize, titleOutline},
        Spacing = titleSpacing,
        BackgroundFullWidth = true
      },
      Text = {
        Gradient = {          MinColor = {0.2, .4, 1, 1}, MaxColor = {0.2, .4, 1, .2},        },
        Font = {textFont, textSize, textOutline},
        Spacing = textSpacing,
        BackgroundFullWidth = true
      }
    },
    Complete = {
      Title = {
        Gradient = {          MinColor = {0, 1, 0, 0.34},          MaxColor = {0, 1, 0, .17},        },
        Font = {titleFont, titleSize, titleOutline},        Spacing = titleSpacing,
        BackgroundFullWidth = true
      },
      Text = {
        Gradient = {          MinColor = {0, 1, 0, .25},          MaxColor = {0, 1, 0, 0.12},        },
        Font = {textFont, textSize, textOutline},        Spacing = textSpacing,
        BackgroundFullWidth = true
      }
    },
    CheevNormal = {

      Title = {
        Gradient = { MinColor = {0.2, .4, 1, 0.45}, MaxColor = {.7, 0, 0.9, .19}},
        Font = {titleFont, titleSize, titleOutline}, Spacing = titleSpacing,
      },
      Text = {
        Gradient = { MinColor = {0.2, .4, 1, 0.25}, MaxColor = {.7, 0, 0.9, .12}},
        Font = {textFont, textSize, textOutline}, Spacing = textSpacing,
      },
    },
    CheevComplete = {

      Title = {
        Gradient = { MinColor = {0.2, .4, 1, 0.45}, MaxColor = {.7, 0, 0.9, .19}},
        Font = {titleFont, titleSize, titleOutline}, Spacing = titleSpacing,
      },
      Text = {
        Gradient = { MinColor = {0.2, .4, 1, 0.25}, MaxColor = {.7, 0, 0.9, .12}},
        Font = {textFont, textSize, textOutline}, Spacing = textSpacing,
      },
    }
  }
}

local FontBank = {
  ['Normal'] = VeneerCriteriaFontNormal,
  ['Progress'] = VeneerCriteriaFontProgress,
  ['Complete'] = VeneerCriteriaFontComplete,
  ['Failed'] = VeneerCriteriaFontFailed,
}

local Scroller_OnShow = function()
  Wrapper.watchMoneyReasons = 0;
  mod.UpdateWrapper()
  mod.SetEvents()
  for i, region in ipairs(Wrapper.header) do
    region:Show()
  end
end

local Scroller_OnHide = function()
  local self = Wrapper
  Wrapper:UnregisterAllEvents()
  Wrapper:SetScript('OnEvent', nil)
  for i, region in ipairs(Wrapper.header) 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()
  if r == 0 then return end
  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())

  mod.UpdateActionButtons('SCROLLING')
end

local WrapperCloseButton_OnClick = function(self)
  if Scroller:IsVisible() then
    Scroller:Hide()
  else
    Scroller:Show()
  end
end

mod.InitializeTrackers = function()

  local c = mod.defaults.ObjectiveHeaderStyle.Normal
  local g1, g2, g3, g4 = unpack(c.Gradient.MinColor)
  local h1, h2, h3, h4 = unpack(c.Gradient.MaxColor)

  for i, name in ipairs(orderedNames) do
    if not mod.orderedHandlers[i] then
      if mod.Tracker(name, i) then
        local handler = mod[name]

        local tracker = CreateFrame('Frame', 'Veneer'..name..'Tracker', Scroll, 'VeneerTrackerTemplate')
        tracker.header:SetText(handler.name)
        tracker.header:SetHeight(headerHeight)
        tracker.header:SetFont(unpack(c.Font))
        tracker.header:SetTextColor(unpack(headerColor))

        tracker.headerbg:SetGradientAlpha('HORIZONTAL', g1, g2 ,g3, g4, h1, h2, h3, h4)
        tracker.headerbg:SetHeight(headerHeight)

        handler.Tracker = tracker
        mod.orderedTrackers[i] = tracker
        mod.namedTrackers[name] = tracker
        mod.indexedTrackers[handler] = tracker
        print('created new tracker frames for new handler')
      end
    end
  end

  Scroller:SetScrollChild(Scroll)
  Scroller:SetScript('OnMouseWheel', Scroller_OnMouseWheel)
  Scroller:SetScript('OnShow', Scroller_OnShow)
  Scroller:SetScript('OnHide', Scroller_OnHide)
  Wrapper.close:SetScript('OnClick', WrapperCloseButton_OnClick)
  Wrapper:SetPoint(unpack(wrapperPosition))
  Scroller_OnShow(Scroller)

  mod.UpdateWrapperStyle()
end

mod.InitializeXPTracker = function()
  local XPBar = Wrapper.XPBar
  if UnitLevel('player') == 100 then
    XPBar:Hide()
    return
  end

  --- xp bar
  XPBar:SetWidth(wrapperWidth - Wrapper.close:GetWidth())
  XPBar.bg:SetAllPoints(XPBar)
  XPBar:RegisterEvent('DISABLE_XP_GAIN')
  XPBar:RegisterEvent('ENABLE_XP_GAIN')
  XPBar:SetScript('OnEvent', mod.UpdateXP)

  if not IsXPUserDisabled() then
    mod.EnableXP(XPBar)
  else
    mod.DisableXP(XPBar)
  end

  mod.UpdateXP(XPBar)
end

mod.EnableXP = function(self)
  self:RegisterEvent('PLAYER_XP_UPDATE')
  self:RegisterEvent('PLAYER_LEVEL_UP')
  self:RegisterEvent('PLAYER_UPDATE_RESTING')
  self.bg:SetTexture(0,0,0,.25)
  self:Show()
end

mod.DisableXP = function(self)
  self:UnregisterEvent('PLAYER_XP_UPDATE')
  self:UnregisterEvent('PLAYER_LEVEL_UP')
  self:UnregisterEvent('PLAYER_UPDATE_RESTING')
  self.bg:SetTexture(0.5,0.5,0.5,0.5)
  self:Hide()
end

mod.UpdateXP = function(self, event)
  if event == 'DISABLE_XP_GAIN' then
    mod.DisableXP(self)
  elseif event == 'ENABLE_XP_GAIN' then
    mod.EnableXP(self)
  end

  if not IsXPUserDisabled() then

    local xp = UnitXP('player')
    local xpmax = UnitXPMax('player')
    local rest = GetXPExhaustion()
    self.fg:SetWidth((xp/xpmax) * self:GetWidth())
    if rest then
      self.rested:ClearAllPoints()
      if xp == 0 then
        self.rested:SetPoint('TOPLEFT', self, 'TOPLEFT', 0, 0)
      else
        self.rested:SetPoint('TOPLEFT', self.fg, 'TOPRIGHT', 0, 0)
      end

      if (xp + rest) > xpmax then
        self.rested:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', 0, 0)
      else
        self.rested:SetWidth((rest/xpmax) * self:GetWidth())
      end
      self.rested:SetPoint('BOTTOM', self, 'BOTTOM')
      self.rested:Show()
    else
      self.rested:Hide()
    end

    if IsResting() then
      self.bg:SetTexture(.2,.8,.2,.5)
    else
      self.bg:SetTexture(0,0,0,.25)
    end
    self.xpText:SetText(xp .. '/'.. xpmax .. (rest and (' ('..tostring(rest)..')') or ''))
  end
end

mod.UpdateReputation = function(self)
end

--- Argument containers
local a1, a2, a3, a4, b1, b2, b3, b4, f1, f2, f3, w1, w2
mod.SetBlockStyle = function(block, name)
  -- var names intended to reflect argument order
  --@debug@
  local c = mod.defaults.ObjectiveTrackerStyle[name]
  --@end-debug@
  --[===[@non-debug
  local c = mod.Conf
  --@end-non-debug]===]
  a1, a2, a3, a4 = unpack(c.Title.Gradient.MinColor)
  b1, b2, b3, b4 = unpack(c.Title.Gradient.MaxColor)
  block.titlebg:SetGradientAlpha('HORIZONTAL', a1, a2, a3, a4, b1, b2, b3, b4)

  a1, a2, a3, a4 = unpack(c.Text.Gradient.MinColor)
  b1, b2, b3, b4 = unpack(c.Text.Gradient.MaxColor)
  block.bg:SetGradientAlpha('HORIZONTAL', a1, a2, a3, a4, b1, b2, b3, b4)

  f1, f2, f3 = unpack(c.Title.Font)
  block.title:SetFont(f1, f2, f3)

  f1, f2 ,f3 = unpack(c.Text.Font)
  block.objectives:SetFont(f1,f2,f3)

  w1 = Wrapper:GetWidth()
  w2 = (c.Title.BackgroundFullWidth and w1 or  block.title:GetStringWidth())

  local titleSpacing, titleSpacing2 = c.Title.Spacing, (c.Title.Spacing * 2)
  local textSpacing, textSpacing2 = c.Text.Spacing, (c.Text.Spacing * 2)

  if block.info.isTrivial then
    block.title:SetTextColor(0.7, 0.7, 0.7, 1)
  elseif block.info.isComplete then
    block.title:SetTextColor(1,1,1,1)
  else
    block.title:SetTextColor(0,.7,1,1)
  end
  block.title:SetSpacing(titleSpacing)
  block.objectives:SetSpacing(textSpacing)
  block.objectives:SetWordWrap(true)

  local titleHeight, textHeight = block.title:GetStringHeight(), block.objectives:GetStringHeight() + block.attachmentHeight
  local blockHeight = titleHeight + titleSpacing2 + textHeight + textSpacing2
  local blockWidth = wrapperMaxWidth

  block.titlebg:SetSize(min(w1, w2), titleHeight + titleSpacing2)
  block.bg:SetSize(w1, textHeight + textSpacing2)
  block:SetSize(blockWidth, blockHeight)

  block.title:SetPoint('TOPLEFT', block.titlebg, 'TOPLEFT', 0, -titleSpacing)
  block.objectives:SetPoint('TOPLEFT', block.titlebg, 'BOTTOMLEFT', textIndent, -textSpacing)

  -- store
  block.titleHeight = titleHeight
  block.textHeight = textHeight
  block.width = blockWidth
  block.height = blockHeight

  print('    |cFF00FFFF'..block:GetName()..'|r:|cFF0088FFSetStyle|r(', blockWidth, 'x', blockHeight, '(textH', textHeight,', titleH', titleHeight, ')')
end

local segments = {'Left', 'Right', 'Tile'}
mod.UpdateWrapperStyle = function()
  local c = mod.defaults.WrapperStyle
  for _, segment in ipairs(segments) do
    Wrapper['Background'..segment]:SetAtlas(c.Header.Background[segment])
    Wrapper['Background'..segment]:SetTexCoord(unpack(c.Header.BackgroundCrop[segment]))
    if c.Header.BackgroundScale[segment] then
    Wrapper['Background'..segment]:SetWidth(c.Header.BackgroundScale[segment])
    end
  end
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
mod.UpdateTrackerBlock = function (handler, blockIndex, info)
  print('  |cFF00FFFFUpdateTrackerBlock('..blockIndex..'|r')
  if not blockIndex or not info then
    return
  end

  local tracker = handler.Tracker

  local t = handler:GetBlock(blockIndex)
  if previousBlock then
    if blockIndex == 1 then
      t:SetPoint('TOPLEFT', previousBlock, 'TOPLEFT', 0, -headerHeight)
    else
      t:SetPoint('TOPLEFT', previousBlock, 'BOTTOMLEFT', 0, 0)
    end
    t:SetPoint('RIGHT', tracker,'RIGHT', 0, 0)
  end
  --print(t:GetName(), t:GetSize())
  --print(t:GetPoint(1))

  t.info = info

  if info.questID then handler.QuestBlock[info.questID] = t end
  if info.questLogIndex then handler.LogBlock[info.questLogIndex] = t end
  if info.watchIndex then handler.WatchBlock[info.watchIndex] = t end

  info.blockIndex = blockIndex
  handler.BlockInfo[blockIndex] = info

  t.Select = handler.Select
  t.Open = handler.Open
  t.Remove = handler.Remove
  t.Link = handler.Link
  t:SetScript('OnMouseUp', handler.OnMouseUp)
  t:SetScript('OnMouseDown', handler.OnMouseDown)
  t.title:SetText(info.title)

  t.attachmentHeight = 0
  if info.isComplete then
    t.objectives:Show()
    t.objectives:SetText(info.completionText)
  elseif info.numObjectives >= 1 then
    t.attachmentHeight = textSpacing
    t.objectives:Show()
    print('  - objective lines:', info.numObjectives, 'can wrap:', t.objectives:CanWordWrap())

    local text = ''

    --- todo: implement objective displays
     -- in an accumulator loop, call upon handler for the appropriate display frame, each defining:
      -- * height of whatever display widget is involved in conveying the task
      -- * number of non-wrapped text lines to account for line space; may be discarded depending on things
      -- * boolean that determines listening for money events or not
    t.attachmentHeight, text = mod.UpdateObjectives(t, info, text)

    t.objectives:SetText(text)
    t.objectives:SetWordWrap(true)



  elseif info.description then
    t.objectives:SetText(info.description)
    t.objectives:SetWordWrap(true)
  else
    t.objectives:SetText(nil)
  end
  local style = 'Normal'
  if info.isComplete then
    style = 'Complete'
  elseif info.superTracked then
    style = 'Super'
  end

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

  if Devian and Devian.InWorkspace() then
    t.debugText:Show()
    t.debugText:SetText(tostring(blockIndex) .. '\n' .. tostring(info.itemButton and info.itemButton:GetName()))
  end

  --- metrics are calculated in SetStyle
  t:SetStyle(style)
  t:Show()

  print('  |cFF00FFFF)|r -> ', t, t:GetHeight())
  return t
end

mod.UpdateObjectives = function(block, info, text)
  local attachmentHeight = block.attachmentHeight
  if info.description then
    print('  -- has description text:', select('#', info.description), info.description)
    text = info.description
  end
  for o, obj in ipairs(info.objectives) do
    --- achievement criteria
    if obj.flags then


      if bit.band(obj.flags, 0x00000001) > 0 then
        obj.type = 'ProgressBar'
      elseif bit.band(obj.flags, 0x00000002) then
        obj.type = 'Hidden'
        obj.widget = nil
      else
        obj.type = 'Text'
        obj.widget = nil
        text = text .. ((text == '') and "" or "\n") .. obj.text
      end

      print('obj.type =', obj.type)
      print('  ** qtyStr:', obj.quantityString, 'qty:', obj.quantity, 'assetID:', obj.assetID)
      obj.widget = mod.SetWidget(obj, info)
      --- none of the above (most quests)
    else
      local line = obj.text
      local color = '00FFFF'
      if obj.finished then
        color = 'FFFFFF'
      elseif obj.type == 'monster' then
        color = 'FFFF00'
      elseif obj.type == 'item' then
        color = '44DDFF'
      elseif obj.type == 'object' then
        color = 'FF44DD'
      end
      text = text .. ((text == '') and "" or "\n") .. '|cFF'..color.. line .. '|r'
    end

    if obj.widget then

      obj.widget:Show()
      obj.widget:SetPoint('TOPLEFT', block.objectives, 'BOTTOMLEFT', 0, -attachmentHeight)
      print('have a widget, height is', obj.widget.height)
      attachmentHeight = attachmentHeight + obj.widget.height
    end

  end
  return attachmentHeight, text
end

mod.UpdateTracker = function(handler)
  print('|cFF00FF88UpdateTracker(|r|cFFFF4400' .. type(handler) .. '|r :: |cFF88FFFF' .. tostring(handler) .. '|r')
  local tracker = handler.Tracker
  local blockIndex = 0
  local trackerHeight = headerHeight
  local w = 300

  previousBlock = handler.Tracker
  local numWatched = handler.GetNumWatched()
  local numBlocks = handler.numBlocks
  local actualBlocks = handler.actualBlocks
  for watchIndex = 1, 25 do
    blockIndex = blockIndex + 1
    if watchIndex <= numWatched then
      local info = handler:GetInfo(watchIndex)
      if info then
        local currentBlock = mod.UpdateTrackerBlock(handler, blockIndex, info)
        previousBlock = currentBlock
        trackerHeight = trackerHeight + currentBlock.height
        numBlocks = max(numBlocks, watchIndex)
        actualBlocks = actualBlocks + 1
      else
        print('|cFFFF0000Failed to draw info for index #'..watchIndex)
      end

    elseif watchIndex <= numBlocks then
      local used = handler.usedBlocks
      local free = handler.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('Stopping scan at', blockIndex)
      break -- done with quest stuff
    end
  end
  handler.numWatched = numWatched
  handler.numBlocks = numBlocks
  handler.actualBlocks = actualBlocks
  handler:Report()
  previousBlock = nil
  if numBlocks > 0 then
    tracker.height = trackerHeight
  else
    tracker.height = 0
  end

  print('|cFF00FF88)|r ->', numBlocks, 'blocks; height', tracker.height, 'last block: ')
end

mod.Quest.numButtons = 0
local usedButtons = mod.Quest.itemButtons
local freeButtons = mod.Quest.freeButtons
mod.UpdateWrapper = function()
  wrapperWidth = wrapperMaxWidth
  scrollWidth = wrapperWidth
  local wrapperBlocks = 0
  -- Update scroll child vertical size
  scrollHeight = 0
  for i, handler in ipairs(orderedHandlers) do
    mod.UpdateTracker(handler)
    if handler.actualBlocks >= 1 then
      local tracker = handler.Tracker
      print('setting', handler.Tracker, 'to anchor to offset', -scrollHeight)
      tracker:SetParent(Scroll) -- this doesn't do anything that relativeTo doesn't
      tracker:SetPoint('TOPLEFT', Scroll, 'TOPLEFT', 0, - scrollHeight)
      tracker:SetSize(wrapperWidth, tracker.height)
      print('adding ', tracker.height)
      scrollHeight = scrollHeight + tracker.height
    end
    wrapperBlocks = wrapperBlocks + handler.actualBlocks
  end
  print('final scrollHeight:', scrollHeight)



  -- Update frame dimensions
  if scrollHeight > wrapperMaxHeight then
    print('  is larger than', wrapperMaxHeight)
    --ScrollBar:Show()
    --scrollWidth = wrapperMaxWidth - scrollBarWidth
    wrapperHeight = wrapperMaxHeight
    -- Make ThumbTexture reflect the viewing scale (smaller for longer scroll, bigger for shorter)
    --ScrollBar:GetThumbTexture():SetHeight((wrapperMaxHeight/scrollHeight) * (wrapperMaxHeight))
    --ScrollBar:SetWidth(scrollBarWidth)
    --ScrollBar:SetPoint('TOPRIGHT', Scroller, 'TOPRIGHT', 0, 0)
    --ScrollBar:SetPoint('BOTTOMLEFT', Scroller, 'BOTTOMRIGHT', -scrollBarWidth, 0)
    --ScrollBar:SetMinMaxValues(1, scrollHeight - wrapperMaxHeight)
  else
    --ScrollBar:Hide()
    wrapperHeight = scrollHeight
  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.header) do
      region:Show()
    end
  else
    for i, region in ipairs(Wrapper.header) do
      region:Hide()
    end
    return
  end
  --wrapperHeight = scrollHeight

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

  Scroller:SetSize(wrapperWidth, wrapperHeight)
  Scroller:SetPoint('TOPLEFT', Wrapper, 'TOPLEFT', 0, -headerHeight)
  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 + headerHeight)

  -- 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)
  )
  mod.UpdateActionButtons('FULL_UPDATE')

  QuestPOIUpdateIcons()
end

--- Queue any active item buttons for update for that frame
mod.UpdateActionButtons = function(updateReason)
  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(usedButtons) do
    local info= mod.Quest.Info[questID]

    print('|cFF00FFFF'.. questID .. '|r', itemButton:GetName())
    local block = mod.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!')
            mod.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
    else
      print('  |cFFFF0088missing block data', itemButton:GetName())
      itemButton.previousItem = nil
      itemButton:Hide()
    end
  end
end

mod.UpdateBlockAction = function (block, itemButton)
  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 mod.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

mod.UpdateItemButtonCooldown = function(button)

end