Creating a Busy Indicator in LiveCode Builder

One of my favorite aspects of creating Widgets with LiveCode Builder (LCB) is that I don’t have to create multiple image assets in order to support various display resolutions. Since it is possible to create very complex shapes using the new drawing routines I no longer have to resort to using PNG images exported from Photoshop for more complicated aspects of my UI.

Busy indicators are one control in particular that are quite a pain to manage. You needed to generate a PNG image for each frame in the animation and you had to create the animation for at least two sizes (1x and 2x). With widgets creating a busy indicator is much simpler and you get better results. Here are a couple of notes from a busy indicator that I just finished working on. You can get the source from my livecode-extensions github repository.

Any kind of shape

As I’ve discussed before, with widgets we can use SVG path data to create complex shapes. For my widget, I took four of the Font Awesome spinner icons and provided some default settings. You can also assign your own SVG path data to the widget via the iconSVGPath property that I added.

Scale to any size

Because the widget is using SVG path data to draw the animated icon, you can scale the widget to any size and it will look great. Here is an LCB handler I wrote based on some code in the Skia library. It will resize a path to a given size while maintaining the aspect ratio.

-- Translated from some Skia code
private handler scaleAndMaintainAspectRatioTransform(in pSrcBounds as Rectangle, in pDestBounds as Rectangle) returns Transform
	// Prepare values for matrix transformation
	variable isLarger as Boolean
	variable sX as Number
	variable sY as Number

	put false into isLarger
	put the width of pDestBounds / the width of pSrcBounds into sX
	put the height of pDestBounds / the height of pSrcBounds into sY

	if sX > sY then
		put true into isLarger
		put sY into sX
		put sX into sY
	end if

	variable tX as Number
	variable tY as Number

	put the left of pDestBounds - (the left of pSrcBounds*sX) into tX
	put the top of pDestBounds - (the top of pSrcBounds*sY) into tY

	variable tDiff as Number

	if isLarger then
		put my width - (the width of pSrcBounds*sY) into tDiff
		put my height - (the height of pSrcBounds*sY) into tDiff
	end if

	// align center
	divide tDiff by 2

	if isLarger then
		add tDiff to tX
		add tDiff to tY
	end if

	// create transformation matrix and apply
	variable tTransform as Transform
	put transform with matrix [sX, 0, 0, sY, tX, tY] into tTransform

	return tTransform
 end handler

The handler takes a source rectangle, e.g. the bounding box of tPath (where tPath is a Path variable in LCB), and a destination rectangle. The destination rectangle is how big you want the shape to be. The handler returns a Transform that can then be applied to the path.

Assuming that mSVGPath is a path variable with valid instructions assigned to it, here is how you would scale mSVGPath to fit within a 16×16 rectangle.

transform mSVGPath by scaleAndMaintainAspectRatioTransform(the bounding box of mSVGPath, rectangle [0,0,16,16])

Rotating around any point by changing the origin

One problem you run into when rotating a path is that sometimes the center of the bounding box of the path is not the point that you want to rotate around. The “spinner” shape in Font Awesome faces this issue. Look at the image below. On the left is the original bounding box of the shape. The cross-hair represents the center of the bounding box. Notice how the middle of the bounding box is not the center of the circles on the left and right. If you were to rotate around the center point of the bounding box the animation would jiggle.

On the right is the bounding box of the shape after rotating it 45 degrees clockwise. Notice how the bounding box size has changed as well as the center point. If you were to rotate the shape around the center of the current bounding box each time, the shape would jump around on the screen.

Changing-Bounding-Box-with-RotationIn order to address these issues, we need to always rotate around the same point, which may not be the center point. In order to do that I wrote this handler (with some help from this site) which will rotate a path around a specified point:

private handler calculateRotatedPath(inout pPath as Path, in pRotation as Number, in pCenter as Point)
	variable tX as Number
	variable tY as Number
	variable tOriginX as Number
	variable tOriginY as Number
	variable tMatrix as List
	variable tA as Number
	variable tB as Number
	variable tC as Number
	variable tD as Number

	-- Clockwise: sin for B, -sin for C
	-- Counterclockwise: -sin for B, sin for C
	variable tAngle as Number
	put (pRotation mod 360)/360*2*pi into tAngle
	put cos(tAngle) into tA -- scale the x
	put sin(tAngle) into tB -- scale the y
	put -sin(tAngle) into tC -- skew the x
	put cos(tAngle) into tD -- skew the y
	put the x of pCenter into tOriginX
	put the y of pCenter into tOriginY

	put tOriginX - tOriginX*tA - tOriginY*tC into tX
	put tOriginY - tOriginX*tB - tOriginY*tD into tY	

	variable tTransform as Transform
	put transform with matrix [tA,tB,tC,tD,tX,tY] into tTransform
	transform pPath by tTransform
end handler

Here is an animated GIF showing the different shapes and animations. bush-indicator-animation-example


Leave a Reply

Your email address will not be published. Required fields are marked *