view Turok/Layout/Layout.lua @ 9:9400a0ff8540

Ugh Timer: - container update directionality - talent update iterates over a non-volatile table to carry out updates - index management steps organized - talentRow status implemented, returns the spell associated with the talent chosen from that row CombatLog: - sort out font controls and unbork arguments
author Nenue
date Sun, 21 Feb 2016 13:08:30 -0500
parents a9b8b0866ece
children
line wrap: on
line source
--- LibFog-1.0
-- In-house solution to processing frame configuration data.
--[[
--
 Config Table fields:

 background .. _color, _texture, _blend       properties of the texture referenced by frame.background
 foregound  .. " " "                          properties of the texture referenced by frame.foreground
 backdrop                                     arguments for SetBackdrop
 border_color                                 sets the border color on an existing frame backdrop
 alpha, alpha_ooc                             uiobject alpha for in and out of combat
 combat_fade                                  true to change alpha levels based on combat
 fade_time, fade_in, fade_out                 fade_time = fade_in or fade_out

 anchor, parent, anchorTo, x, y               args for UIObject:SetPoint()
 height, width                                args for Region:SetSize()
 strata, layer, level                         override the strata, draw layer, and draw level where they apply

 padding                                      space between frame and background edge
 foreground_inset                             space between background and foreground edge
 spacing                                      space between segmented foreground elements (e.g. combo points)

 font, size, outline                          args for FontString:SetFont() respectively
 justifyV, justifyH                           LEFT/CENTER/RIGHT, TOP/MIDDLE/BOTTOM

 SetFrameLayout():
 Applies:
  anchor, parent, anchorTo, x, y
  width, height,
  padding, spacing, foreground_inset
  backdrop, border_color, background_color
  alpha, alpha_ooc

 SetStatusTextures():
 Applies:
  foreground_texture, foreground_color, foreground_blend
  background_texture, background_color, background_blend

 Defines:
  fill_direction, fill_width, fill_height, fill_inset
  :TranslateX(progress)
  :TranslateY(progress)
  :SetFillPoints()

 SetFontLayout():
 Applies:
  anchor, parent, anchorTo, x, y
  width, height
  font, size, outline
  justifyH, justifyV

 -
 ]]
local MAJOR, MINOR = "LibFog-1.0", 1
local F, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not F then return end

local _G, UIParent, type, unpack, pairs, ipairs, min, abs, tostring, print = _G, UIParent, type, unpack, pairs, ipairs, math.min, math.abs, tostring, print
local InCombatLockdown, GetTime, PlaySoundKitID, CreateFrame = InCombatLockdown, GetTime, PlaySoundKitID, CreateFrame
local tinsert, wipe, concat = table.insert, table.wipe, table.concat
local FADE_OUT_TIME, FADE_IN_TIME = 1, 0.4

--@debug@
local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool
local ADDON, scriptargs = ...
scriptargs.LibFog = F
local print = function(...)
  if _G.Devian and _G.DevianDB.workspace ~= 1 then
    --_G.print('Layout', ...)
  end
end
--- debug highlighters
local namef  = function(s) return '|cFFFFFF00' ..tostring(s).. '|r' end    -- uiobject targeted function
local namet  = function(s) return '|cFFFF4400 '..tostring(s).. '|r' end    --   region targeted function
local valuef = function(s) return '|cFF88FF88'..(s ~= nil and ('"'..tostring(s)..'"') or ('nil'))..'|r' end    -- uiobject name
local valuet = function(s) return '|cFFAACCAA'..(s ~= nil and ('"'..tostring(s)..'"') or ('nil'))..'|r' end    --   string value
local valuen = function(s) return '|cFFAADD77' ..(s ~= nil and ('"'..tostring(s)..'"') or ('nil')).. '|r' end    --   string enumeration
local valued = function(s) return '|cFFFFFFFF' ..(s ~= nil and ('"'..tostring(s)..'"') or ('nil')).. '|r' end    --   number
--@end-debug@
local GetPrint = function(trace)
  if trace then
    return print
  else
    return function() end
  end
end

--- Layout control metadata
local lastdb      -- fallback if a table isn't found mid-operation
local ui_meta = {
  beepkit = 15263, -- something to play when debugging
}
local ui_embeds = {
  ['Frame'] = 'SetFrameLayout',
  ['Texture'] = 'SetTextureLayout',
  ['Font'] = 'SetFontLayout',
}

