PyperCard CheatSheet

PyperCard is an easy and simple GUI framework with a focus on beginner Python programmers. This page contains all the technical information you need in one place. You should use it for reference purposes. If you’d like to learn about PyperCard you should consult the PyperCard Tutorials.

Installation

PyperCard only works with Python 3.6 or above. It is built on the Kivy framework and should work anywhere Kivy does (Windows, OSX, Linux, Android and iOS).

PyperCard is on PyPI and can be installed thus:

pip3 install pypercard

Check the installation works with the following “Hello World!” test app:

from pypercard import CardApp, Card

app = CardApp(stack=[Card("hello", text="Hello, World!"), ])
app.run()

Save this somewhere (e.g. as test.py) and run it with:

python3 test.py

If all goes to plan you should see a Window containing the words “Hello, World” appear.

If you require help or support please use our chat channel. If you think you’ve found a bug in PyperCard or would like to suggest a new feature or improvement, please do so by raising a new issue via our GitHub page for the project. We expect everyone to abide by our Code of Conduct – we’re a friendly and welcoming project, but we won’t tolerate rudeness, prejudice or other forms of anti-social behaviour via our communication channels.

Core Concepts

PyperCard is inspired by HyperCard and means you should be familiar with the following core concepts:

  • An application is made from a stack of cards.
  • Each card in the stack represents a screen in the application. At the very least, a card must have a unique title attribute (but will often have further attributes that define its content and behaviour).
  • Users move between cards via transitions, usually activated by pressing a button.
  • Transitions can be as simple as a string identifying the title of the next card to display. However, a transition can also be a function that returns a string identifying the next card to display. Business logic for the application happens in these transition functions.
  • Simple form inputs can be used to capture input from the user.
  • An application has a data store Python dictionary to be used to set and get arbitrary application state.
  • Transition funtions always take two arguments, a reference to the application’s data_store (containing application state) and the value of the form_input found on the preceeding card from which the user is transitioning.
  • Future versions of PyperCard will automate the packaging of your app for Windows, OSX, Linux, Android and iOS.

The following diagram may help you visualise these concepts. There are three cards in the application stack: blue, white and yellow. The blue card can transition to the white and yellow cards (as demonstrated by the arrows).

Splash image

Colours

It is possible to set the colour of various aspects of the user interface (for example, the text colour, background colour, button colour and button background).

To make this as easy as possible for beginners, colours can be specified by their English names. A full list of recognized colour names (and an example of the colour) can be found in the palette shown below.

