First, the code imports the required libraries.

# Based on code written by @DavidGlaude on Twitter
# https://twitter.com/DavidGlaude/status/1340365817138044933
# https://gist.github.com/dglaude/4bf8d0a13c9c8ca8b05d6c0e9176bd20

import time
import alarm
import displayio
import board
import adafruit_imageload
from adafruit_display_shapes.rect import Rect
from adafruit_magtag.magtag import Graphics
from digitalio import DigitalInOut, Direction, Pull

Next, a list of the projects to choose from is declared. The names in this list should match the name of the .bmp used in the selection screen and the .py file in the projects/ directory, so weather has /bmps/weather.bmp and /projects/weather.py.

projects = [
    "weather",
    "spacex",
    "covid",
    "showerthoughts",
    "tides",
    "year",
    "showtimes",
    "slideshow",
]

Then, the four buttons that will be used to navigate the UI are initialized.

btnA = DigitalInOut(board.D15)
btnA.direction = Direction.INPUT
btnA.pull = Pull.UP

btnB = DigitalInOut(board.D14)
btnB.direction = Direction.INPUT
btnB.pull = Pull.UP

btnC = DigitalInOut(board.D12)
btnC.direction = Direction.INPUT
btnC.pull = Pull.UP

btnD = DigitalInOut(board.D11)
btnD.direction = Direction.INPUT
btnD.pull = Pull.UP

After that, the display is set up and the code checks to see if any buttons are pressed down. If any of them are, that means that the code will let the user choose a project to load. If none of them are, then the most recently loaded project will be loaded (if no project has been previously loaded, it defaults to the first one).

graphics = Graphics(auto_refresh=False)
display = graphics.display
group = displayio.Group()

selector = False
if not btnA.value or not btnB.value or not btnC.value or not btnD.value:
    selector = True

The code now checks to see if it should run the UI to let the user select the project to load. If it should, it then sets up the display with a white background and all the background images.

The MagTag is rather particular about what bmps it supports, so if you want to make your own bmps, click the link below. Just remember that for the MagTag, you need to use 8-bit indexed color.

The colors that I used are: 0x000000, 0xb1afad, 0x70696b, and 0xffffff

if selector:
    background = Rect(0, 0, 296, 128, fill=0xFFFFFF)
    group.append(background)
    for i in range(8):
        sprite_sheet, palette = adafruit_imageload.load(
            f"/bmps/{projects[i]}.bmp",
            bitmap=displayio.Bitmap,
            palette=displayio.Palette,
        )
        sprite = displayio.TileGrid(
            sprite_sheet,
            pixel_shader=palette,
            width=1,
            height=1,
            tile_width=62,
            tile_height=54,
            x=6 + 74 * (i % 4),
            y=6 + 62 * (i // 4),
        )
        group.append(sprite)

Before the main loop is started, there's still some more display setup to do. A hollow rectangle is added that will be used as a sort of cursor to show what project is currently selected, and then the whole display group is displayed. The code then waits a few seconds and sets selected, which is used to track the currently selected project, to 0.

[...]   
    rect = Rect(4, 4, 66, 58, outline=0x000000, stroke=2)
    group.append(rect)
    display.show(group)
    display.refresh()

    time.sleep(5)
    print("Ready")
    selected = 0

The code now enters the main loop. It first checks if the first and last buttons are selected. If they are, it makes the first item in alarm.sleep_memory the selected variable. If another button is selected, it moves the cursor rectangle in the direction indicated by the arrows on the MagTag buttons (first is left, second is up, third is down, fourth is right), refreshes the display, and changes the selected variable to reflect where the cursor is.

[...]   
    while True:
        if not btnA.value and not btnD.value:
            alarm.sleep_memory[0] = selected
            break
        
        if not btnA.value and selected != 0 and selected != 4:
            selected -= 1
            rect.x -= 74
            display.refresh()
            print("left")
            time.sleep(5)
            continue

        if not btnB.value and selected > 3:
            selected -= 4
            rect.y -= 62
            display.refresh()
            print("up")
            time.sleep(5)
            continue

        if not btnC.value and selected < 4:
            selected += 4
            rect.y += 62
            display.refresh()
            print("down")
            time.sleep(5)
            continue

        if not btnD.value and selected != 3 and selected != 7:
            selected += 1
            rect.x += 74
            display.refresh()
            print("right")
            time.sleep(5)
            continue

Now that the main loop is done, the buttons can be deinitialized so that they don't conflict with any buttons used in the chosen project.

btnA.deinit()
btnB.deinit()
btnC.deinit()
btnD.deinit()

Finally, the first item in alarm.sleep_memory is used as an index of the projects list to get the project to import. When something is imported like at the top of this file, it is usually a class, so it usually doesn't run anything but imports until it is initialized. In this case, a script is being imported so it is run just like if it was code.py or if it was run with python file.py on a computer.

print("Starting ", projects[int(alarm.sleep_memory[0])])
__import__("/projects/" + projects[int(alarm.sleep_memory[0])])

This guide was first published on Dec 30, 2020. It was last updated on Dec 30, 2020.

This page (Code Run-Through) was last updated on Dec 29, 2020.

Text editor powered by tinymce.