--- Resolve a parent object from varying depths
-- @param self target object
-- @parent string/number name of global frame or number of GetParent() hops to make
-- @childKey index key to retrieve from the resolved parent object
local function ParentValue(self, parent, childKey)
  local print = GetPrint(self.trace)

  local relativeTo = parent
  if type(parent) == 'number' then
    print(cWord('* Parent Trace:'), cNum(parent), cKey(self:GetName()))
    relativeTo = self:GetParent()
    for i = 2, parent do
      if relativeTo.GetParent and relativeTo:GetParent() ~= UIParent then
        --@debug@
        print('  ', 'parent =', relativeTo:GetName())
        --@end-debug
        local next = relativeTo:GetParent()
        relativeTo = next
      else
        --@debug@
        print('  ',cWord(self:GetName()), 'parent =', cWord(parent))
        --@debug-end@
      end
    end
  end

  -- This allows us to call for an immediate sub-frame and still get a usable value if it's not there
  if childKey and relativeTo[childKey] then
    return relativeTo[childKey]
  end
  return relativeTo
end

--- Animations
F.animate_regions = {}
setmetatable(F.animate_regions, {
  __newindex = function (t,k,v)
    rawset(t,k,v)
    v.animationID = k
    F.SetAnimationGroup(v)
  end
})