Alternatively, instead of naming the colour of choice, you can provide the hex RGB value as a string in two common forms: 0xRRGGBB (raw hex) and #RRGGBB (web hex).

    black     dimgrey     darkgrey     grey
    grey0     grey1     grey2     grey3
    grey4     grey5     grey6     grey7
    grey8     grey9     grey10     grey11
    grey12     grey13     grey14     grey15
    grey16     grey17     grey18     grey19
    grey20     grey21     grey22     grey23
    grey24     grey25     grey26     grey27
    grey28     grey29     grey30     grey31
    grey32     grey33     grey34     grey35
    grey36     grey37     grey38     grey39
    grey40     grey41     grey42     grey43
    grey44     grey45     grey46     grey47
    grey48     grey49     grey50     grey51
    grey52     grey53     grey54     grey55
    grey56     grey57     grey58     grey59
    grey60     grey61     grey62     grey63
    grey64     grey65     grey66     grey67
    grey68     grey69     grey70     grey71
    grey72     grey73     grey74     grey75
    grey76     grey77     grey78     grey79
    grey80     grey81     grey82     grey83
    grey84     grey85     grey86     grey87
    grey88     grey89     grey90     grey91
    grey92     grey93     grey94     grey95
    grey96     grey97     grey98     grey99
    lightgrey     gainsboro     whitesmoke     white
    darkred     saddlebrown     sienna     sienna1
    sienna2     sienna3     sienna4     brown
    brown1     brown2     brown3     brown4
    maroon     maroon1     maroon2     maroon3
    maroon4     firebrick     firebrick1     firebrick2
    firebrick3     firebrick4     darkgoldenrod     darkgoldenrod1
    darkgoldenrod2     darkgoldenrod3     darkgoldenrod4     rosybrown
    rosybrown1     rosybrown2     rosybrown3     rosybrown4
    darkkhaki     mediumvioletred     indianred     indianred1
    indianred2     indianred3     indianred4     peru
    violetred     violetred1     violetred2     violetred3
    violetred4     chocolate     chocolate1     chocolate2
    chocolate3     chocolate4     tan     tan1
    tan2     tan3     tan4     orchid
    orchid1     orchid2     orchid3     orchid4
    goldenrod     goldenrod1     goldenrod2     goldenrod3
    goldenrod4     palevioletred     palevioletred1     palevioletred2
    palevioletred3     palevioletred4     burlywood     burlywood1
    burlywood2     burlywood3     burlywood4     darksalmon
    lightgoldenrod     lightgoldenrod1     lightgoldenrod2     lightgoldenrod3
    lightgoldenrod4     palegoldenrod     lightcoral     khaki
    khaki1     khaki2     khaki3     khaki4
    sandybrown     wheat     wheat1     wheat2
    wheat3     wheat4     salmon     salmon1
    salmon2     salmon3     salmon4     antiquewhite
    antiquewhite1     antiquewhite2     antiquewhite3     antiquewhite4
    linen     oldlace     red     red1
    red2     red3     red4     deeppink
    deeppink1     deeppink2     deeppink3     deeppink4
    orangered     orangered1     orangered2     orangered3
    orangered4     tomato     tomato1     tomato2
    tomato3     tomato4     hotpink     hotpink1
    hotpink2     hotpink3     hotpink4     coral
    coral1     coral2     coral3     coral4
    darkorange     darkorange1     darkorange2     darkorange3
    darkorange4     lightsalmon     lightsalmon1     lightsalmon2
    lightsalmon3     lightsalmon4     orange     orange1
    orange2     orange3     orange4     lightpink
    lightpink1     lightpink2     lightpink3     lightpink4
    pink     pink1     pink2     pink3
    pink4     gold     gold1     gold2
    gold3     gold4     peachpuff     peachpuff1
    peachpuff2     peachpuff3     peachpuff4     navajowhite
    navajowhite1     navajowhite2     navajowhite3     navajowhite4
    moccasin     bisque     bisque1     bisque2
    bisque3     bisque4     mistyrose     mistyrose1
    mistyrose2     mistyrose3     mistyrose4     blanchedalmond
    papayawhip     lavenderblush     lavenderblush1     lavenderblush2
    lavenderblush3     lavenderblush4     seashell     seashell1
    seashell2     seashell3     seashell4     cornsilk
    cornsilk1     cornsilk2     cornsilk3     cornsilk4
    lemonchiffon     lemonchiffon1     lemonchiffon2     lemonchiffon3
    lemonchiffon4     floralwhite     snow     snow1
    snow2     snow3     snow4     darkolivegreen
    darkolivegreen1     darkolivegreen2     darkolivegreen3     darkolivegreen4
    olivedrab     olivedrab1     olivedrab2     olivedrab3
    olivedrab4     lawngreen     chartreuse     chartreuse1
    chartreuse2     chartreuse3     chartreuse4     yellowgreen
    greenyellow     beige     lightgoldenrodyellow     yellow
    yellow1     yellow2     yellow3     yellow4
    lightyellow     lightyellow1     lightyellow2     lightyellow3
    lightyellow4     ivory     ivory1     ivory2
    ivory3     ivory4     darkgreen     mediumspringgreen
    green     green1     green2     green3
    green4     springgreen     springgreen1     springgreen2
    springgreen3     springgreen4     lightseagreen     forestgreen
    seagreen     seagreen1     seagreen2     seagreen3
    seagreen4     limegreen     mediumseagreen     turquoise
    turquoise1     turquoise2     turquoise3     turquoise4
    mediumturquoise     mediumaquamarine     aquamarine     aquamarine1
    aquamarine2     aquamarine3     aquamarine4     darkseagreen
    darkseagreen1     darkseagreen2     darkseagreen3     darkseagreen4
    lightgreen     palegreen     palegreen1     palegreen2
    palegreen3     palegreen4     honeydew     honeydew1
    honeydew2     honeydew3     honeydew4     mintcream
    darkcyan     deepskyblue     deepskyblue1     deepskyblue2
    deepskyblue3     deepskyblue4     darkturquoise     cyan
    cyan1     cyan2     cyan3     cyan4
    dodgerblue     dodgerblue1     dodgerblue2     dodgerblue3
    dodgerblue4     darkslategrey     darkslategray     darkslategray1
    darkslategray2     darkslategray3     darkslategray4     royalblue
    royalblue1     royalblue2     royalblue3     royalblue4
    steelblue     steelblue1     steelblue2     steelblue3
    steelblue4     cadetblue     cadetblue1     cadetblue2
    cadetblue3     cadetblue4     cornflowerblue     slategrey
    slategray     slategray1     slategray2     slategray3
    slategray4     lightslategray     lightslategrey     skyblue
    skyblue1     skyblue2     skyblue3     skyblue4
    lightskyblue     lightskyblue1     lightskyblue2     lightskyblue3
    lightskyblue4     lightblue     lightblue1     lightblue2
    lightblue3     lightblue4     paleturquoise     paleturquoise1
    paleturquoise2     paleturquoise3     paleturquoise4     lightsteelblue
    lightsteelblue1     lightsteelblue2     lightsteelblue3     lightsteelblue4
    powderblue     lightcyan     lightcyan1     lightcyan2
    lightcyan3     lightcyan4     aliceblue     azure
    azure1     azure2     azure3     azure4
    navy     navyblue     darkblue     mediumblue
    blue     blue1     blue2     blue3
    blue4     midnightblue     darkslateblue     slateblue
    slateblue1     slateblue2     slateblue3     slateblue4
    mediumslateblue     lightslateblue     blueviolet     mediumpurple
    mediumpurple1     mediumpurple2     mediumpurple3     mediumpurple4
    darkviolet     darkorchid     darkorchid1     darkorchid2
    darkorchid3     darkorchid4     purple     purple1
    purple2     purple3     purple4     mediumorchid
    mediumorchid1     mediumorchid2     mediumorchid3     mediumorchid4

