How can I make a pie timer? I need to be able to draw a circular arc with dynamically changing angles.

Hey everybody,

With thanks to toga for discovering a flaw in my module, I have corrected it so that the progressRing works as expected in all circumstances, including when content is being scaled (letterbox, zoomEven, zoomStretch), and if the progressRing is wider than the device’s screen width or height. You can download the corrected module here, and I’ve also updated the sample project available here. I’ve also updated the post above to include the corrected code.

Here’s what was happening: the progressRing utilizes a mask image to create the circular shape and “donut hole.” The mask image is procedurally generated, but previously it was not necessarily meeting the strict requirements for a mask image (height/width must be divisible by 4, and must have a 3-pixel black border on all sides) if the device’s width and height did not match the content width/height defined in your project’s config.lua. I’ve fixed this by making the mask file a fixed size that is not determined by the progressRing’s radius, but rather by the device’s screen size. Then the image mask is scaled appropriately to match up with the progressRing’s defined radius.

Thanks again toga for finding this flaw, and enjoy everybody!

Thanks a lot. I will try it tomorrow.

Really Really nice, good work!!!,

I will download this now!!

Great job

UPDATE: With daily build #2015.2544, Corona Labs fixed the finalize() bug that prevented objects’ finalize events from being triggered when their parent display group was removed. Woohoo! However, my progressRing module had a bug that only manifests itself now that finalize() is working again. I had utilized a workaround developed by @sergeylerg, but there was an error in my implementation of that fix that causes a crash when run on build 2015.2544 and later. I fixed that error by adding a 1ms timer when checking to see if finalize events are being triggered as they should. The finalize() fix is not necessary for up-to-date Corona builds, but I’m leaving it in for folks who might be running legacy Corona builds.

Please get the updated version of the progressRing module at http://www.jasonschroeder.com/2014/12/21/progress-ring-module-for-corona-sdk/

I’ve also updated the code that appears earlier in this thread to the latest version. Thanks Corona Labs for fixing that bug!

I’ve modularized an implementation of this (so you can create a “progressRing” with one line of code: “loal ring = progressRing.new()” ) and have been meaning to share it in a blog post. This will give me cause to finally get that done. Stay tuned. I’ll post a link here once that’s done. Give me maybe a day or two…I’ll want to clean up and comment my module before making it public.

I solved it using image masks. Not a cool solution.

I did this a while ago with 4 images for each quadrant, each with a rotating mask. Should be feasible to create a nice and clean class for this without too much effort. Regardless of the framework, it’s not a simple primitive shape, so never a straightforward thing to do.

