View Single Post
02-12-16, 02:59 PM   #37
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
I wanted to post here because I came up with a technique based on the one outlined in this topic which can be applied to any source texture, rather than relying on semi-circles.

You start by arranging 4 textures in a grid, setting their texture coordinates so they look like a single copy of the original image.

Now our goal is to drain (or fill) each quarter by slicing the original texture from its center to one of its edges.

We can use a rotation animation to get the angle we want, but of course this also rotates our texture so it doesn't line up with the rest of the image.

However, we can use SetTexCoord to rotate our texture in the opposite direction to the rotation animation to correct its orientation.

Here's an animation to illustrate the basic concept: http://gfycat.com/BriefShowyElephantseal

The red square is where the scrollframe would normally be used to clip the edges of our rotating texture.

And this picture shows the finished design with various fill directions: http://gfycat.com/RealisticBaggyDarklingbeetle

I'll leave the script I wrote here.
Lua Code:
  1. -- Usage:
  2. -- spinner = CreateSpinner(parent)
  3. -- spinner:SetTexture('texturePath')
  4. -- spinner:SetBlendMode('blendMode')
  5. -- spinner:SetVertexColor(r, g, b)
  6. -- spinner:SetClockwise(boolean) -- true to fill clockwise, false to fill counterclockwise
  7. -- spinner:SetReverse(boolean) -- true to empty the bar instead of filling it
  8. -- spinner:SetValue(percent) -- value between 0 and 1 to fill the bar to
  9.  
  10. -- Some math stuff
  11. local cos, sin, pi2, halfpi = math.cos, math.sin, math.rad(360), math.rad(90)
  12. local function Transform(tx, x, y, angle, aspect) -- Translates texture to x, y and rotates about its center
  13.     local c, s = cos(angle), sin(angle)
  14.     local y, oy = y / aspect, 0.5 / aspect
  15.     local ULx, ULy = 0.5 + (x - 0.5) * c - (y - oy) * s, (oy + (y - oy) * c + (x - 0.5) * s) * aspect
  16.     local LLx, LLy = 0.5 + (x - 0.5) * c - (y + oy) * s, (oy + (y + oy) * c + (x - 0.5) * s) * aspect
  17.     local URx, URy = 0.5 + (x + 0.5) * c - (y - oy) * s, (oy + (y - oy) * c + (x + 0.5) * s) * aspect
  18.     local LRx, LRy = 0.5 + (x + 0.5) * c - (y + oy) * s, (oy + (y + oy) * c + (x + 0.5) * s) * aspect
  19.     tx:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
  20. end
  21.  
  22. -- Permanently pause our rotation animation after it starts playing
  23. local function OnPlayUpdate(self)
  24.     self:SetScript('OnUpdate', nil)
  25.     self:Pause()
  26. end
  27.  
  28. local function OnPlay(self)
  29.     self:SetScript('OnUpdate', OnPlayUpdate)
  30. end
  31.  
  32. local function SetValue(self, value)
  33.     -- Correct invalid ranges, preferably just don't feed it invalid numbers
  34.     if value > 1 then value = 1
  35.     elseif value < 0 then value = 0 end
  36.    
  37.     -- Reverse our normal behavior
  38.     if self._reverse then
  39.         value = 1 - value
  40.     end
  41.    
  42.     -- Determine which quadrant we're in
  43.     local q, quadrant = self._clockwise and (1 - value) or value -- 4 - floor(value / 0.25)
  44.     if q >= 0.75 then
  45.         quadrant = 1
  46.     elseif q >= 0.5 then
  47.         quadrant = 2
  48.     elseif q >= 0.25 then
  49.         quadrant = 3
  50.     else
  51.         quadrant = 4
  52.     end
  53.    
  54.     if self._quadrant ~= quadrant then
  55.         self._quadrant = quadrant
  56.         -- Show/hide necessary textures if we need to
  57.         if self._clockwise then
  58.             for i = 1, 4 do
  59.                 self._textures[i]:SetShown(i < quadrant)
  60.             end
  61.         else
  62.             for i = 1, 4 do
  63.                 self._textures[i]:SetShown(i > quadrant)
  64.             end
  65.         end
  66.         -- Move scrollframe/wedge to the proper quadrant
  67.         self._scrollframe:SetAllPoints(self._textures[quadrant])   
  68.     end
  69.  
  70.     -- Rotate the things
  71.     local rads = value * pi2
  72.     if not self._clockwise then rads = -rads + halfpi end
  73.     Transform(self._wedge, -0.5, -0.5, rads, self._aspect)
  74.     self._rotation:SetRadians(-rads)
  75. end
  76.  
  77. local function SetClockwise(self, clockwise)
  78.     self._clockwise = clockwise
  79. end
  80.  
  81. local function SetReverse(self, reverse)
  82.     self._reverse = reverse
  83. end
  84.  
  85. local function OnSizeChanged(self, width, height)
  86.     self._wedge:SetSize(width, height) -- it's important to keep this texture sized correctly
  87.     self._aspect = width / height -- required to calculate the texture coordinates
  88. end
  89.  
  90. -- Creates a function that calls a method on all textures at once
  91. local function CreateTextureFunction(func, self, ...)
  92.     return function(self, ...)
  93.         for i = 1, 4 do
  94.             local tx = self._textures[i]
  95.             tx[func](tx, ...)
  96.         end
  97.         self._wedge[func](self._wedge, ...)
  98.     end
  99. end
  100.  
  101. -- Pass calls to these functions on our frame to its textures
  102. local TextureFunctions = {
  103.     SetTexture = CreateTextureFunction('SetTexture'),
  104.     SetBlendMode = CreateTextureFunction('SetBlendMode'),
  105.     SetVertexColor = CreateTextureFunction('SetVertexColor'),
  106. }
  107.  
  108. local function CreateSpinner(parent)
  109.     local spinner = CreateFrame('Frame', nil, parent)
  110.    
  111.     -- ScrollFrame clips the actively animating portion of the spinner
  112.     local scrollframe = CreateFrame('ScrollFrame', nil, spinner)
  113.     scrollframe:SetPoint('BOTTOMLEFT', spinner, 'CENTER')
  114.     scrollframe:SetPoint('TOPRIGHT')
  115.     spinner._scrollframe = scrollframe
  116.    
  117.     local scrollchild = CreateFrame('frame', nil, scrollframe)
  118.     scrollframe:SetScrollChild(scrollchild)
  119.     scrollchild:SetAllPoints(scrollframe)
  120.    
  121.     -- Wedge thing
  122.     local wedge = scrollchild:CreateTexture()
  123.     wedge:SetPoint('BOTTOMRIGHT', spinner, 'CENTER')
  124.     spinner._wedge = wedge
  125.    
  126.     -- Top Right
  127.     local trTexture = spinner:CreateTexture()
  128.     trTexture:SetPoint('BOTTOMLEFT', spinner, 'CENTER')
  129.     trTexture:SetPoint('TOPRIGHT')
  130.     trTexture:SetTexCoord(0.5, 1, 0, 0.5)
  131.    
  132.     -- Bottom Right
  133.     local brTexture = spinner:CreateTexture()
  134.     brTexture:SetPoint('TOPLEFT', spinner, 'CENTER')
  135.     brTexture:SetPoint('BOTTOMRIGHT')
  136.     brTexture:SetTexCoord(0.5, 1, 0.5, 1)
  137.    
  138.     -- Bottom Left
  139.     local blTexture = spinner:CreateTexture()
  140.     blTexture:SetPoint('TOPRIGHT', spinner, 'CENTER')
  141.     blTexture:SetPoint('BOTTOMLEFT')
  142.     blTexture:SetTexCoord(0, 0.5, 0.5, 1)
  143.    
  144.     -- Top Left
  145.     local tlTexture = spinner:CreateTexture()
  146.     tlTexture:SetPoint('BOTTOMRIGHT', spinner, 'CENTER')
  147.     tlTexture:SetPoint('TOPLEFT')
  148.     tlTexture:SetTexCoord(0, 0.5, 0, 0.5)
  149.    
  150.     -- /4|1\ -- Clockwise texture arrangement
  151.     -- \3|2/ --
  152.  
  153.     spinner._textures = {trTexture, brTexture, blTexture, tlTexture}
  154.     spinner._quadrant = nil -- Current active quadrant
  155.     spinner._clockwise = true -- fill clockwise
  156.     spinner._reverse = false -- Treat the provided value as its inverse, eg. 75% will display as 25%
  157.     spinner._aspect = 1 -- aspect ratio, width / height of spinner frame
  158.     spinner:HookScript('OnSizeChanged', OnSizeChanged)
  159.    
  160.     for method, func in pairs(TextureFunctions) do
  161.         spinner[method] = func
  162.     end
  163.    
  164.     spinner.SetClockwise = SetClockwise
  165.     spinner.SetReverse = SetReverse
  166.     spinner.SetValue = SetValue
  167.    
  168.     local group = wedge:CreateAnimationGroup()
  169.     local rotation = group:CreateAnimation('Rotation')
  170.     spinner._rotation = rotation
  171.     rotation:SetDuration(0)
  172.     rotation:SetEndDelay(1)
  173.     rotation:SetOrigin('BOTTOMRIGHT', 0, 0)
  174.     group:SetScript('OnPlay', OnPlay)
  175.     group:Play()
  176.    
  177.     return spinner
  178. end
  179.  
  180. ----------
  181. -- Demo
  182. ----------
  183.  
  184. local spinner1 = CreateSpinner(UIParent)
  185. spinner1:SetPoint('BOTTOMRIGHT', UIParent, 'CENTER', -2, 2)
  186. spinner1:SetSize(64, 64)
  187. spinner1:SetTexture('interface/icons/inv_mushroom_11')
  188.  
  189. spinner1:SetClockwise(false)
  190. spinner1:SetReverse(false)
  191.  
  192. local spinner2 = CreateSpinner(UIParent)
  193. spinner2:SetPoint('BOTTOMLEFT', UIParent, 'CENTER', 2, 2)
  194. spinner2:SetSize(64, 64)
  195. spinner2:SetTexture('interface/icons/inv_mushroom_11')
  196.  
  197. spinner2:SetClockwise(true)
  198. spinner2:SetReverse(false)
  199.  
  200. local spinner3 = CreateSpinner(UIParent)
  201. spinner3:SetPoint('TOPRIGHT', UIParent, 'CENTER', -2, -2)
  202. spinner3:SetSize(64, 64)
  203. spinner3:SetTexture('interface/icons/inv_mushroom_11')
  204.  
  205. spinner3:SetClockwise(true)
  206. spinner3:SetReverse(true)
  207.  
  208. local spinner4 = CreateSpinner(UIParent)
  209. spinner4:SetPoint('TOPLEFT', UIParent, 'CENTER', 2, -2)
  210. spinner4:SetSize(64, 64)
  211. spinner4:SetTexture('interface/icons/inv_mushroom_11')
  212.  
  213. spinner4:SetClockwise(false)
  214. spinner4:SetReverse(true)
  215.  
  216. local f = CreateFrame('frame')
  217. local timespent = 0
  218. f:SetScript('OnUpdate', function(self, elapsed)
  219.     timespent = timespent + elapsed
  220.     if timespent >= 3 then
  221.         timespent = 0
  222.     end
  223.    
  224.     local value = timespent / 3
  225.     spinner1:SetValue(value)
  226.     spinner2:SetValue(value)
  227.     spinner3:SetValue(value)
  228.     spinner4:SetValue(value)
  229. end)
  Reply With Quote