The CardApp

The CardApp class is used to instantiate an application. Depending on your level of skill and / or need for refinement, there are several approaches to this.

If you have defined a stack in a JSON file (see below), all that is required is:

from pypercard import CardApp

app = CardApp()
app.load("my_stack.json")
app.run()

If you’re declaring your stack of cards with Python (something you’ll need to do for more complicated applications), then you can pass the stack as an argument when instantiating the CardApp class. The stack argument must be a list of Card instances:

from pypercard import CardApp, Card

app = CardApp(stack=[Card("hello", text="Hello, World!"), ])
app.run()

Finally, you could use the CardApp class’s add_card method to add individual Card instances. However, the stack argument is far more convenient and is a simple convenience wrapper around the add_card to save you time.

Obviously, as demonstrated in the examples above, to make the application run you call the run method.

The CardApp can take the following arguments when instantiated:

  • name - what your operating system will display as the name of the application. Defaults to "A PyperCard Application :-)".
  • data_store - a dictionary the application should use for storing application state. Pass in your own dictionary if you need to pre-load it with default state, depending on the needs of your application. If you don’t supply a data_store argument, then PyperCard will use an empty dictionary. The data_store instance is one of the arguments passed into transition functions (see below).
  • stack - a list of Card instances which defines the application’s stack (see description above).

For more information about the CardApp class, please see API Reference.

Cards

Instances of the Card class represent different screens in the application stack. Users transition between cards in one of two ways: by pressing a button to activate a transition (see below) or by automatically advancing to a target card after a pre-determined period of time.

All cards must have a unique (and preferably meaningful) title attribute, supplied as an argument when the object is instantiated. All the other attributes for a card are optional and define how the card looks and behaves. Such attributes are usually assigned when the card is instantiated. Once the card is associated with an application its attributes cannot be changed.

