Creating a graphical Python app with CardStock

The Dealer

© Lead Image by Unsplash Amol Tyagi

© Lead Image by Unsplash Amol Tyagi

Author(s):

CardStock provides a simple development environment for building a Python graphical application.

CardStock [1] is a multiplatform software development tool inspired by Apple's HyperCard. CardStock's simple design greatly facilitates building graphical Python programs that can run either on your desktop or online as a web application (Figure 1). You can use CardStock to augment your applications with text, graphics, images, buttons, text entry fields, and Web Views. You can even play sounds and add clip art. In this article, I explain how to install CardStock on Linux, how it works, and how to get started.

Figure 1: This calculator is just one of the many CardStock programs you can run on www.cardstock.run.

Installing CardStock

The easiest way to install CardStock on any Linux distribution involves a two-step process. First, install the libasound and libwebkit2gtk development libraries from your distribution's native repositories. Second, install CardStock with pip, Python's package manager. On Ubuntu 22.04, installation looks like this:

sudo apt install libasound2-dev libwebkit2gtk-4.0-dev
pip3 install cardstock

The CardStock manual warns that the second step, which also installs the wxPython graphical toolkit, "can take a very long time to build." In my case, pip took about 20 minutes to install wxPython on a computer with an i5 CPU running at 1.6GHz and 16GBs of RAM.

When pip finished, I found two executable files called cardstock and csviewer installed in $HOME/.local/bin. The cardstock file, the development environment shown in the figures for this article, saves your Cardstock programs as one file with a .cds extension. The other file, csviewer, is the interpreter that will actually load and execute those files when you want to use them. Assuming you saved your CardStock program as myprogram.cds, you can run the program by either typing

csviewer myprogram.cds

at the prompt or defining csviewer as the handler of .cds files in your file manager or desktop environment.

The CardStock Stack Designer

Both visually and structurally, CardStocks programs are stacks of cards that run one at a time, each with its own user interface. Each stack can contain multiple graphical objects and custom Python code.

You build your stack in the Designer (Figure 2), CardStock's graphical interface. In the Designer, the left panel is where you add cards and fill the cards with objects. The right panel hosts a property editor (top) and a code editor (bottom), where you can see and edit all of the current object's properties.

Figure 2: The Designer's default configuration, plus a demo.

