Skip to content

Lab 18: Mode Switching

Button A moves forward through a list of demo shapes; button B moves back. The % (modulo) operator wraps the index around automatically, so the list loops from the last entry back to the first with no extra if checks anywhere.

Sample Program Code

Five modes, each a (name, draw_function) pair — the same shape Lab 24's emotion table will generalize into data:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
# Lab 18: Mode Switching
# Button A moves forward through a list of modes; button B moves back.
# The % (modulo) operator wraps the index around automatically, so the
# mode list loops from the last entry back to the first with no extra
# if-checks.

import config
import shapes
from array import array
from utime import sleep

display = config.init_display()
button_a, button_b = config.init_buttons()

WHITE = config.WHITE
BLACK = config.BLACK
NO_FILL = config.NO_FILL
FILL = config.FILL
FONT = config.SMALL_FONT

HALF_WIDTH = config.WIDTH // 2
HALF_HEIGHT = config.HEIGHT // 2


def draw_rectangle():
    display.rect(60, 80, 120, 90, WHITE)


def draw_circle():
    shapes.circle(display, HALF_WIDTH, HALF_HEIGHT, 60, WHITE, NO_FILL)


def draw_triangle():
    points = array('h', [0, -60, 58, 46, -58, 46])
    shapes.poly(display, HALF_WIDTH, HALF_HEIGHT, points, WHITE, NO_FILL)


def draw_lines():
    display.line(50, 70, 190, 180, WHITE)
    display.line(50, 180, 190, 70, WHITE)


def draw_ring():
    # The mode a round screen was made for.
    shapes.ring(display, HALF_WIDTH, HALF_HEIGHT, config.SAFE_RADIUS, WHITE, 3)


MODES = (
    ("Rectangle", draw_rectangle),
    ("Circle", draw_circle),
    ("Triangle", draw_triangle),
    ("Lines", draw_lines),
    ("Ring", draw_ring),
)


def show_mode(index):
    name, draw = MODES[index]
    display.fill(BLACK)
    x = HALF_WIDTH - (len(name) * FONT.WIDTH) // 2
    display.text(FONT, name, x, 28, WHITE, BLACK)
    draw()


def pressed(button):
    if button.value() == 1:
        return False
    sleep(0.02)
    return button.value() == 0


def wait_for_release(button):
    while button.value() == 0:
        sleep(0.01)


mode_index = 0
show_mode(mode_index)

while True:
    if pressed(button_a):
        mode_index = (mode_index + 1) % len(MODES)
        show_mode(mode_index)
        wait_for_release(button_a)

    if pressed(button_b):
        mode_index = (mode_index - 1) % len(MODES)
        show_mode(mode_index)
        wait_for_release(button_b)

    sleep(0.01)

# Things to try:
#
# 1. Every mode here starts with a full display.fill(BLACK), and you can
#    see it happen when you press a button. That is acceptable for a menu
#    -- it happens once per press, not sixty times a second. Knowing when
#    a full wipe is fine is as useful as knowing when it is not.
#
# 2. Which of these five shapes looks right on a round screen and which
#    looks like a mistake? The rectangle and the triangle both have
#    corners pointing at a bezel that has none.

Here's the first mode:

Simulated output of 18-modes.py

Which Shapes Belong on a Round Screen?

Step through all five modes and look for the two that feel out of place. The rectangle and the triangle both have corners pointing at a bezel that has none — they read as slightly wrong on this screen in a way the circle and the ring never do. That's not a flaw in the code; it's a real design fact about round displays worth carrying into every layout decision from here on.

Every mode switch here calls a full display.fill(BLACK), and that's fine — it happens once per button press, not sixty times a second. Knowing when a full wipe is harmless is just as useful as knowing when it isn't.