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 else 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 else 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 else 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.

In 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.