In the property editor top right, the leftmost button in the toolbar is the object selector (called the hand tool in CardStock's documentation). The hand tool lets you select, resize, drag, and drop objects on a card.

The other buttons in the property editor toolbar add graphical elements, all controllable with Python code. In addition to basic geometric figures, CardStock supports several types of clickable buttons as well as text entry fields, Web Views (more on this later), images, and text labels. The pencil button in the middle lets you draw freehand.

All in all, the Designer is quite easy to use. Until you get to actual coding, building software programs with CardStock feels a lot like creating a slideshow with LibreOffice Impress or similar programs. All of the objects are blocks of software code, but you can add objects to a card and then delete, move, resize, align, and group them, similar to an Impress slide. The main difference between Impress and CardStock is that each card and object must have a meaningful, unique name. If you don't do this, CardStock will assign obscure strings to each component, making your stack harder to document and debug.

You can drag and drop objects, change their style (e.g., with or without visible borders), and then adjust their position by one or more pixels at a time by moving them while you press the Shift or Alt keys. If necessary, you can even distribute objects in several overlapping layers, with the exception of text fields and Web Views, which must stay in the topmost layer.

The Code Editor

What makes CardStock really useful is how easy it is to attach event-driven Python code to any stack component. You do this in the Designer's code editor (shown in Figures 3 and 4, bottom right).

Compared to heavyweights like Emacs, vi, or Kate, the CardStock editor is pretty basic. However, it has all the basic functions: keyword suggestions and autocomplete, syntax highlighting, error highlighting, and Python regular expressions to find and replace text. CardStock also has a help function that shows information about the most recently selected property or event.

Whenever you create an object, or select an existing one, clicking on the + Add Event button in the code editor shows all the events applicable to that object. After selecting an event, you can enter the code that describes what should happen to the object whenever that event happens.

In Figure 3, I first clicked on the Image button (fifth from the left) to add a screenshot of the Linux Magazine home page and then rotated it 45 degrees counter-clockwise (shown as 315 – that is 360 minus 45 – for the image's rotation property).

Figure 3: You set each object's initial properties in the property editor (upper right). Behavior during execution is controlled by events defined in the code editor (bottom right).

Then, I clicked on + Add Event (for a description of the main CardStock events, see the "CardStock Events" box), selected on_periodic, and inserted code that tells the image to rotate 45 degrees clockwise, around its center, every time the event happens (i.e., about 30 times per second). As shown in Figure 3, the code editor prompts you with the events that can be applied to the current object via a context menu.

CardStock Events

CardStock supports lots of events. Some events are at the stack level. For example, on_resize() makes the stack redraw itself when you resize its window. Other events only involve single cards or single objects.

Due to space contraints, I've provided a few examples for each category (for the entire list, see the CardStock Reference guide [2], included in the official wiki [3]).

The on_setup() event applies to both cards and objects. It lets you set the initial value of every variable available for the stack or objects. I recommend using this event to avoid unpredictable behavior.

The on_show_card(self) event describes what happens as soon a card is shown (see Figure 4 for an example). Its counterpart, on_hide_card(), does the exact opposite.

The on_periodic() event happens inside every object or card, approximately 30 times per second (see Figure 5). Use this event for any check or code that must run continuously.

The on_message() and broadcast_message() events make their recipients execute the code they contain. With on_message(), the code only applies to the object it is attached to. You call it, for example, by writing OBJECT_NAME.send_message(). As its name implies, broadcast_message() goes to all of the components in the stack.

At a lower level, on_click() describes what happens when you click on an object, while on_mouse_enter(), on_mouse_exit(), and on_mouse_move() run when the cursor enters, leaves, or moves inside an object without clicking any button. To make something happen when you press the main mouse button inside an object, use on_mouse_press() or on_mouse_release().

CardStock also can run code in response to key presses, with events like on_key_press(self, key_name) or on_key_hold(). Like on_periodic, these events are called approximately 30 times per second, for every key that remains pressed.

Next, using the hand tool to select it, I went back to configure the card. I changed its fill_color to yellow (Figure 4) in the property editor. In the code editor, I also defined the on_show_card(self) event to tell the CardStock viewer to wait three seconds every time that card is shown and then automatically move to the next card. Figure 4 also introduces what's probably the most ubiquitous variable in CardStock: self. The self variable basically means that the code that follows applies to the same object that triggers the current event.

Figure 4: CardStock treats cards and objects in the same way: You define their properties in the property editor and their events in the code editor.

Figure 5 shows the results of the code from Figures 3 and 4: The card that is running in the CardStock viewer is captured during continuous rotation with the image upside down.

Figure 5: The CardStock card viewer executes the programs created with the Designer. Notice how the screenshot has rotated in respect to its initial position.

CardStock also lets you move and animate objects in more complex ways. The command

object.animate_center(3, [400,100])

moves the object from its current position and centers it at the coordinates 400 and 100 (in pixels) on the object's card, taking three seconds to complete the action.

Another way to move objects is to assign speed, in pixels per second, along the X and Y axes of the object's card, and set both values to 0 when the object must stop:

object.speed=[0,30]
object.speed=[0,0]

You can also change the speed on each axis automatically by adding statements to an object's on_periodic() event as follows:

self.speed.y -= 30

Other object properties can also be animated. For instance, to gradually change, over two seconds, the background of a card from its current color to red, you would use

card.animate_fill_color(2, 'red')

You can control the execution of these or any other animations by attaching them to an event. For example, associating the command above with an on_mouse_enter event would cause the card to change to red whenever a mouse pointer enters the current object.

When doing this, keep in mind that different animations happen simultaneously, while commands of the same type are executed sequentially. To end all of an object's animations, use the object.stop_animating() event.

If you are interested in text processing, you can use CardStock to build data entry forms. While these forms are not visually appealing, they are very practical and easy to assemble (Figures 6 and 7).

Figure 6: You can control almost every parameter of a CardStock text label.
Figure 7: A CardStock text entry field, placed below the label defined in Figure 6, with a default value.

Web View

CardStock lets you embed a basic web browser in your stack. Using the globe icon (the fourth button from the left in the Designer property editor toolbar), you can create a Web View. A Web View can render local pages (i.e., HTML code that you assign to the HTML property) or load the actual web page designated in the URL property (Figure 8).

Figure 8: CardStock applications can even browse the Internet!

Web Views don't compare with Firefox or Chrome due to speed, but Web Views function as actual browsers and can greatly extend your CardStock program's use cases. In Figure 9, for example, I used the search function of the Linux Magazine website in my CardStock stack to search for my articles.

Figure 9: A CardStock Web View offers all the essential functionality of a real web browser.

If needed, you can even limit browsing to certain domains. You do this by explicitly listing the domains in the allowed_hosts property. Optionally, you can run the following JavaScript code on the web page you load:

webview_1.run_java_script("YOUR JavaScript CODE HERE")

Testing and Debugging

Once you've built a stack, you can export it as a desktop or web application by saving your stack with all the images, audio files, and Python modules it needs. For web applications, CardStock uploads your program to www.cardstock.run. If you don't already have an account, it can help you set one up. After the upload, your web application will get a unique URL that anybody can load in their browser and run.

Before exporting, however, you first need to ensure that your application works as intended. To do this, you can check if your stack is working at any time by clicking the Run Stack button in the toolbar or choosing the same option in the File menu. Alternatively, you can select Run From Current Card and run the stack from that point.

For complete debugging, selecting Show Console in the menu opens a console where you can enter Python commands and check the values of some variables and read error messages, as well as anything your stack prints with the print() function. Indeed, if your stack contains any call to print(), the Console will open by itself the first time print() is used.

A better option is to use the Variable Inspector and the Error List for variables and error messages, respectively. The Variable Inspector (Show | Hide Variables) provides a compact, interactive view of all the variables in the stack and lets you change them while the stack is running.

The Error List, available from the Help menu, shows each error as a clickable link to the line of code that produced it. Finally, Help | All Code shows all of your stack's code.

Conclusions

Now that you know the basics, the most efficient way to learn programming with CardStock is to study and hack the many examples available from the CardStock File menu.

In my opinon, the most intriguing part of CardStock is that its executable .cds files are not binary files; they are plain text files. If you compare the portion of the .cds file shown in Figure 10 with Figure 3, you will immediately see that image settings and event definition in Figure 3 make up the source code shown in Figure 10!

Figure 10: The CardStock source code shown here corresponds to the image settings and event definition shown in Figure 3.

Much like shell scripts, .cds files are just plain text files that tell the CardStock viewer what it should draw and do. This means that you can copy, paste, mix, or even generate CardStock programs automatically by having other software write .cds files.

Even ignoring this feature, I recommend CardStock as a fun, efficient, and well-documented way to start learning Python programming, which may have very practical applications in schools and small businesses.

The Author

Marco Fioretti (http://mfioretti.substack.com) is a freelance author, trainer, and researcher based in Rome, Italy, who has been working with free/open source software since 1995 and on open digital standards since 2005. Marco also is a board member of the Free Knowledge Institute (http://freeknowledge.eu).