local function newArc(startAngle, widthAngle, radius, xPos, yPos, color) startAngle = startAngle or 0 widthAngle = widthAngle or 90 radius = radius or 100 local vert = {} vert[#vert+1] = 0 vert[#vert+1] = 0 local minX, minY = 0,0 local initX, initY = radius \* math.cos(startAngle), radius \* math.sin(startAngle) if (initX \< minX) then minX = initX end if (initY \< minY) then minY = initY end for i = 0, widthAngle do local a = (startAngle+i)\*math.pi/180 local x, y = radius \* math.cos(a), radius \* math.sin(a) vert[#vert+1] = x vert[#vert+1] = y if (x \< minX) then minX = x end if (y \< minY) then minY = y end end local arc = display.newPolygon(xPos + minX, yPos + minY, vert) arc.anchorX, arc.anchorY = 0, 0 arc:setFillColor(color[1],color[2],color[3]) return arc end local startAngle = 270 local curAngle = 0 local radius = 100 local buffer = 25 local color1, color2 = {0,0,.8}, {0.4,0.4,.8} local xPos, yPos = display.contentCenterX, display.contentCenterY local arc1 = newArc(startAngle,curAngle,radius+buffer,xPos,yPos,color1); local arc2 = newArc(startAngle,curAngle,radius,xPos,yPos,color2); local function update() curAngle = curAngle + 1 if (curAngle == 360) then curAngle = 0 end arc1:removeSelf(); arc2:removeSelf(); arc1 = newArc(startAngle,curAngle,radius+buffer,xPos,yPos,color1); arc2 = newArc(startAngle,curAngle,radius,xPos,yPos,color2); timer.performWithDelay(10, update) end update()

Is this what you want? The hardest part, imo, is getting the polygon to stay in one place, which I added some code to the drawArc function.

Great work, I’m only wondering if the aliasing visible in the video is still there?

We have our own module using 4 masks and changing the corners position to simulate the progress, but the aliasing problem is not visible. I would gladly change to something less ‘hacky’ :wink:

Hi Krystian,

Unfortunately the method I use relied exclusively on Corona’s vector display objects (newCircle, newRect, newPolygon, etc.), which are not aliased (at least not yet). So if you need a softer edge, then I think your own workaround is probably the better bet, at least for now. 

I took a look, and I couldn’t find a feature request for anti-aliased vector objects. It’s probably worth making a request and trying to drum up some votes for it (I’d throw a couple votes at it). You can make a new request here: http://feedback.coronalabs.com/forums/188732-corona-sdk-feature-requests-feedback 

Thanks,

Jason

Hmmm

Have you seen this?

http://feedback.coronalabs.com/forums/188732-corona-sdk-feature-requests-feedback/suggestions/5034842-switchable-antialiasing-during-runtime

I’m now a bit confused about the aliasing.

BTW…

As an easy workaround for aliasing of vector graphics on corona, I used to create an object 3-4 times larger and scale it down.

It worked quite ok for me back when I needed it for some testing.

I could be wrong, but I was under the impression that anti-aliasing was removed a year or two ago and has not come back since. There is the option for changing the OpenGL Rendering mode to “linear” or “nearest neighbor,” but that only impacts images loaded from file, not vector objects. Here’s the request for that feature, and notes from David Rangel on its implementation (the request is marked as “completed”): http://feedback.coronalabs.com/forums/188732-corona-sdk-feature-requests-feedback/suggestions/3574881-nearest-neighbor-opengl-rendering-mode 

I know I said “give me a couple of days” like 3 weeks ago, but seriously - I’m very VERY close to having a nice fully-fleshed out module for this - hopefully it’ll be worth the wait. You can add a new progressRing by simply calling progressRing.new(), customize it in many ways very easily, and then start it, tell it to advance/retreat to any position (and specify how long it’ll take to get there), pause current movement, resume, reset, etc. It’s taken me a lot longer than I anticipated, but I think it’s nice and polished. The moment it’s available online, I’ll post a link here.

Thanks looking forward to it.

Me too.

How’s this:

[EDIT] Get this mathlib library too: http://code.coronalabs.com/code/mathliblua

require("mathlib") local function newCycle( x, y, inner, outer, from, range ) range = range or 1 if (range \< 1) then range = 1 elseif (range \> 360) then range = 360 end local centre = {x=0,y=0} local pt = {x=0, y=-(inner+(outer-inner)/2)} pt = math.rotateTo( pt, from, centre ) local path = {pt.x,pt.y} for i=1, range do pt = math.rotateTo( pt, 1, centre ) path[#path+1] = pt.x path[#path+1] = pt.y end local line = display.newLine( unpack( path ) ) line.strokeWidth = outer-inner line.x, line.y = x, y return line end local range = display.newGroup() range.i = 1 transition.to( range, { time=2000, i=271 } ) Runtime:addEventListener( "enterFrame", function() newCycle( 200, 100, 50, 100, 0, range.i ) end )

Actually, this is better:

[EDIT] Btw, you will need to download the mathlib library here: http://code.coronalabs.com/code/mathliblua

require("mathlib") local function newCycle( parent, x, y, inner, outer, from, range ) range = range or 1 if (range \< 1) then range = 1 elseif (range \> 360) then range = 360 end local centre = {x=0,y=0} local offsetY = -(inner+(outer-inner)/2) local pt = {x=0, y=offsetY} pt = math.rotateTo( pt, from, centre ) local path = {pt.x,pt.y} for i=1, range do pt = math.rotateTo( pt, 1, centre ) path[#path+1] = pt.x path[#path+1] = pt.y end local line = display.newLine( parent, unpack( path ) ) line.strokeWidth = outer-inner return line end local function newTimer( parent, x, y, inner, outer, colour, from, range, time ) local group = display.newGroup() group.x, group.y = x, y group.i = 1 local function render() if (group.numChildren \> 0) then group[1]:removeSelf() end local line = newCycle( group, 0, 0, inner, outer, from, group.i ) line:setStrokeColor( colour[1], colour[2], colour[3], colour[4] or 1 ) end local function onComplete() Runtime:removeEventListener( "enterFrame", render ) end transition.to( group, { time=time, i=range, onComplete=onComplete } ) Runtime:addEventListener( "enterFrame", render ) return group end newTimer( display.currentStage, 200, 100, 50, 100, {.5,.5,1}, 180, 181, 1000 ) display.newCircle( 200, 100, 5 )

@Horacebury: as usual, your solution is elegantly simple and effective! Mine has lots of options and I think is still a good option, but I don’t think I can compete with yours in terms of compactness. I think my module clocks in at around 250 lines, not including the commented-out documentation lines. …Of course, 50 of those 250 lines are to fix the finalize() bug (using @sergeylerg’s solution) so that I can automatically clean up my Runtime listeners if the ring object’s parent group is removed. Hope to post my module tonight … Between the full-time non-app job, two active client projects and twin 19-month olds, any extracurricular coding takes waaay longer than it should!

Hey, you know, it’s just time and repetition. I’ve had practice with this one a few weeks ago and that solution got real messy. This one just popped into my head as I’m having a good coding day and fortunately plenty of time.