Just like fames can be moved up/down in frame layers using SetFrameStrata/SetFrameLevel, textures can be moved up/down using SetDrawLayer.
You can add frames and textures to do what you need, not just to the spinner but to other frames as well.
This is my target spinner example but using the same circle texture as a background, the spinner and an overlay to give it a ring look. It’s a rudementry example but there aren’t any other circle/ring type textures in the UI that I could find on a quick look…
-- Usage:
-- spinner = CreateSpinner(parent)
-- spinner:SetTexture('texturePath')
-- spinner:SetBlendMode('blendMode')
-- spinner:SetVertexColor(r, g, b)
-- spinner:SetClockwise(boolean) -- true to fill clockwise, false to fill counterclockwise
-- spinner:SetReverse(boolean) -- true to empty the bar instead of filling it
-- spinner:SetValue(percent) -- value between 0 and 1 to fill the bar to
-- Some math stuff
local cos, sin, pi2, halfpi = math.cos, math.sin, math.rad(360), math.rad(90)
local function Transform(tx, x, y, angle, aspect) -- Translates texture to x, y and rotates about its center
local c, s = cos(angle), sin(angle)
local y, oy = y / aspect, 0.5 / aspect
local ULx, ULy = 0.5 + (x - 0.5) * c - (y - oy) * s, (oy + (y - oy) * c + (x - 0.5) * s) * aspect
local LLx, LLy = 0.5 + (x - 0.5) * c - (y + oy) * s, (oy + (y + oy) * c + (x - 0.5) * s) * aspect
local URx, URy = 0.5 + (x + 0.5) * c - (y - oy) * s, (oy + (y - oy) * c + (x + 0.5) * s) * aspect
local LRx, LRy = 0.5 + (x + 0.5) * c - (y + oy) * s, (oy + (y + oy) * c + (x + 0.5) * s) * aspect
tx:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
end
-- Permanently pause our rotation animation after it starts playing
local function OnPlayUpdate(self)
self:SetScript('OnUpdate', nil)
self:Pause()
end
local function OnPlay(self)
self:SetScript('OnUpdate', OnPlayUpdate)
end
local function SetValue(self, value)
-- Correct invalid ranges, preferably just don't feed it invalid numbers
if value > 1 then value = 1
elseif value < 0 then value = 0 end
-- Reverse our normal behavior
if self._reverse then
value = 1 - value
end
-- Determine which quadrant we're in
local q, quadrant = self._clockwise and (1 - value) or value -- 4 - floor(value / 0.25)
if q >= 0.75 then
quadrant = 1
elseif q >= 0.5 then
quadrant = 2
elseif q >= 0.25 then
quadrant = 3
else
quadrant = 4
end
if self._quadrant ~= quadrant then
self._quadrant = quadrant
-- Show/hide necessary textures if we need to
if self._clockwise then
for i = 1, 4 do
self._textures[i]:SetShown(i < quadrant)
end
else
for i = 1, 4 do
self._textures[i]:SetShown(i > quadrant)
end
end
-- Move scrollframe/wedge to the proper quadrant
self._scrollframe:SetAllPoints(self._textures[quadrant])
end
-- Rotate the things
local rads = value * pi2
if not self._clockwise then rads = -rads + halfpi end
Transform(self._wedge, -0.5, -0.5, rads, self._aspect)
self._rotation:SetRadians(-rads)
end
local function SetClockwise(self, clockwise)
self._clockwise = clockwise
end
local function SetReverse(self, reverse)
self._reverse = reverse
end
local function OnSizeChanged(self, width, height)
self._wedge:SetSize(width, height) -- it's important to keep this texture sized correctly
self._aspect = width / height -- required to calculate the texture coordinates
end
-- Creates a function that calls a method on all textures at once
local function CreateTextureFunction(func, self, ...)
return function(self, ...)
for i = 1, 4 do
local tx = self._textures[i]
tx[func](tx, ...)
end
self._wedge[func](self._wedge, ...)
end
end
-- Pass calls to these functions on our frame to its textures
local TextureFunctions = {
SetTexture = CreateTextureFunction('SetTexture'),
SetBlendMode = CreateTextureFunction('SetBlendMode'),
SetVertexColor = CreateTextureFunction('SetVertexColor'),
}
local function CreateSpinner(parent)
local spinner = CreateFrame('Frame', nil, parent)
-- ScrollFrame clips the actively animating portion of the spinner
local scrollframe = CreateFrame('ScrollFrame', nil, spinner)
scrollframe:SetPoint('BOTTOMLEFT', spinner, 'CENTER')
scrollframe:SetPoint('TOPRIGHT')
spinner._scrollframe = scrollframe
local scrollchild = CreateFrame('frame', nil, scrollframe)
scrollframe:SetScrollChild(scrollchild)
scrollchild:SetAllPoints(scrollframe)
-- Wedge thing
local wedge = scrollchild:CreateTexture()
wedge:SetPoint('BOTTOMRIGHT', spinner, 'CENTER')
spinner._wedge = wedge
-- Top Right
local trTexture = spinner:CreateTexture()
trTexture:SetPoint('BOTTOMLEFT', spinner, 'CENTER')
trTexture:SetPoint('TOPRIGHT')
trTexture:SetTexCoord(0.5, 1, 0, 0.5)
-- Bottom Right
local brTexture = spinner:CreateTexture()
brTexture:SetPoint('TOPLEFT', spinner, 'CENTER')
brTexture:SetPoint('BOTTOMRIGHT')
brTexture:SetTexCoord(0.5, 1, 0.5, 1)
-- Bottom Left
local blTexture = spinner:CreateTexture()
blTexture:SetPoint('TOPRIGHT', spinner, 'CENTER')
blTexture:SetPoint('BOTTOMLEFT')
blTexture:SetTexCoord(0, 0.5, 0.5, 1)
-- Top Left
local tlTexture = spinner:CreateTexture()
tlTexture:SetPoint('BOTTOMRIGHT', spinner, 'CENTER')
tlTexture:SetPoint('TOPLEFT')
tlTexture:SetTexCoord(0, 0.5, 0, 0.5)
-- /4|1\ -- Clockwise texture arrangement
-- \3|2/ --
spinner._textures = {trTexture, brTexture, blTexture, tlTexture}
spinner._quadrant = nil -- Current active quadrant
spinner._clockwise = true -- fill clockwise
spinner._reverse = false -- Treat the provided value as its inverse, eg. 75% will display as 25%
spinner._aspect = 1 -- aspect ratio, width / height of spinner frame
spinner:HookScript('OnSizeChanged', OnSizeChanged)
for method, func in pairs(TextureFunctions) do
spinner[method] = func
end
spinner.SetClockwise = SetClockwise
spinner.SetReverse = SetReverse
spinner.SetValue = SetValue
local group = wedge:CreateAnimationGroup()
local rotation = group:CreateAnimation('Rotation')
spinner._rotation = rotation
rotation:SetDuration(0)
rotation:SetEndDelay(1)
rotation:SetOrigin('BOTTOMRIGHT', 0, 0)
group:SetScript('OnPlay', OnPlay)
group:Play()
return spinner
end
local spinner1 = CreateSpinner(UIParent)
spinner1:SetPoint('CENTER', UIParent)
spinner1:SetSize(64, 64)
spinner1:SetTexture("Interface\\Masks\\CircleMaskScalable")
spinner1:SetVertexColor(0, 1, 0)
spinner1:SetClockwise(false)
spinner1:SetReverse(false)
spinner1:Hide()
spinner1.Background = spinner1:CreateTexture()
spinner1.Background:SetDrawLayer("BACKGROUND", -1)
spinner1.Background:SetSize(84, 84)
spinner1.Background:SetPoint("CENTER")
spinner1.Background:SetTexture("Interface\\Masks\\CircleMaskScalable")
spinner1.Background:SetVertexColor(0.8, 0.2, 0.6)
local CenterOverlay = CreateFrame("Frame", nil, spinner1)
CenterOverlay:SetFrameLevel(10)
CenterOverlay:SetSize(34, 34)
CenterOverlay:SetPoint("CENTER")
CenterOverlay.Texture = CenterOverlay:CreateTexture()
CenterOverlay.Texture:SetAllPoints()
CenterOverlay.Texture:SetTexture("Interface\\Masks\\CircleMaskScalable")
CenterOverlay.Texture:SetVertexColor(0.2, 0.2, 0.8)
local healthmax = 0
local health = 0
spinner1:RegisterUnitEvent("UNIT_HEALTH", target)
spinner1:RegisterEvent("PLAYER_TARGET_CHANGED")
spinner1:SetScript("OnEvent", function(sxelf, event, ...)
if event == "PLAYER_TARGET_CHANGED" then
if not UnitExists("target") then
spinner1:Hide()
return
end
spinner1:Show()
end
healthmax = UnitHealthMax("target")
health = UnitHealth("target")
local h = health / healthmax
spinner1:SetValue(h)
end)