Progress Ring Plugin for Corona

      

About the Plugin

The Progress Ring Plugin for Corona allows developers to add customizable circular "progress rings" to their Corona apps and games in as little as a single line of code. These rings can rotate to indicate a timer countdown, player/enemy health bars, or even be used in chart & graph demonstrations in a business app. It was designed to be lightweight and flexible, with good performance even on older mobile devices.

 

Adding the Plugin to your App

  1. Activate the plugin at the Corona Marketplace
  2. Add an entry into the plugins table of your project's build.settings file. Below is an example of a minimal build.settings file with the required entry for the progressRing plugin:
settings = {}

settings.plugins = {
  ['plugin.progressRing'] = {publisherId = 'com.schroederapps'},
}

 

Creating a Progress Ring

To create a progressRing object, first require the plugin into your app/scene:

local progressRing = require('plugin.progressRing')

Next, call progressRing.new(), which will return a progress ring object with the default parameters:

local myRing = progressRing.new()

NOTE: Progress rings are, at their core, just Corona GroupObjects. They can be rotated, repositioned, inserted into other group objects, and destroyed just like any other group object. Basically, if you can do it to a Corona group object, then you can do it to your progress ring.

 

Customizing Your Progress Ring

Calling progressRing.new() is the fastest way to add a progress ring to your app, but the returned object will always look the same, and may not suit your needs. But it's very easy to customize your ring by passing a table of parameters as an argument to progressRing.new(). This table can include any of the following key/value pairs. None are required:

key type value notes
parent Corona display group a variable name referencing a Corona display group to insert the ring into. Defaults to display.currentStage.
x number the x coordinate where the ring should be placed. Defaults to nil (effectively 0).
y number the y coordinate where the ring should be placed. Defaults to nil (effectively 0).
radius number the radius of your ring in Corona content units. Defaults to 100.
diameter number the diameter of your ring in Corona content units. If both radius and diameter are specified, diameter is given priority. Defaults to 200
ringDepth number a number between 0 and 1 representing the depth of your ring. A ringDepth of 1 will result in a fully-round ring (“all donut, no hole”), while a ringDepth of 0 would result in a practically invisible ring (“all hole, no donut”). Defaults to .33.
ringColor table a table containing 4 numbers between 0 and 1, representing the RGBA values of your ring’s bar color. Defaults to {1, .5, 0, 1}.
bgColor table a table containing 4 numbers between 0 and 1, representing the RBGA values of your ring’s background color. Defaults to {.7, .7, .7, 1}.
strokeColor table a table containing 4 numbers between 0 and 1, representing the RBGA values of your ring’s stroke color. If not specified, this value will default to whatever your ring's bgColor is.
strokeWidth number a number representing the width of your ring’s stroke in Corona content units. Defaults to 0.
counterclockwise boolean whether or not the ring should advance in a counter-clockwise manner. Defaults to false.
hideBG boolean whether or not the background should be invisible. Defaults to false.
time number the amount of time in milliseconds your ring will take to make a full 360-degree rotation. Defaults to 10000 (10 seconds).
position number a number between 0 and 1 representing the starting position of your progress bar. 0 would result in no visible progress bar (i.e. 0 degrees), whereas 1 would result in a full progress bar (i.e. 360 degrees). Defaults to 0.
bottomImage string path to an image file (i.e. images/bottom.png) that will appear “underneath” or “behind” your progress ring. Automatically supports dynamic image scaling (@2x, @3x, etc.). Defaults to nil.
topImage string path to an image file (i.e. images/top.png) that will appear “on top of” or “in front of” your progress ring. Automatically supports dynamic image scaling (@2x, @3x, etc.). Defaults to nil.

 


Object Methods & Properties

Calling progressRing.new() will create a progress ring object, but it's just going to sit there statically until you call one or more of the following methods on your created objects. Note that methods expect a reference to the ring object, so be sure to call them using a : colon operator, not a . dot, as indicated below.

 

ring:goTo(position, [time], [onComplete])

Advances or retreats the position of the progress ring to the position specified. Will cancel and override any currently-running goTo call. Upon completion, a progressRingEvent is fired with a phase of completed.

Accepts the following arguments:

key type value notes
position number a number between 0 and 1 representing the position of the bar should advance or retreat to. 0 would result in no visible progress bar (i.e. 0 degrees), whereas 1 would result in a full progress bar (i.e. 360 degrees). (required)
time number the number of milliseconds for the ring to advance or retreat to the specified position. If nil, the time is determined by the time value specified for a full rotation when the ring was created. For example, if a ring has a time value of 10000 for a full rotation, and you are moving the ring from position 0 to position .5, then it will take 5 seconds to advance by default. (optional)
onComplete function a function to call when the ring completes its advancement or retreat. A reference to the ring object will be passed as a single argument to the function. (optional)

Example:

local progressRing = require('plugin.progressRing')
local myRing = progressRing.new()

local function onMovementComplete(target)
  print('progress ring ' .. tostring(target) .. ' completed its rotation!')