--- Constructs the generic status frame
function F:SetFrameLayout(c)
  local print = GetPrint(c.trace)
  self.trace = c.trace or self.trace

  self.db = c
  self.labels = {}

  self.name = self:GetName()
  self.combat = InCombatLockdown()
  self:SetPoint(c.anchor, c.parent, c.anchorTo, c.x, c.y)
  self:SetSize(c.width, c.height)
  self:SetFrameStrata(c.strata)
  self.throttle_rate = 0.010
  self.throttle_point = GetTime()

  if c.backdrop then
    c.backdrop.edgeSize = c.padding*4
    self:SetBackdrop(c.backdrop)
  end
  if c.background_color and c.border_color then
    self:SetBackdropColor(unpack(c.background_color))
    self:SetBackdropBorderColor(unpack(c.border_color))
  end

  --@debug@
  print(namef('SetFrameLayout'),'(', valuet(self:GetName()),')')
  print(cText('  dimensions:'), cNum(c.width), 'by', cNum(c.height), '::', c.x, c.y,  c.anchor, c.anchorTo, c.parent)
  --@end-debug@

  self.width = c.width
  self.height = c.height
  self.foreground_inset = c.foreground_inset
  self.padding = c.padding
  self.spacing = c.spacing
  self.alpha = c.alpha

  self.combatFade = c.combatFade
  self.alpha_fade_in = c.alpha_fade_in or c.alpha_fade
  self.alpha_fade_out = c.alpha_fade_out or c.alpha_fade
  print('  fade on combat =', self.combatFade)
  for _, type in ipairs({'alpha', 'alpha_ooc'}) do
    self[type] = c[type]
    --_G.print('DB', self:GetName(), type, self[type])
    for _, subtype in ipairs({'_passive', '_active'}) do
      self[type..subtype] = c[type..subtype] or self[type]
      --_G.print('DB', self:GetName(), type..subtype, self[type..subtype])
      for _, subsubtype in ipairs({'_empty', '_half', '_full'}) do
        self[type..subtype..subsubtype] = c[type..subtype..subsubtype] or self[type..subtype]
        --_G.print('DB', self:GetName(), type..subtype..subsubtype, self[type..subtype..subsubtype])
        self[type..subsubtype] = c[type..subsubtype] or self[type..subtype]
        --_G.print('DB', self:GetName(), type..subsubtype, self[type..subsubtype])
      end
    end
  end
  self.UpdateAlpha = F.UpdateAlpha
  --if c.combatFade then
  self.faderID = #F.animate_regions+1
  F.animate_regions[#F.animate_regions+1] = self
  print(cText('  animation target ID #'), cNum(self.faderID))
  --end

  self:UpdateAlpha(InCombatLockdown())

  if self.lefttext then
    print('  auto-lefttext')
    F.SetFontLayout(self.lefttext, c.lefttext or c)
  end
  if self.righttext then
    print('  auto-righttext')
    F.SetFontLayout(self.righttext, c.righttext or c)
  end
end

--- Seeds basic animations, else a template can be specified by the frame data
function F:SetAnimationGroup(group)
  local print = GetPrint(self.trace)

  group = group or self.animationClass or nil
  --@debug@
  if not group then
    print('  |cFFFF44AAseeding debug animations', self:GetName())
    self.__flash = self:CreateAnimationGroup(self:GetName()..'Flasher')
    self.__flash:SetToFinalAlpha(true)
    local fade1 = self.__flash:CreateAnimation('Alpha')
    fade1:SetChange(-1)
    fade1:SetDuration(.6)
    fade1:SetOrder(1)
    local fade2 = self.__flash:CreateAnimation('Alpha')
    fade2:SetChange(1)
    fade2:SetDuration(.6)
    fade2:SetOrder(2)
    fade2:SetEndDelay(.6)
    --@debug@
    self.__flash.fade1 = fade1
    self.__flash.fade2 = fade2
    self.__flash:SetLooping('NONE')
    self.__flash:SetScript('OnFinished', function()
      print(self:GetName(), '[>>>] Done animating flash.')
      self.flashing = nil
    end)--@end-debug@
    self.Flash = F.Flash

    self.__fade = self:CreateAnimationGroup(self:GetName()..'Fader')
    self.__fade:SetToFinalAlpha(true)
    self.__fade:SetScript('OnFinished', function()
      print(self:GetName(), '[>>>] Done animating fade.')
      self.fading = nil
      local a = self.__flash.fade1:GetToAlpha()
      --self:SetAlpha(a)
      if a == 0 and not self.__fade.noHide then
        self:Hide()
      end
      if self.__fade.queuedFade then
        print('[>>>]  ', cWord(self:GetName())..'.'..cKey('__fade:'), 'starting queued fade')
        local fadeTo, fadeDuration, noHide = unpack(self.__fade.queuedFade)
        self.__fade.queuedFade = nil
        self:Fade(fadeTo, fadeDuration, noHide)
      end
    end)
    self.Fade = F.Fade
  end
  --@end-debug@
  if not self.animationID then
    tinsert(F.animate_regions, self) -- uses rawset, so won't death loop
    self.animationID = #F.animate_regions
  end
end

function F:Flash(duration, peak, valley, holdUp, holdDown)
  local print = GetPrint(self.trace)

  local fl = self.__flash
  print('[>>>]', self:GetName(), duration, peak, valley, holdUp, holdDown)
  fl.fade1:SetFromAlpha(valley)
  fl.fade1:SetToAlpha(peak)
  fl.fade1:SetDuration(duration)
  fl.fade1:SetEndDelay(holdUp)
  fl.fade2:SetFromAlpha(peak)
  fl.fade2:SetToAlpha(valley)
  fl.fade2:SetDuration(duration)
  fl.fade2:SetEndDelay(2)
  self.flashing = true
  fl:Play()
end

--- Fade to a value; fading to 0 will hide the frame
function F:Fade(duration, fadeTo, noHide)

  print('|cFFFF6600[>>>]', cText('Fade('), self:GetName(), cText(')'))
  local fader = self.__fade
  local fade1 = self.__flash.fade1
  if fader:IsPlaying() then
    self.__fade.queuedFade = {duration, fadeTo, noHide }
    print('    ', cText('playing:'), cNum(fader:GetDuration())..'s', cNum(fade1:GetToAlpha()))
    print('    ', cPink('queued {'),  cNum(duration)..'s', cNum(self:GetAlpha()), 'to', cNum(fadeTo), '}')
    return
  else
    print('    ', cNum('starting {'), cNum(duration), cNum(self:GetAlpha()), 'to', cNum(fadeTo), '}')
  end
  fade1:SetParent(fader)
  fade1:SetFromAlpha(self:GetAlpha())
  fade1:SetToAlpha(fadeTo)
  fade1:SetDuration(duration)
  fade1:SetOrder(1)
  self.__fade.noHide = noHide
  self.fading = true
  fader:Play()
end

function F:Beep()
  if self.quiet then
    return
  end
  PlaySoundKitID(F.beepkit) -- random anub'rhekan sounds
end


--- Defines the corners used in filling operations
local directionBase = {
  ['LEFT'] = 'TOPRIGHT',
  ['RIGHT'] = 'TOPLEFT',
  ['UP'] = 'BOTTOMLEFT',
  ['DOWN'] = 'TOPLEFT'
}
local directionPeak ={
  ['LEFT'] = 'BOTTOMLEFT',
  ['RIGHT'] = 'BOTTOMRIGHT',
  ['UP'] = 'TOPRIGHT',
  ['DOWN'] = 'BOTTOMRIGHT',
}
local directionPeakTo = {
  ['LEFT'] = 'BOTTOMRIGHT',
  ['RIGHT'] = 'BOTTOMLEFT',
  ['UP'] = 'BOTTOMRIGHT',
  ['DOWN'] = 'TOPRIGHT'
}
local anchorInverse = {['LEFT'] = 'RIGHT', ['RIGHT'] = 'LEFT', ['TOP'] = 'BOTTOM', ['BOTTOM'] = 'TOP' }
--- Directional coefficients for corner positions
-- [1] = end X, [2] = end Y, [3] = base X, [4] = base Y
local directionCoord = {
  ['LEFT']   = {-1,  0, -1, -1},
  ['RIGHT']  = { 1,  0, 1, -1},
  ['UP']     = { 0, 1, 1, 1},
  ['DOWN']   = { 0, -1, 1, -1},
  ['CENTER'] = {.5, .5, 1, 1}
}
local paddingScale = {
  ['UP'] = 1,
  ['DOWN'] = -1,
  ['LEFT'] = -1,
  ['RIGHT'] = 1,
}
local embedScale = {
  ['LEFT'] = {1, false},
  ['RIGHT'] = {-1, false},
  ['TOP'] = {false, -1},
  ['BOTTOM'] = {false, 1}
}

--- Determines the correct relative X offset for a given progress ratio
--- x = 0 + padding + spacing + i, where i = {embedded | icon_size +spacing}
-- Factors padding and spacing parameters, including icon embedding dimensions.
-- @param self frame on which the foreground/background textures and config data exist
-- @param ratio progess from 0 to 1; 0 returns the starting corner coordinate, 1 returns end corner
-- @param scale overrides the pixel fill length, with starting corner used as scaling origin
local TranslateX    = function(self, ratio, scale)
  if not scale then
    scale = self.fill_width
  end
  if self.fill_inverse then
    ratio = 1 - ratio
  end
  ----_G.print('Update', 'dx', self.fill_x)
  local x =
      scale * ratio * directionCoord[self.fill_direction][1]
  return min(x, scale) + self.fill_x
end

--- Determines the correct relative Y offset for a given progress ratio
-- Factors padding and spacing parameters, including icon embedding dimensions.
-- @param self frame on which the foreground/background textures and config data exist
-- @param ratio progess from 0 to 1; 0 returns the starting corner coordinate, 1 returns end corner
-- @param scale overrides the pixel fill length, with starting corner used as scaling origin
local TranslateY    = function(self, ratio, scale)
  if not scale then
    scale = self.fill_height
  end
  if self.fill_inverse then
    ratio = 1 - ratio
  end

  --_G.print('Update', 'dy', self.fill_y)
  -- (height of bar offset by the size of any overlapping element) * progress ratio, offset by the size of any overlapping embed + inset
  local y =
      scale * ratio * directionCoord[self.fill_direction][2] - self.foreground_inset
  return min(y, scale) + self.fill_y
end
local SetProgress = function(self, progress, scaleX, scaleY)
  --[[_G.print('Update', cText(self:GetName()),
    "\n     d:", progress, 'dX:', directionCoord[self.fill_direction][1], 'dY:', directionCoord[self.fill_direction][2], 'dI:', self.fill_inverse,
    "\n  peak:", self.fill_anchor, self.fill_anchorTo, TranslateX(self, progress), TranslateY(self,progress))
    --]]

  self.foreground:SetPoint(self.fill_anchor, self.background, self.fill_anchorTo, TranslateX(self, progress, scaleX), TranslateY(self, progress, scaleY))
end

--- Assigns textures to the generic foreground background bar regions
-- Called as F.SetStatusTextures(MyFrame, config pointer)
-- foreground_inset - number of pixels by which the foreground edges occlude the background (basically always negative)
-- padding - number of pixels between the background edge and frame edge (basically always positive)
function F:SetStatusTextures(c)
  local print = GetPrint(self.trace)

  if c == nil then
    c = self.db or lastdb
    if not c then
      error('No config table found')
    end
  end
  local relativeTo = ParentValue(self, c.parent, c.parentKey)


  self.fill_direction = c.fill_direction or 'RIGHT'
  self.fill_inset = c.padding - c.foreground_inset  -- foreground edge / frame edge
  self.fill_width = self.width - self.fill_inset*2       -- foreground left / right edges
  self.fill_height = self.height - self.fill_inset*2     --           " top / bottom "
  self.fill_inverse = c.fill_inverse
  self.fill_x, self.fill_y = 0, 0
  self.fill_insets = {
    LEFT = self.fill_inset,
    TOP = self.fill_inset,
    BOTTOM = self.fill_inset,
    RIGHT = self.fill_inset
  }


  -- create filling points
  self.fill_base     = directionBase[self.fill_direction]
  self.fill_anchor   =   directionPeak[self.fill_direction]
  self.fill_anchorTo =   directionPeakTo[self.fill_direction]
  print("   foreground:", self.fill_base, self.fill_base, TranslateX(self, 0), TranslateY(self, 0))
  print("   foreground fill:", self.fill_anchor, self.fill_anchorTo)

  -- calculate icon embed
  if self.icon and self.icon.embedded then
    print(cText('#### EMBED ####'))
    -- change anchor point so the icon is inside
    self.icon.anchor = self.icon.anchorTo
    print('   * Icon anchor:' , cText(self.icon.anchor), 'to', cText(self.icon.anchorTo))
    print('   * Fill start:', cText(self.fill_base))


    local coordSet = {nil, nil}
    local baseSet = {nil, nil }
    local endSet = {nil, nil}
    for dir, coords in pairs(embedScale) do
      if self.icon.anchor:match(dir) then
        print('   xtrans { matches', dir, 'include {', coords[1], coords[2], '}')
        if not coordSet[1] then coordSet[1] = coords[1] end
        if not coordSet[2] then coordSet[2] = coords[2] end

        -- the embedding position can overlap either corner
        if self.fill_base:match(dir) then
          print('   base corner also matches')
          if not baseSet[1] then baseSet[1] = coords[1] end
          if not baseSet[2] then baseSet[2] = coords[2] end
        else

          if not endSet[1] then endSet[1] = coords[1] end
          if not endSet[2] then endSet[2] = coords[2] end
        end
      end
    end

    -- make sure there are values if none of them matched at all for some reason
    coordSet = {coordSet[1] or 0, coordSet[2] or 0}
    baseSet = {baseSet[1] or 0, baseSet[2] or 0 }
    -- needs to produce a negative number
    endSet = {endSet[1] or 0, endSet[2] or 0 }


    print('  == xtrans push =', unpack(coordSet))
    print('  == fbase delta =', unpack(baseSet))
    print('  == ftail delta =', unpack(endSet))

    -- determine the foreground displacement

    self.icon_dx = (min(self.fill_width, self.fill_height) + self.spacing)
    self.icon_dy = (min(self.fill_width, self.fill_height) + self.spacing)
    print('   * Foreground compression:', cNum(self.icon_dx))

    self.icon_size = self.icon.size
    self.icon_x = self.padding * baseSet[1] - (self.icon.size - self.icon_dx)
    self.icon_y = self.padding * baseSet[2] - (self.icon.size - self.icon_dy)
    print('   * Icon dims:' , cNum(self.icon_size), ' offset:', cNum(self.icon_dx))


    local ofi = self.fill_insets[self.fill_direction]
    print('   * Fill inset('..cWord(self.fill_direction)..') from', cNum(ofi), 'to', cNum(self.fill_insets[self.fill_direction]))

    -- used to place the starting corner
    self.fill_x = (self.icon_dx) * baseSet[1]
    self.fill_y = (self.icon_dy) * baseSet[2]
    print('   * fill offset dX:', self.fill_x)
    print('   * fill offset dY:', self.fill_y)

    -- amount taken off of fill scale, subtract spacing
    self.icon_dx_cut = abs(self.icon_dx * -baseSet[1])
    self.icon_dy_cut = abs(self.icon_dy * -baseSet[2])

    local ofw, ofh = self.fill_width, self.fill_height
    self.fill_width = self.fill_width - self.icon_dx_cut
    self.fill_height = self.fill_height - self.icon_dy_cut
    print('   * Scale dX:', self.icon_dx_cut, cNum(ofw), 'to', cNum(self.fill_width))
    print('   * Scale dY:', self.icon_dy_cut, cNum(ofh), 'to', cNum(self.fill_height))

    self.icon:ClearAllPoints()
    self.icon:SetPoint(self.icon.anchor, self, self.icon.anchorTo, self.icon_x, self.padding * coordSet[2])
    self.icon:SetSize(self.icon_size, self.icon_size)
  end

  --@debug@
  print(namet('SetStatusTextures'),'(', valuef(self:GetName()), ')')
  print('   form:', self.padding,'-', self.foreground_inset, '+', self.fill_width, self.padding,'-', self.foreground_inset, '+','x', self.padding,'-', self.foreground_inset, '+', self.fill_height, self.padding,'-', self.foreground_inset, '+')
  print('  ', valuen(concat(c.background_color,', ')),valuet(c.background_texture), valuen(concat(c.foreground_color,', ')), valuet(c.foreground_texture))
  print('   background:', self.padding, self.padding, 'BOTTOMLEFT', 'BOTTOMLEFT' , '::', -self.padding, -self.padding, 'TOPRIGHT', 'TOPRIGHT')
  --@end-debug@

  self.background:ClearAllPoints()
  self.background:SetPoint('BOTTOMLEFT', self, 'BOTTOMLEFT', self.padding, self.padding)
  self.background:SetPoint('TOPRIGHT', self, 'TOPRIGHT', -self.padding, -self.padding)
  if c.background_texture ~= '' and c.background_texture ~= nil then
    self.background:SetTexture(c.background_texture)
    self.background:SetVertexColor(unpack(c.background_color))
  else
    self.background:SetTexture(unpack(c.background_color))
  end
  self.background:SetBlendMode(c.background_blend)

  if c.foreground_texture ~= '' and c.foreground_texture ~= nil then
    self.foreground:SetTexture(c.foreground_texture)
    self.foreground:SetVertexColor(unpack(c.foreground_color))
  else
    self.foreground:SetTexture(unpack(c.foreground_color))
  end
  self.foreground:SetBlendMode(c.foreground_blend)

  self.foreground:ClearAllPoints()
  local dx = (directionCoord[self.fill_direction][3] * -self.foreground_inset) + self.fill_x
  local dy = (directionCoord[self.fill_direction][4] * -self.foreground_inset) + self.fill_y
  print('   foreground base:', cNum(dx), cNum(dy))
  self.foreground:SetPoint(self.fill_base, self.background, self.fill_base, dx, dy)

  SetProgress(self, 1)

  self.TranslateX = TranslateX
  self.TranslateY = TranslateY
  self.SetFillPoints = SetFillPoints
  self.SetProgress = SetProgress
end
--- Sets properties for a generated FontString
-- Takes the FontString object as a self, and applies config values.
-- In particalur, the anchor parent is derived from either a fixed number of GetParent() or global name
-- @usage Region:SetFontLayout([table])
function F:SetFontLayout (c)
  local print = GetPrint(self.trace)

  if c == nil then
    c = lastdb
    if not c then
      error('No config table found')
    end
  end
  local relativeTo = ParentValue(self, c.parent, c.parentKey)

  --@debug@
  print(namet('FontLayout'),'(', valuet(self:GetName()), ')', valuet(c.font), valuet(c.size), valuet(c.outline))
  print('  ', valuen(c.anchor or 'CENTER'), relativeTo:GetName(), valuen(c.anchorTo or 'CENTER'), valued(c.x or 0),
    valued(c.y or 0))
  --@end-debug@
  self:SetPoint(
    c.anchor or 'CENTER',
    relativeTo,
    c.anchorTo or 'CENTER',
    c.x or 0,
    c.y or 0)
  self:SetSize(
    c.width or relativeTo:GetWidth(),
    c.height or relativeTo:GetHeight())
  self:SetFont(
    c.font,
    c.size,
    c.outline)
  self:SetJustifyH(c.justifyH or 'CENTER')
  self:SetJustifyV(c.justifyV or 'MIDDLE')
  self:SetTextColor(unpack(c.text_color or {1,1,1,1}))
end

--- For setting up widget pieces
function F:SetTextureLayout(c)
  local print = GetPrint(self.trace)

  if c == nil then
    c = lastdb
    if not c then
      error('No config table found')
    end
  end
  local relativeTo = ParentValue(self, c.parent, c.parentKey)

  self.size = c.size
  self.width = c.width
  self.height = c.height
  self.x = c.x
  self.y = c.y
  self.embedded = c.embedded
  self.anchor = c.anchor
  self.anchorTo = c.anchorTo

  self:SetPoint(c.anchor, relativeTo, c.anchorTo, c.x, c.y)
  self:SetSize(c.size or c.width, c.size or c.height)
  self:SetAlpha(c.alpha)
  self:SetDesaturated(c.desaturated or false)
  print('|cFF00FFFFSetTextureLayout(|r', self:GetName(), '|cFF00FFFF)|r')
  print('  ', c.anchor, relativeTo, c.anchorT, c.x, c.v)
  if c.combatFade and not (relativeTo.faderID and F.animate_regions[relativeTo.faderID]) then
    tinsert(F.animate_regions, self)
    self.faderID = #F.animate_regions
    --@debug@
    print('register fadeable texture #'..self.faderID)
    --@end-debug@
  end
end

local alphaSubType = {
  [1] = '_passive',
  [2] = '_active'
}
local alphaFillType = {
  [1] = '_empty',
  [2] = '_half',
  [3] = '_full',
}
function F:UpdateAlpha(inCombat, displayState, fillState)
  local print = function() end
  print(cWord('UpdateAlpha(')..self:GetName()..cWord(')'))
  local alphaType = inCombat and 'alpha' or 'alpha_ooc'
  local alphaDuration = inCombat and 'alpha_fade_in' or 'alpha_fade_out'
  local displayState = displayState or self.displayState or nil
  local fillState = fillState or self.fillState or nil

  local alphaSubType = ''
  if displayState then
    alphaSubType = (displayState == 1) and '_passive' or ((displayState == 2) and '_active' or '' )
  end

  local alphaFillType = ''
  if fillState then
    alphaFillType = (fillState == 1) and '_half' or ((fillState == 2) and '_full' or '_empty')
  end

  local fadeTo = self[alphaType..alphaSubType..alphaFillType]
  local fadeDuration = self[alphaDuration] or 0
  print(' alphaKey:', cWord(alphaType..alphaSubType..alphaFillType))
  print(' alphaTo:', cNum(fadeTo), 'duration:', cNum(fadeDuration))
  if self:IsVisible() and fadeDuration ~= 0 then
    self:Fade(fadeDuration, fadeTo or (inCombat and 1 or 0.5), true)
    print('   |cFFFFFF00  :Fade()|r', 'dur='..cNum(fadeDuration), cNum(fadeTo), cWord(self:GetName()))
  else
    self:SetAlpha(fadeTo)
    print('   |cFF00FF00  :SetAlpha('..fadeTo..')|r', cText(self:GetName()))
  end
end

--- Sets an OnUpdate within a time-throttled wrapper
-- throttle rate should be slightly smaller than 1/average frame rate
function F:SetFrameScript(updateFunc, showFunc, hideFunc, ...)
  local print = GetPrint(self.trace)

  if not self.__oldscripts then
    self.__oldscripts = {}
  end

  self:SetScript('OnUpdate', nil) -- clear any scripts
  self:SetSCript('OnUpdate', function(self)
    if GetTime() < self.throttle_time then
      return
    end
    self.throttle_time = self.throttle_time + self.throttle_rate
    updateFunc(self)
  end) -- put the new function in its place
  local changeSet = {
    { self.__oldscripts,onUpdate = self:GetScript('OnUpdate') },
  }

  if showFunc then
    tinsert(changeSet,
      {onShow = self:GetScript('OnShow')})
    self:SetScript('OnShow', showFunc)
  end

  if hideFunc then
    tinsert(changeSet,
      {onHide = self:GetScript('OnShow')})
    self:SetScript('OnHide', hideFunc)
  end
end

--- Embed
function F:Embed(object)
  print('Doing embed')
  for k, v in pairs(ui_embeds) do
    print('embedding Set'..k..'Layout')
    object['Set'..k..'Layout'] = self[v]
  end
  object.SetStatusTextures = self.SetStatusTextures

  --- map a generic layout method if embedded into a frame object
  if object.GetObjectType and ui_embeds[object.GetObjectType()] then
    if ui_embeds[object.GetObjectType()] then
      object.SetLayout = ui_embeds[object.GetObjectType()]
    end
  end
end