The available attributes are (see the API Reference for more details):

  • title - a unique (within the stack) and preferably meaningful string identifier for the card.
  • text - the (string) textual content of the card. It’s possible to change the look of the text using BBCode style markup. If there is a form, the text will be displayed in the form of a label for the form input, otherwise the text will take up the full screen.
  • text_color - a string containing the default colour of the text. Defaults to "white". See the section on colour (above) for valid colour names and values.
  • text_size - an integer representing the default size of the text. Defaults to 48 (px).
  • form - the type of form input (see below) associated with the card. Must be one of the attributes of the Inputs enum class: TEXTBOX, TEXTAREA, MULTICHOICE, SELECT or SLIDER.
  • options - a list or tuple containing configuration options needed by the form input. See the description of each form input (below) for more information.
  • sound - a string containing the path to a sound file to play when the card is displayed. Only sound formats supported by the platform upon which the application is running can be played.
  • sound_repeat - a boolean flag to indicate if the sound associated with the card should continue to loop.
  • background - a string containing either the colour (see above) of the card’s background, or a path to an image to display as the background. The default value is "black".
  • buttons - a list or tuple containing dictionary definitions of the buttons to display on the card which will be used to activate a transition to other cards in the stack. Each button dictionary must have at least two attributes: label (associated with a string containing the text to display on the button) and target (associated with either a string containing the title of the target card to transition to, or a function [see below] containing business logic which will return the title of the target card for transition). Button dictionaries may also contain three options attributes: text_size (an integer indication of the size of the button’s label text), text_color (a string containing the colour of the button’s label text), and background_color (a string containing the colour of the button’s background).
  • auto_advance - a floating point value to indicate the number of seconds to wait until the card automatically transitions to the card indicated by the auto_target attribute. It is possible to mix buttons and auto_advance as a means to transition from a card. If a button is clicked before the scheduled automatic transition then the scheduled transition will not take place.
  • auto_target - a string containing the title of the card to transition to after the number of seconds indicated by the auto_advance attribute.

Here’s an example of a card with textual content and a single button which will transition the application to another card called another_card. Notice how the first argument is the card’s mandatory and unique title attribute:

from pypercard import Card, CardApp

card = Card("example_card", text="Hello, World!", text_color="red")
app = CardApp(stack=[card, ])
app.run()

It’s important to understand the life-cycle of a card.

When the Card class is instantiated with the various attributes needed to describe the new card object’s content and behaviour, the various constraints required for the card to behave properly are validated. If there’s a problem (for example, you specify a form input which requires options to work properly, but you fail to supply any options) then a ValueError exception will be raised.

When the card is added to the application (usually as a member of a stack list when the application is instantiated), then it is drawn as a Kivy.uix.screenmanager.Screen instance and added to a screen manager object that belongs to the application.

If a card is indicated as the next target to display, before it is shown to the user all the textual content of the card is re-formatted against the contents of the application’s data_store. This means Python’s built-in simple string templating language can be used to make the content of the card dynamic. For instance if the text attribute of the card was the string, "Hello, {name}.", and the application’s data_store dictionary was, {"name": "Nicholas"}, then the textual content displayed to the user would be updated to, "Hello, Nicholas". The textual content, form label and button labels can all be updated in this way.

When a card is first displayed to the user two things happen: if a sound should be played (and repeated) then this is started, and if the card can automatically advance after a certain period of time, this event is scheduled.

Finally, when the card is removed from the screen (and before the next card is displayed), then if there is any sound playing, this is stopped.

JSON Stacks

PyperCard is designed to be easy to use and understand with special attention paid to the needs of beginner developers.

Writing Python code can be intimidating for beginner developers. In order to declare the UI stack of cards in Python a developers needs to instantiate several classes, create a Python list and even write their own functions when all they want to do is describe a very simple stack of cards.

As a result, it’s possible to declare a simple stack of cards that require no business logic using the JSON data format. This was the approach taken by Adafruit who provided the inspiration for this project.

The JSON file must contain an array of JSON objects. Each object represents a card in the application’s stack.

Attributes of the JSON objects must match the names of the arguments used when instantiating the Python Card class (see above).

Use the CardApp class’s load convenience function with the path to the JSON file to load the stack:

from pypercard import CardApp

app = CardApp()
app.load("my_stack.json")
app.run()

The JSON data format is a lightweight and easy to read solution which has the advantage of being a ubiquitous form of data exchange. Following simple naming conventions for defining JSON objects means little effort is needed to define a working stack for a simple app:

[
    {
        "title": "hello",
        "text": "Hello there!",
        "text_color": "green",
        "buttons": [
            {
                "label": "OK",
                "target": "goodbye"
            }
        ]
    },
    {
        "title": "goodbye",
        "text": "Goodbye!",
        "text_color": "red",
        "buttons": [
            {
                "label": "OK",
                "target": "hello"
            }
        ]
    }
]

This also has the advantage that, at some later date, a graphical beginner friendly tool, could be created to emit valid JSON files to make this process even less intimidating. Nevertheless, writing JSON in a text editor is not an onerous task and goes some way to demonstrating how simple it is to use a text based medium for programming.

Forms

As has been pointed out in the section on the Card class (see above), a card can contain a form input field. Only one form input field can be displayed on each card. The input field is specified as one of the attributes of the Inputs enumeration class:

from pypercard import Card, Inputs, CardApp

