🎨 App starter

August 12, 2026 Β· View on GitHub

Copy the script below to start your first app with the published product packages and pydevices-examples's small app_runtime helper.

Use thisWhen you want…
App starter (this page)A minimal template: draw the UI, handle clicks, runtime.run_forever()
pydevices_demoA feature tour: rotation, hardware scrolling, buffered text, timers

Prerequisites

  • A working board config on your path.
  • Product packages installed from TestPyPI/MIP, or a sibling pydevices development checkout.
  • src/utils on the path so app_runtime resolves.

Save the boilerplate as main.py (or any name you prefer) and run it from the REPL or as your device's entry point.

Boilerplate

One file for every host. Build the UI, subscribe callbacks, then runtime.run_forever(). The runtime auto-service dispatches input and QUIT β€” do not call runtime.poll() from an on_tick callback. Pass async_=runtime.timer_async to on_tick so async hosts do not arm a sync timer before the loop is running.

"""
my_app.py β€” starting point for a pydevices-examples app.

Copy and rename to build your own project. Uses board_config, pygraphics,
and the optional eventsys coordinator selected by app_runtime.
"""

from board_config import display_drv
from app_runtime import runtime
from pygraphics import Area

# --- customize: colors and layout ---
BG = 0
BTN = 0xF800       # red
BTN_ON = 0x07E0    # green

button = None
pressed = False


def redraw():
    global button
    w, h = display_drv.width, display_drv.height
    display_drv.fill(BG)
    color = BTN_ON if pressed else BTN
    button = Area(display_drv.fill_rect(w // 2 - 50, h // 2 - 25, 100, 50, color))
    display_drv.show()


def on_click(e):
    global pressed
    if button is not None and button.contains(e.pos):
        pressed = not pressed
        redraw()


redraw()
runtime.on(runtime.events.MOUSEBUTTONDOWN, on_click)
runtime.run_forever()

Hit testing and pygraphics.Area

The boilerplate imports Area from pygraphics only for hit-testing. display_drv.fill_rect(...) returns an (x, y, w, h) tuple; wrapping it in Area lets you write button.contains(e.pos) instead of inline coordinate math.

displaydev and eventsys do not depend on pygraphics. If you want a stack with no pygraphics import β€” or you install only those packages β€” keep the tuple from fill_rect and test clicks directly:

# displaydev + eventsys only β€” no pygraphics import
button = None  # (x, y, w, h)


def redraw():
    global button
    w, h = display_drv.width, display_drv.height
    display_drv.fill(BG)
    color = BTN_ON if pressed else BTN
    button = display_drv.fill_rect(w // 2 - 50, h // 2 - 25, 100, 50, color)
    display_drv.show()


def hit(rect, pos):
    x, y, w, h = rect
    px, py = pos
    return x <= px < x + w and y <= py < y + h


def handle_event(e):
    global pressed
    if e.type == runtime.events.MOUSEBUTTONDOWN:
        if hit(button, e.pos):
            pressed = not pressed
            redraw()

Stick with from pygraphics import Area when you also use rectangle helpers from pygraphics β€” union (area1 + area2), clip, inset, or dirty rects returned by pygraphics draw functions. See Drawing and fonts.

Run it

From a full clone, save the boilerplate as lib/main.py, then set PYTHONPATH/MICROPYPATH, cd lib, and run it directly β€” no path bootstrap needed:

cd pydevices-examples/lib
PYTHONPATH=.:utils ../.venv/bin/python main.py

With sibling source checkouts instead of installed packages:

cd pydevices-examples/lib
export PYTHONPATH=.:utils:../../pydevices/lib:../../pydevices/drivers/display
python3 main.py

On MCU, install the matching board config, the optional eventsys package, and pydevices-examples's utils package, then run main.py from flash or the REPL. For fallback path handling, see Utils path setup.

Interact: tap or click the centered rectangle β€” it toggles between red and green.

Walkthrough

redraw()

Clears the screen, draws one clickable rectangle, and calls display_drv.show() once. fill_rect returns (x, y, w, h); the boilerplate wraps that in Area for button.contains(event.pos).

Recreate Area objects whenever you change layout (same pattern as real apps with moving widgets).

on_click / event callbacks

Per-event handling stays in callbacks registered with runtime.on(...). The starter handles MOUSEBUTTONDOWN only. Add more runtime.on subscriptions for keys, encoders, and other devices β€” see Events.

Main loop

runtime.run_forever() keeps the app live on every host β€” see Runtime and multimer. PyScript and Jupyter board configs export timer_async=True; desktop defaults to sync unless PYDEVICES_TIMER_ASYNC=1 is set before the coordinator is created.

Customize

  1. Rename the file and module docstring.
  2. Layout β€” add more Area regions, sprites, or shapes in redraw().
  3. Text β€” for labels and lists, use Font + FrameBuffer + blit_rect (Drawing and fonts, font_simpletest.py).
  4. Timers β€” use runtime.on_tick(callback, period=…, async_=runtime.timer_async); see multimer and pydevices_demo.

!!! tip "Next steps beyond this template" - Rotation and hardware scroll β€” pydevices_demo - Event types and runtime β€” Events, Runtime - All example scripts β€” Examples catalog