end

myRing:goTo(.9, 3000, onMovementComplete)

 

ring:pause()

Pauses a progress ring in motion. Accepts no arguments.

Example:

local progressRing = require('plugin.progressRing')
local myRing = progressRing.new()

local function pauseThatRing()
  myRing:pause()
end

myRing:goTo(1) --start moving the ring (takes 10 seconds)
timer.performWithDelay(2000, pauseThatRing) --pause the ring after 2 seconds

 

ring:resume()

Resumes motion on a paused progress ring. Accepts no arguments.

Example:

local progressRing = require('plugin.progressRing')
local myRing = progressRing.new()

local function pauseThatRing()
  myRing:pause()
end

local function resumeThatRing()
  myRing:resume()
end

myRing:goTo(1) -- start moving the ring (takes 10 seconds)
timer.performWithDelay(2000, pauseThatRing) -- pause the ring after 2 seconds
timer.performWithDelay(4000, resumeThatRing) -- resume the motion 2 seconds later

 

ring:reset()

Returns ring to the starting position defined when the object was created. Cancels any currently-running goTo call. Accepts no arguments.

Example:

local progressRing = require('plugin.progressRing')
local myRing = progressRing.new()

local function resetMe()
  myRing:reset()
end

myRing:goTo(1, 2000, resetMe) -- do a full 2-second rotation, then reset

 

ring:setBgColor([r], [g], [b], [a])

Changes the background color of the ring object after it's already been created. accepts up to 4 numeric arguments indicating the RGBA values of the new background color.

Example:

local progressRing = require('plugin.progressRing')
-- create a ring object with a red background
local myRing = progressRing.new({
  bgColor = {1, 0, 0},
})

-- change the background color to blue after 2 seconds:
timer.performWithDelay(2000, function()
  myRing:setBgColor(0, 0, 1)
end)

 

ring:setRingColor([r], [g], [b], [a])

Changes the ring color of the ring object after it's already been created. accepts up to 4 numeric arguments indicating the RGBA values of the new ring color.

Example:

local progressRing = require('plugin.progressRing')
-- create a ring object with a red ring
local myRing = progressRing.new({
  ringColor = {1, 0, 0},
})

-- change the ring color to blue after 2 seconds:
timer.performWithDelay(2000, function()
  myRing:setRingColor(0, 0, 1)
end)

 

ring:setStrokeColor([r], [g], [b], [a])

Changes the stroke color of the ring object after it's already been created. accepts up to 4 numeric arguments indicating the RGBA values of the new stroke color.

Example:

local progressRing = require('plugin.progressRing')
-- create a ring object with a red stroke
local myRing = progressRing.new({
  strokeColor = {1, 0, 0},
})

-- change the stroke color to blue after 2 seconds:
timer.performWithDelay(2000, function()
  myRing:setStrokeColor(0, 0, 1)
end)

 

ring.position

A number between 0 and 1 indicating where the progress ring currently stands in its progression. Setting this value will cause the ring to instantly jump to that position.

Example:

local progressRing = require('plugin.progressRing')
local myRing = progressRing.new()

-- check ring's position:
print(myRing.position) -- >> 0

-- set ring's position
myRing.position = .75 -- ring will visually jump to the .75 position

 

ring.strokeWidth

A number representing the strokeWidth of the ring object. As with other Corona display objects, adjusting this value will instantly change the object's visuals.

Example:

local progressRing = require('plugin.progressRing')

-- create a ring object with a strokeWidth of zero
local myRing = progressRing.new({
  strokeWidth = 0
})

-- change the ring's strokeWidth to 10 after 2 seconds:
timer.performWithDelay(2000, function()
  myRing.strokeWidth = 20
end)

 


Adding Event Listeners

Progress Ring objects dispatch progressRingEvents at key moments that can be listened for and acted on. The event table that is dispatched contains the following key/value pairs:

key type value notes
name string will always be progressRingEvent
target reference a reference to the progress ring object that dispatched the event
phase string will be one of the following: completed, paused, resumed, or reset
position number a number between 0 and 1 indicating the ring's position when the event was dispatched.
time number the time value that was passed to a completed goTo call. Will always be nil when event.phase ~= 'completed'.

To listen for these events, add an event listener to your ring object as such:

local progressRing = require('plugin.progressRing')

local function ringEventListener(event)
  print('Ring event fired! Target: ' .. event.target .. '; Phase: ' .. event.phase)
end

local myRing = progressRing.new()
myRing:addEventListener('progressRingEvent', ringEventListener)
myRing:goTo(1)

 


Gotchas

Progress ring objects utilize multiple masks to create the "donut hole" effect, and have perfectly round edges. Corona has a nested masking limit of three. Because of this, progress ring objects with a ringDepth less than 1 cannot be inserted into masked groups, including display.newContainer, widget.newScrollView or widget.newTableView objects.

Rings with a ringDepth of 1 only use two nested masks because they don't have a 'hole' and can be inserted into masked groups, but only "one deep."