card = Card("form_example", form=Inputs.TEXTBOX, text="A text box")
app = CardApp(stack=[card, ])
app.run()

In the example above, a text box with the label "A text box" will be displayed by the card.

The value of the form input field is used as an argument into the transition function called when the user moves away from the card.

Sometimes, the form input field needs extra information to designate options needed to display the input field properly. These options are explained below as each form field is described.

TEXTBOX

A text box is a single line text entry field which needs no special options:

_images/textbox.png

TEXTAREA

A text area is a multi line text entry field which needs no special options:

_images/textarea.png

MULTICHOICE

A multiple choice field allows users to select none, any or all of a range of options. These options should be expressed as a tuple of string values.

_images/multichoice.png

SELECT

A selector field allows users to select either a single value or no value from a range of options. The options should be expressed as a tuple of string values.

_images/select.png

SLIDER

A slider is for providing numeric input. It must have a minimum (min), maximum (max) and optional step value provided in a tuple of numeric values.

_images/slider.png

The code for creating each of the illustrated form elements, including examples of the options for multiple choice, selector and slider fields is copied below:

from pypercard import Inputs, Card, CardApp


stack = [
    Card(
        "TextBox",
        form=Inputs.TEXTBOX,
        text="A single line textbox",
        buttons=[{"label": "Next", "target": "TextArea"}],
    ),
    Card(
        "TextArea",
        form=Inputs.TEXTAREA,
        text="A multi line text area",
        buttons=[{"label": "Next", "target": "MultiChoice"}],
    ),
    Card(
        "MultiChoice",
        form=Inputs.MULTICHOICE,
        options=["Ham", "Eggs", "Bacon", "Sausage"],
        text="A multiple choice selection",
        buttons=[{"label": "Next", "target": "Select"}],
    ),
    Card(
        "Select",
        form=Inputs.SELECT,
        options=["Red", "Green", "Yellow", "Blue"],
        text="A single choice collection",
        buttons=[{"label": "Next", "target": "Slider"}],
    ),
    Card(
        "Slider",
        form=Inputs.SLIDER,
        options=(-100, 100, 10),
        text="A slider with min/max/step",
        buttons=[{"label": "Next", "target": "TextBox"}],
    ),
]
app = CardApp(name="Examples of form elements...", stack=stack)
app.run()

Transitions

Transitions are how the user moves between cards. Transitions can be of two types:

  • A string value referencing the unique title attribute of the target card.
  • A function, containing business logic, which returns a string value referencing the unique title attribute of the target card.

Transitions are declared in two places:

  • As the value associated with the "target" attribute of a button, or;
  • As the value of a card’s auto_target attribute.

If you’re using transition functions, you should declare them first, before creating the Card objects which may reference them.

Transition functions always take two arguments and must always return a string containing a valid card title. The two arguments are:

  • data_store - a reference to the application’s data_store instance which is used to set and get application state.
  • form_value - the current value of the form input field in the card from which the user is transitioning. This will be None if the card didn’t contain a form input field, or false-y if the user didn’t enter anything.

As a result, your transition function should look something like this:

def my_transition(data_store, form_value):
    # Arbitrary Python code here.
    return "a_card_title"

Warning

The code within the transition function can be any arbitrary Python, but it is important to note that these are blocking functions so do not do anything which will cause the application to pause.

Please note that the text associated with labels and buttons is formatted for template names with the values found in the data_store (see the example below). In this way, values stored in the data_store can safely be shown to the user.

The following simple example demonstrates how transition functions work:

from pypercard import Inputs, Card, CardApp


def get_name(data_store, form_value):
    """
    Gets the name of the user from the form field and stores it in the
    data_store. If no name is given, causes an error to be displayed.
    """
    if form_value:
        data_store["name"] = form_value
        return "hello"
    else:
        return "error"


stack = [
    Card(
        "get_value",
        form=Inputs.TEXTBOX,
        text="What is your name..?",
        buttons=[
            {"label": "OK", "target": get_name}  # Use the function!
        ]
    ),
    Card(
        "hello",
        text="Hello {name}!",
        buttons=[
            {"label": "OK", "target": "get_value"}
        ]
    ),
    Card(
        "error",
        text="ERROR\n\nPlease enter a name!",
        text_color="white",
        background="red",
        auto_advance=3,
        auto_target="get_value"
    ),
]

app = CardApp(stack=stack)
app.run()

The end result looks like this:

_images/name_app.gif

Packaging

Coming in a future version.