Write a Widget in 8 Steps
This lesson originally appeared as a blog post by Georgia Hutchings.
Ever thought that your LiveCode project could be improved by a custom control? Perhaps you want to add a rounded rectangle push button to your stack, where the border color is different to the text color? Maybe you’re thinking of building an app with 10 cards, and each card needs a header bar, and each header bar needs a label, a button, a background graphic, a line graphic… Getting tired just thinking about it? Enter widgets.
Widgets are controls that can be dragged from the tools palette and dropped onto your stack. In LiveCode 8, users now have the ability to write their own custom controls, or widgets, in the new LiveCode Builder language. The purpose of this article is to guide you through the widget writing process in LiveCode Builder, from the very start to using your end product in a LiveCode stack.
We are going to follow 8 steps to write a widget in LiveCode Builder:
- Create a .lcb File
- Saving and Loading the Widget’s Properties
- Handlers to Set the Widget’s Properties
- Creating and Drawing the Widget
- Defining Custom Handlers
- Testing, Packaging and Installing the Widget
- Use the Widget in a LiveCode Stack
The widget we will be writing is the speech bubble, as featured in the first app on the CreateIt with LiveCode course (check out this video for more information on the course).
Disclaimer: The following is intended towards the more advanced user, although all are welcome to try writing widgets, and have a go using LiveCode Builder in general!
First we need to select a text editor to write the code in. At the moment, LiveCode does not have a built in editor for .lcb files, but since they are plain text files they can be written in any text editor. I suggest using TextWrangler, which is available to download for free online. There is a custom LiveCode Builder color scheme which can be used in TextWrangler, which you can get here. Once you have downloaded the color scheme file, you need to put it in
~/Library/Application Support/BBEdit/Language Modules
or equivalent folder.
Next, create a folder “speechBubble” in which to save the .lcb file. When you build and package extensions, additional files that are associated with the extension are created. By saving the .lcb file in a folder, all additional files that are created will be saved in the same folder. Open TextWrangler, select the LiveCode Builder color scheme and save the file as “speechBubble.lcb” in the “speechBubble” folder. Now we can begin writing the widget!
The first thing we need to do is declare the type of extension we are writing. There are two types of extension in LiveCode Builder: widgets and libraries. Widgets are controls and libraries add new commands and functions to the engine. We are writing a widget and so we need to declare the extension as such.
-- declaring extension as widget, followed by identifier widget community.livecode.gohutchings.speechBubble
The widget declaration is followed by an identifier. An extension identifier should be in the form
where the username should be the username you use to log into the LiveCode extension store. Don’t have a username? Create one here.
In this widget, we have two dependencies: the com.livecode.canvas library, which specifies the syntax definitions and bindings for canvas drawing and operations in LiveCode Builder, and the com.livecode.widget library, which consists of the operations on widgets provided by LiveCode Builder.
use com.livecode.canvas use com.livecode.widget
This widget has two properties: the text that will be displayed in the speech bubble and the color of the speech bubble. For these two properties, we only need to declare “set” handlers. The properties’ values will be stored in private instance variables, which we will need to declare later.
property bubbleText get mText set setText property bubbleColor get mBubbleColor set setBubbleColor
In order to Package any extension in LiveCode Builder we need to add metadata which is required by the LiveCode store: the extension’s title, the author and a version number. If the required metadata is not added, then a number of warnings may be printed into the log field when building and packaging the extension.
metadata title is "Speech Bubble" metadata author is "Georgia Hutchings" metadata version is "1.0.0"
We also need to add some metadata for the properties declared above. For each property we need to set: the editor for the property in the property inspector of the widget; the default value that is displayed in the editor if the property has not been set yet.
For the property bubbleText, we want the property editor to be a string editor and the default value to be “Hello World”. For the property bubbleColor, we want to restrict the user to two options: blue and grey. To do this, we need to set the property editor to an enum editor. We also need to set the options available, as well as the default value.
metadata bubbleText.editor is "com.livecode.pi.string" metadata bubbleText.default is "Hello World" metadata bubbleColor.editor is "com.livecode.pi.enum" metadata bubbleColor.options is "blue,grey" metadata bubbleColor.default is "blue"
Finally, we need to declare any private instance variables and constants that we are going to use. We have four variables in this widget: two that store the property values and two that store paints. There is also one constant that will be used when drawing the widget.
private variable mText as String private variable mBubbleColor as String private variable mBackgroundPaint as Paint private variable mTextPaint as Paint constant kBuffer is 10
Now that we have taken care of declarations, we can get on with writing the handlers!
We want to be able to save and load the widget’s properties. If the following handlers are not included then every time you open a saved stack that includes a widget, the widget will lose it’s property data and the properties will be set to the default values.
First we write the OnSave() handler, which saves the widget’s properties when a stack containing a widget is saved:
public handler OnSave(out rProperties as Array) put the empty array into rProperties put mText into rProperties["bubbleText"] put mBubbleColor into rProperties["bubbleColor"] end handler
Next we write the OnLoad() handler, which loads the widget’s properties when a stack containing a widget is opened:
public handler OnLoad(in pProperties as Array) put pProperties["bubbleText"] into mText put pProperties["bubbleColor"] into mBubbleColor end handler
Next we will take care of the handler that set the widget’s properties.
In the property declarations, we defined “set” handlers for each of the properties. These are called when the user sets a property in the property inspector. We will define the “set” handlers below. The set handlers are private to the widget and don’t return anything. For this widget, both set handlers take a string as a parameter (the string entered by the user into the property editor in the property inspector).
First we want to be able to set the bubbleText property. This handler is very straightforward as all we need to do is set the mText variable to the string entered by the user.
private handler setText(in pText as String) put pText into mText end handler
Next we want to set the bubbleColor property. The background color and text color depend on this property, so we will also set the paint variables that we declared in step two when the bubbleColor property is changed. The reason for doing this is so we can speed up the drawing process, by taking as much calculation out of it as possible.
private handler setBubbleColor(in pColor as String) if pColor is "blue" then put solid paint with color [0,128/255,1] into mBackgroundPaint put solid paint with color [1,1,1] into mTextPaint else if pColor is "grey" then put solid paint with color [224/255,224/255,224/255] into mBackgroundPaint put solid paint with color [0,0,0] into mTextPaint end if put pColor into mBubbleColor end handler
Next we write the OnCreate() handler. This is called when the widget is first created, and here is where we initialise the private instance variables that we declared in step two. We will set the variables that store the property values to the default values. We will use the setBubbleColor() handler to set the mBubbleColor, mBackgroundPaint and mTextPaint variables in one.
public handler OnCreate() put "Hello World" into mText setBubbleColor(“blue”) end handler
The OnPaint() handler is called whenever LiveCode needs to redraw the widget, and it is here that we write the code that draws the widget. First, we draw the speech bubble. The speech bubble is made up of two shapes: a rounded rectangle that forms the main body of the bubble and a crescent shape in the bottom corner. We start by setting the paint of the canvas, then we fill the paths of the two shapes. Next we draw the text. We need to change the paint of the canvas and set the font, then we can draw the text within a text box.
public handler OnPaint() -- draw the speech bubble set the paint of this canvas to fetchPaint("bubble") fill fetchPath("rounded rectangle") on this canvas fill fetchPath("crescent") on this canvas -- fill in the text set the paint of this canvas to fetchPaint("text") set the font of this canvas to font "Helvetica" at size 12 fill text mText at top left of fetchTextBox() on this canvas end handler
You will notice that the paint, the paths and the text box are all set from custom handlers. We will write these handlers in the next step.
The fetchPaint() handler is private, it takes a string as a parameter and it returns a paint. Since the paints are stored in variables, all we need to do in this handler is return the correct variable based on the object.
private handler fetchPaint(in pObject as String) returns Paint if pObject is "bubble" then return mBackgroundPaint else if pObject is "text" then return mTextPaint end if end handler
The fetchPath() handler is private, takes a string as a parameter and returns a Path. This handler draws the path for the main body of the speech bubble, a rounded rectangle, and a crescent shape that forms the flick at the bottom.
Typically, the blue speech bubble looks as though the speaker is to the right of the screen and vice versa for the grey speech bubble. We use the buffer constant as we do not want the rounded rectangle path to go all the way to the edge of the widget’s bounds on all sides. This is so that we have space for the crescent in either the bottom right (blue speech bubble) or bottom left (grey speech bubble) in the bounds of the widgets.
In order to draw the crescent shape we need to draw two curved lines. We do this by first creating a new empty path, then move to a point to begin a new subpath. Next we continue the path with a curve through a specified point to another specified point. Finally, we curve through another specified point to end up at the start point.
private handler fetchPath(in pObject as String) returns Path if pObject is "rounded rectangle" then variable tRect as Rectangle if mBubbleColor is "blue" then put rectangle [0,0,my width-kBuffer,my height-kBuffer] into tRect else if mBubbleColor is "grey" then put rectangle [kBuffer,0,my width,my height-kBuffer] into tRect end if return rounded rectangle path of tRect with radius 10 else if pObject is "crescent" then variable tCrescentPath as Path put the empty path into tCrescentPath if mBubbleColor is "blue" then move to point [my width-kBuffer,my height-2*kBuffer] on tCrescentPath curve through point [my width-kBuffer/2,my height-kBuffer] to point [my width,my height-kBuffer] on tCrescentPath curve through point [my width-kBuffer,my height-kBuffer/2] to point [my width-2*kBuffer,my height-2*kBuffer] on tCrescentPath else if mBubbleColor is "grey" then move to point [kBuffer,2*kBuffer] on tCrescentPath curve through point [kBuffer/2,my height-kBuffer] to point [0,my height-kBuffer] on tCrescentPath curve through point [kBuffer,my height-kBuffer/2] to point [2*kBuffer,my height-2*kBuffer] on tCrescentPath end if return tCrescentPath end if end handler
The fetchTextBox() handler is private, takes no parameters and returns a Rectangle. Again, the position of the text box depends on the color of the speech bubble. We want the text box to fit nicely inside the main body of the speech bubble, with a ten pixel buffer between the edges of the text box and the edge of the rounded rectangle part of the speech bubble.
private handler fetchTextBox() returns Rectangle variable tRect as Rectangle if mBubbleColor is "blue" then put rectangle [kBuffer,kBuffer,my width-2*kBuffer,my height-2*kBuffer] into tRect else if mBubbleColor is "grey" then put rectangle [2*kBuffer,kBuffer,my width-kBuffer,my height-2*kBuffer] into tRect end if return tRect end handler
That concludes the custom handlers. The final thing we need to do is add the following line to the end of our code:
That’s it for the code! So let’s test, package and install the widget.
Open LiveCode 8 (download for latest release is available here), and open the Extension Builder from the Tools Menu.
You will then need to select the folder icon in the top right hand corner and choose your speechBubble.lcb file.
You should be able to see five buttons on the bottom right hand side of the plugin: Package; Uninstall; Install; Script and Test.
First we want to hit Test. This will compile and load the widget, and a new stack should pop up containing the widget. Any compile errors will be displayed in the log field in the plugin. Providing the widget has compiled successfully we can Package and Install the widget.
To package the widget, you will need to have an icon. Pressing the Package button will ask you to select an icon, and once the icon has been selected an extension package will be produced. You will find this in the “speechBubble” folder. The file name will be the widget identifier that you set in step two after declaring the extension as widget, and the file extension will be .lci.
Finally, pressing Install will install the widget in the “Extensions” directory of your “My LiveCode” folder. It will now be available to you whenever you run the LiveCode IDE.