It is suggested you use the latest version of CircuitPython available (8.x at the time of writing this guide) for best results.
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your PyPortal CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
Files in the bundle:
# SPDX-FileCopyrightText: 2023 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT
#
# Faux Floppy Disk with LCD Screen
# Display file icons on screen
import os
import time
import board
import displayio
import adafruit_imageload
import terminalio
import adafruit_touchscreen
from adafruit_display_text import label
from adafruit_display_shapes.rect import Rect
# Get a dictionary of filenames at the passed base directory
# each entry is a tuple (filename, bool) where bool = true
# means the filename is a directory, else false.
def get_files(base):
files = os.listdir(base)
file_names = []
for isdir, filetext in enumerate(files):
if not filetext.startswith("."):
if filetext not in ('boot_out.txt', 'System Volume Information'):
stats = os.stat(base + filetext)
isdir = stats[0] & 0x4000
if isdir:
file_names.append((filetext, True))
else:
file_names.append((filetext, False))
return file_names
def get_touch(screen):
p = None
while p is None:
time.sleep(0.05)
p = screen.touch_point
return p[0]
# Icon Positions
ICONSIZE = 48
SPACING = 18
LEFTSPACE = 38
TOPSPACE = 10
TEXTSPACE = 10
ICONSACROSS = 4
ICONSDOWN = 3
PAGEMAXFILES = ICONSACROSS * ICONSDOWN # For the chosen display, this is the
# maximum number of file icons that will fit
# on the display at once (display dependent)
# File Types
BLANK = 0
FILE = 1
DIR = 2
BMP = 3
WAV = 4
PY = 5
RIGHT = 6
LEFT = 7
# Use the builtin display
display = board.DISPLAY
WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height
ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
board.TOUCH_YD, board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(WIDTH, HEIGHT))
# Create base display group
displaygroup = displayio.Group()
# Load the bitmap (this is the "spritesheet")
sprite_sheet, palette = adafruit_imageload.load("/icons.bmp")
background = Rect(0, 0, WIDTH - 1, HEIGHT - 1, fill=0x000000)
displaygroup.append(background)
# Create enough sprites & labels for the icons that will fit on screen
sprites = []
labels = []
for _ in range(PAGEMAXFILES):
sprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width=1, height=1, tile_height=48,
tile_width=48,)
sprites.append(sprite) # Append the sprite to the sprite array
displaygroup.append(sprite)
filelabel = label.Label(terminalio.FONT, color=0xFFFFFF)
labels.append(filelabel)
displaygroup.append(filelabel)
# Make the more files and less files icons (> <)
moresprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width=1, height=1, tile_height=48,
tile_width=48,)
displaygroup.append(moresprite)
moresprite.x = WIDTH - ICONSIZE + TEXTSPACE
moresprite.y = int((HEIGHT - ICONSIZE) / 2)
lesssprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width=1, height=1, tile_height=48,
tile_width=48,)
displaygroup.append(lesssprite)
lesssprite.x = -10
lesssprite.y = int((HEIGHT - ICONSIZE) / 2)
display.root_group = displaygroup
filecount = 0
xpos = LEFTSPACE
ypos = TOPSPACE
displaybase = "/" # Get file names in base directory
filenames = get_files(displaybase)
currentfile = 0 # Which file is being processed in all files
spot = 0 # Which spot on the screen is getting a file icon
PAGE = 1 # Which page of icons is displayed on screen, 1 is first
while True:
if currentfile < len(filenames) and spot < PAGEMAXFILES:
filename, dirfile = filenames[currentfile]
if dirfile:
filetype = DIR
elif filename.endswith(".bmp"):
filetype = BMP
elif filename.endswith(".wav"):
filetype = WAV
elif filename.endswith(".py"):
filetype = PY
else:
filetype = FILE
# Set icon location information and icon type
sprites[spot].x = xpos
sprites[spot].y = ypos
sprites[spot][0] = filetype
#
# Set filename
labels[spot].x = xpos
labels[spot].y = ypos + ICONSIZE + TEXTSPACE
# The next line gets the filename without the extension, first 11 chars
labels[spot].text = filename.rsplit('.', 1)[0][0:10]
currentpage = PAGE
# Pagination Handling
if spot >= PAGEMAXFILES - 1:
if currentfile < (len(filenames) + 1):
# Need to display the greater than touch sprite
moresprite[0] = RIGHT
else:
# Blank out more and extra icon spaces
moresprite[0] = BLANK
if PAGE > 1: # Need to display the less than touch sprite
lesssprite[0] = LEFT
else:
lesssprite[0] = BLANK
# Time to check for user touch of screen (BLOCKING)
touch_x = get_touch(ts)
print("Touch Registered ")
# Check if touch_x is around the LEFT or RIGHT arrow
currentpage = PAGE
if touch_x >= int(WIDTH - ICONSIZE): # > Touched
if moresprite[0] != BLANK: # Ensure there are more
if spot == (PAGEMAXFILES - 1): # Page full
if currentfile < (len(filenames)): # and more files
PAGE = PAGE + 1 # Increment page
if touch_x <= ICONSIZE: # < Touched
if PAGE > 1:
PAGE = PAGE - 1 # Decrement page
else:
lesssprite[0] = BLANK # Not show < for first page
print("Page ", PAGE)
# Icon Positioning
if PAGE != currentpage: # We have a page change
# Reset icon locations to upper left
xpos = LEFTSPACE
ypos = TOPSPACE
spot = 0
if currentpage > PAGE:
# Decrement files by a page (current page & previous page)
currentfile = currentfile - (PAGEMAXFILES * 2) + 1
else:
# Forward go to the next file
currentfile = currentfile + 1
else:
currentfile += 1 # Increment file counter
spot += 1 # Increment icon space counter
if spot == PAGEMAXFILES: # Last page ended with
print("hit")
# calculate next icon location
if spot % ICONSACROSS: # not at end of icon row
xpos += SPACING + ICONSIZE
else: # start new icon row
ypos += ICONSIZE + SPACING + TEXTSPACE
xpos = LEFTSPACE
# End If Changed Page
# Blank out rest if needed
if currentfile == len(filenames):
for i in range(spot, PAGEMAXFILES):
sprites[i][0] = BLANK
labels[i].text = " "
# End while
Coding the application on the Adafruit PyPortal with CircuitPython provides a clean, high level method of creating a user interface which can be adapted for other displays of varying sizes and types.
If a greyscale display, smaller (or larger) display or possibly an eInk device was desired, the application only would need a few changes (if there is capacitive touch, a bit more coding to use buttons if not).
import os import time import board import displayio import adafruit_imageload import terminalio import adafruit_touchscreen from adafruit_display_text import label from adafruit_display_shapes.rect import Rect
A function, get_files(), gets a dictionary of files for the passed-in director name. For this demo, the base directory "/" is used. If the code is modified to do subdirectories, etc. the function only needs the correct path. The return is a dictionary of filename, boolean tuples. If the boolean is True, the filename refers to a directory.
# Get a dictionary of filenames at the passed base directory
# each entry is a tuple (filename, bool) where bool = true
# means the filename is a directory, else false.
def get_files(base):
files = os.listdir(base)
file_names = []
for j, filetext in enumerate(files):
if not filetext.startswith("."):
if filetext not in ('boot_out.txt', 'System Volume Information'):
stats = os.stat(base + filetext)
isdir = stats[0] & 0x4000
if isdir:
file_names.append((filetext, True))
else:
file_names.append((filetext, False))
return filenames
And then a function to wait and get a touchscreen touch and return the x coordinate only (all that is needed):
def get_touch(screen)
p = None
while p is None:
time.sleep(0.05)
p = screen.touch_point
return p[0]
The values which get the file icons spaced out appropriately. The values are for PyPortal, but if you want to use a different display, you can change the values here rather than "magic numbers" scattered throughout the code.
# Icon Positions ICONSIZE = 48 SPACING = 18 LEFTSPACE = 38 TOPSPACE = 10 TEXTSPACE = 10 ICONSACROSS = 4 ICONSDOWN = 3 PAGEMAXFILES = ICONSACROSS * ICONSDOWN # For the chosen display, this is the # maximum number of file icons that will fit # on the display at once (display dependent)
There is blank icon, followed by five defined file icons, and the left & right arrows for pagination. If more icons are wanted, add them after the last file icon (currently PY).
# File Types BLANK = 0 FILE = 1 DIR = 2 BMP = 3 WAV = 4 PY = 5 RIGHT = 6 LEFT = 7
The display is then set up. The code uses the CircuitPython displayio framework. The display touchscreen is set up for use.
# Use the builtin display
display = board.DISPLAY
WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height
ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
board.TOUCH_YD, board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(WIDTH, HEIGHT))
# Create base display group
displaygroup = displayio.Group()
Now to load the icons:
# Load the bitmap (this is the "spritesheet")
sprite_sheet, palette = adafruit_imageload.load("/icons.bmp")
This code sets up all the icon positions based on the constants defined earlier:
sprites = []
labels = []
for _ in range(PAGEMAXFILES):
sprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width=1, height=1, tile_height=48,
tile_width=48,)
sprites.append(sprite) # Append the sprite to the sprite array
displaygroup.append(sprite)
filelabel = label.Label(terminalio.FONT, color=0xFFFFFF)
labels.append(filelabel)
displaygroup.append(filelabel)
# Make the more files and less files icons (> <)
moresprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width=1, height=1, tile_height=48,
tile_width=48,)
displaygroup.append(moresprite)
moresprite.x = WIDTH - ICONSIZE + TEXTSPACE
moresprite.y = int((HEIGHT - ICONSIZE) / 2)
lesssprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width=1, height=1, tile_height=48,
tile_width=48,)
displaygroup.append(lesssprite)
lesssprite.x = -10
lesssprite.y = int((HEIGHT - ICONSIZE) / 2)
display.root_group = displaygroup
The code iterates through all the filenames, displaying them in sequential icon locations. The icons are labeled with the first 10 characters of the filename (without the extension). This can be changed, there isn't a lot of room.
filename, dirfile = filenames[currentfile]
if dirfile:
filetype = DIR
elif filename.endswith(".bmp"):
filetype = BMP
elif filename.endswith(".wav"):
filetype = WAV
elif filename.endswith(".py"):
filetype = PY
else:
filetype = FILE
# Set icon location information and icon type
sprites[spot].x = xpos
sprites[spot].y = ypos
sprites[spot][0] = filetype
#
# Set filename
labels[spot].x = xpos
labels[spot].y = ypos + ICONSIZE + TEXTSPACE
# The next line gets the filename without the extension, first 11 chars
labels[spot].text = filename.rsplit('.', 1)[0][0:10]
The next chunk of code is lengthy, handling the pagination. Putting the < and > icons on screen as necessary, check for a touch on the screen, and changing pages (clearing any icons as needed). It's page oriented, there is not smooth scrolling or any dragging a finger to see files.
touch_x = get_touch(ts)
print("Touch Registered ")
# Check if touch_x is around the LEFT or RIGHT arrow
currentpage = PAGE
if touch_x >= int(WIDTH - ICONSIZE): # > Touched
if moresprite[0] != BLANK: # Ensure there are more
if spot == (PAGEMAXFILES - 1): # Page full
if currentfile < (len(filenames)): # and more files
PAGE = PAGE + 1 # Increment page
if touch_x <= ICONSIZE: # < Touched
if PAGE > 1:
PAGE = PAGE - 1 # Decrement page
else:
lesssprite[0] = BLANK # Not show < for first page
print("Page ", PAGE)
# Icon Positioning
if PAGE != currentpage: # We have a page change
# Reset icon locations to upper left
xpos = LEFTSPACE
ypos = TOPSPACE
spot = 0
if currentpage > PAGE:
# Decrement files by a page (current page & previous page)
currentfile = currentfile - (PAGEMAXFILES * 2) + 1
else:
# Forward go to the next file
currentfile = currentfile + 1
else:
currentfile += 1 # Increment file counter
spot += 1 # Increment icon space counter
if spot == PAGEMAXFILES: # Last page ended with
print("hit")
# calculate next icon location
if spot % ICONSACROSS: # not at end of icon row
xpos += SPACING + ICONSIZE
else: # start new icon row
ypos += ICONSIZE + SPACING + TEXTSPACE
xpos = LEFTSPACE
# End If Changed Page
# Blank out rest if needed
if currentfile == len(filenames):
for i in range(spot, PAGEMAXFILES):
sprites[i][0] = BLANK
labels[i].text = " "
# End while
Page last edited January 22, 2025
Text editor powered by tinymce.