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
- Declarations
- 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!
1. Create a .lcb File
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!
2. Declarations
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
community.livecode..
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!
3. Saving and Loading the Widget’s Properties
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.
4. Handlers to 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
5. Creating and Drawing the Widget
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.
6. Defining Custom Handlers
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:
end widget
That’s it for the code! So let’s test, package and install the widget.
7. Test, Package, Install
Open LiveCode 8 (download for latest release is available here), and open the extensionBuilder plugin.
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.
8. Use the Widget in a LiveCode Stack
Having successfully installed your widget, you can now use it in a LiveCode stack. In the IDE, open a new mainstack and then all you need to do is drag out your widget from the tools palette. In the style of Blue Peter, here is one I made earlier:
15 comments
Join the conversationRichmond - April 30, 2015
This article is really good.
However, I wouldn’t want to ruin my reputation by not having a “however”: I note you recommend TextWrangler – which is a super bit of software IFF you are working on a Mac: but very many people aren’t, so let me recommend jEdit: http://jedit.org/
Georgia - April 30, 2015
Hi Richmond,
Thanks! My main reason for recommending TextWrangler is because we have a LiveCode Builder colour scheme that can be used in TextWrangler, but of course users are free to use whatever text editor they feel most comfortable with.
Georgia
Peter - May 4, 2015
For cross-platform work, I have also recently published a LiveCode Builder editing mode for the Atom text editor: https://atom.io/
To add LiveCode Builder syntax highlighting and indenting, just install the “language-livecode” package from Atom’s settings dialog.
Richmond - May 4, 2015
That’s really marvellous: installing Atom as I write. Thank you so much!
Ben M - May 4, 2015
That’s great! Do you know if it’s compatible with Microsoft”s new Visual Studio Code, which is based on Atom? (If not sure I’ll check tomorrow)
Josep - May 8, 2015
Great article! How create widget for use NFC from Android? or how connect to Android SDK?
lhasbhopa - May 25, 2015
Hi! Geogia,
Thank you very much for the post.I learned a lot from it.However my question is not related to this post.I am new to livecode and I have one problem in the current app I am working on. In my app I want to make two fields overlapped and when the card opens the second filed will remain hidden but when user clicks on the first one it will show the content in the field two.I manage to solve the problem when I put two fields in a different position.But when i overlapped them I am not getting what I want.Can you please help me to solve this issue.I would be so grateful to you if you can help me with this.Following are the script I wrote.Its working but I want them to be overlap so that when the second filed is hidden.It won’t take a long gap between the second filed and the upcoming fields after it.
on Opencard
hide fld 2
end Opencard
on mouseUp
show fld 2
end mouseUp
Much Regards
Tsering
Georgia Hutchings - May 26, 2015
Hi Tsering,
If I have understood correctly:
You have two fields, field 1 and field 2. Field 2 is behind field 1, and it is hidden. When you click on field 1 you want field 2 to be revealed.
I suggest for the script:
on openCard
hide field 2
show field 1
end openCard
on mouseUp
hide field 1
show field 2
end mouseUp
For the size and position of the fields, if you want field 2 to be directly behind field 1 then you need to set the size and position of the fields to be the same, and then lock size and position (in the Size and Position pane in the property inspector) so that they remain in the same place.
I hope I have understood your problem correctly and have helped. I suggest using the LiveCode forums, as you will be able to upload your stack so that it is easier for others to understand the problem. Please let me know if I can help you further.
Kind Regards,
Georgia
lhasbhopa - May 27, 2015
Thank you very much Georgia for your kind gesture.Yes it helped me.But,again I need to trouble you a bit. is it possible not to hide the field 1, when field 2 shows.Instead let field 2 to drop down under field 1 and field to remain at its position, And On mouseDown field 2 will go back to its first position and remain hidden at the back of field 1 given that field 2 is hidden right at the same position of field 1.
Much Regards
Tsering
PS: And thank you very much for suggesting about the forum.Actually I lost my password for both livecode forum and my personal email,therefore can’t login to both accounts hahaha! .But i will definitely registered with different account now.
Georgia Hutchings - May 27, 2015
Hi Tsering,
I think you want something like:
command showField
show field 2
set the location of field 2 to x,y
end showField
command hideField
hide field 2
set the location of field 2 to (the location of field 1)
end hideField
That should solve the problem for you.
Hope to see you back on the forums soon!
Kind Regards,
Georgia
Mark Smith - July 26, 2015
Thanks for an excellent intro Georgia. One bit has me confused. When you say “where the username should be the username you use to log into the LiveCode extension store. Don’t have a username? Create one here.” I am a bit puzzled because I log into the store with my full email address (which you probably have access to). I am presuming that is NOT what I should be putting into this declaration, correct? Then what? Thanks
Mark
Georgia Hutchings - September 2, 2015
Hi Mark,
Make sure you are logged in to your LiveCode account, then try this link: http://livecode.com/account/developer/register. You can create your developer id here. If you look back at step 2, then you will see my developer id is gohutchings.
Hope that clears things up for you!
Thanks,
Georgia
Mikey - August 30, 2015
Waaaaay back in step 1, when you suggest ‘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!’
That implies, to me, that you should open the color scheme and save it as the source for the widget. I don’t think that’s what you meant.
Georgia Hutchings - September 2, 2015
Hi Mikey,
It just means to open a new text document in text wrangler and save it as .lcb file so that before you start writing the widget you have the file saved in the right place with the right extension and with the right colour scheme in TextWrangler.
Hope that makes sense,
Georgia
Christopher Armstrong - February 11, 2016
Great documentation. Followed it and worked like a charm. Gave me a much better understanding how widgets and libraries work.