All together now!
So now that we know how all the components work, here is an example of how this would all go together. This example uses all of these tools to make a UI with three window tabs with elements like buttons and images on them. You can start by using this as a base and customize it to be your own.
You can click Download: Project Zip in the code listing to get all the files or click the green button to see the files in the GitHub repository.
Copy the files to the PyPortal CIRCUITPY drive in the directories listed at the bottom of the page.
# SPDX-FileCopyrightText: 2020 Richard Albritton for Adafruit Industries # # SPDX-License-Identifier: MIT import time import board import microcontroller import displayio import busio from analogio import AnalogIn import neopixel import adafruit_adt7410 from adafruit_bitmap_font import bitmap_font from adafruit_display_text.label import Label from adafruit_button import Button import adafruit_touchscreen from adafruit_pyportal import PyPortal # ------------- Constants ------------- # # Sound Effects soundDemo = "/sounds/sound.wav" soundBeep = "/sounds/beep.wav" soundTab = "/sounds/tab.wav" # Hex Colors WHITE = 0xFFFFFF RED = 0xFF0000 YELLOW = 0xFFFF00 GREEN = 0x00FF00 BLUE = 0x0000FF PURPLE = 0xFF00FF BLACK = 0x000000 # Default Label styling TABS_X = 0 TABS_Y = 15 # Default button styling: BUTTON_HEIGHT = 40 BUTTON_WIDTH = 80 # Default State view_live = 1 icon = 1 icon_name = "Ruby" button_mode = 1 switch_state = 0 # ------------- Functions ------------- # # Backlight function # Value between 0 and 1 where 0 is OFF, 0.5 is 50% and 1 is 100% brightness. def set_backlight(val): val = max(0, min(1.0, val)) try: board.DISPLAY.auto_brightness = False except AttributeError: pass board.DISPLAY.brightness = val # Helper for cycling through a number set of 1 to x. def numberUP(num, max_val): num += 1 if num <= max_val: return num else: return 1 # Set visibility of layer def layerVisibility(state, layer, target): try: if state == "show": time.sleep(0.1) layer.append(target) elif state == "hide": layer.remove(target) except ValueError: pass # This will handle switching Images and Icons def set_image(group, filename): """Set the image file for a given goup for display. This is most useful for Icons or image slideshows. :param group: The chosen group :param filename: The filename of the chosen image """ print("Set image to ", filename) if group: group.pop() if not filename: return # we're done, no icon desired # CircuitPython 6 & 7 compatible image_file = open(filename, "rb") image = displayio.OnDiskBitmap(image_file) image_sprite = displayio.TileGrid( image, pixel_shader=getattr(image, "pixel_shader", displayio.ColorConverter()) ) # # CircuitPython 7+ compatible # image = displayio.OnDiskBitmap(filename) # image_sprite = displayio.TileGrid(image, pixel_shader=image.pixel_shader) group.append(image_sprite) # return a reformatted string with word wrapping using PyPortal.wrap_nicely def text_box(target, top, string, max_chars): text = pyportal.wrap_nicely(string, max_chars) new_text = "" test = "" for w in text: new_text += "\n" + w test += "M\n" text_height = Label(font, text="M", color=0x03AD31) text_height.text = test # Odd things happen without this glyph_box = text_height.bounding_box target.text = "" # Odd things happen without this target.y = int(glyph_box[3] / 2) + top target.text = new_text def get_Temperature(source): if source: # Only if we have the temperature sensor celsius = source.temperature else: # No temperature sensor celsius = microcontroller.cpu.temperature return (celsius * 1.8) + 32 # ------------- Inputs and Outputs Setup ------------- # light_sensor = AnalogIn(board.LIGHT) try: # attempt to init. the temperature sensor i2c_bus = busio.I2C(board.SCL, board.SDA) adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48) adt.high_resolution = True except ValueError: # Did not find ADT7410. Probably running on Titano or Pynt adt = None # ------------- Screen Setup ------------- # pyportal = PyPortal() pyportal.set_background("/images/loading.bmp") # Display an image until the loop starts pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=1) # Touchscreen setup [ Rotate 270 ] display = board.DISPLAY display.rotation = 270 if board.board_id == "pyportal_titano": screen_width = 320 screen_height = 480 set_backlight( 1 ) # 0.3 brightness does not cause the display to be visible on the Titano else: screen_width = 240 screen_height = 320 set_backlight(0.3) # We want three buttons across the top of the screen TAB_BUTTON_Y = 0 TAB_BUTTON_HEIGHT = 40 TAB_BUTTON_WIDTH = int(screen_width / 3) # We want two big buttons at the bottom of the screen BIG_BUTTON_HEIGHT = int(screen_height / 3.2) BIG_BUTTON_WIDTH = int(screen_width / 2) BIG_BUTTON_Y = int(screen_height - BIG_BUTTON_HEIGHT) # Initializes the display touch screen area ts = adafruit_touchscreen.Touchscreen( board.TOUCH_YD, board.TOUCH_YU, board.TOUCH_XR, board.TOUCH_XL, calibration=((5200, 59000), (5800, 57000)), size=(screen_width, screen_height), ) # ------------- Display Groups ------------- # splash = displayio.Group() # The Main Display Group view1 = displayio.Group() # Group for View 1 objects view2 = displayio.Group() # Group for View 2 objects view3 = displayio.Group() # Group for View 3 objects # ------------- Setup for Images ------------- # bg_group = displayio.Group() splash.append(bg_group) set_image(bg_group, "/images/BGimage.bmp") icon_group = displayio.Group() icon_group.x = 180 icon_group.y = 120 icon_group.scale = 1 view2.append(icon_group) # ---------- Text Boxes ------------- # # Set the font and preload letters font = bitmap_font.load_font("/fonts/Helvetica-Bold-16.bdf") font.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()") # Text Label Objects feed1_label = Label(font, text="Text Window 1", color=0xE39300) feed1_label.x = TABS_X feed1_label.y = TABS_Y view1.append(feed1_label) feed2_label = Label(font, text="Text Window 2", color=0xFFFFFF) feed2_label.x = TABS_X feed2_label.y = TABS_Y view2.append(feed2_label) sensors_label = Label(font, text="Data View", color=0x03AD31) sensors_label.x = TABS_X sensors_label.y = TABS_Y view3.append(sensors_label) sensor_data = Label(font, text="Data View", color=0x03AD31) sensor_data.x = TABS_X + 16 # Indents the text layout sensor_data.y = 150 view3.append(sensor_data) # ---------- Display Buttons ------------- # # This group will make it easy for us to read a button press later. buttons = [] # Main User Interface Buttons button_view1 = Button( x=0, # Start at furthest left y=0, # Start at top width=TAB_BUTTON_WIDTH, # Calculated width height=TAB_BUTTON_HEIGHT, # Static height label="View 1", label_font=font, label_color=0xFF7E00, fill_color=0x5C5B5C, outline_color=0x767676, selected_fill=0x1A1A1A, selected_outline=0x2E2E2E, selected_label=0x525252, ) buttons.append(button_view1) # adding this button to the buttons group button_view2 = Button( x=TAB_BUTTON_WIDTH, # Start after width of a button y=0, width=TAB_BUTTON_WIDTH, height=TAB_BUTTON_HEIGHT, label="View 2", label_font=font, label_color=0xFF7E00, fill_color=0x5C5B5C, outline_color=0x767676, selected_fill=0x1A1A1A, selected_outline=0x2E2E2E, selected_label=0x525252, ) buttons.append(button_view2) # adding this button to the buttons group button_view3 = Button( x=TAB_BUTTON_WIDTH * 2, # Start after width of 2 buttons y=0, width=TAB_BUTTON_WIDTH, height=TAB_BUTTON_HEIGHT, label="View 3", label_font=font, label_color=0xFF7E00, fill_color=0x5C5B5C, outline_color=0x767676, selected_fill=0x1A1A1A, selected_outline=0x2E2E2E, selected_label=0x525252, ) buttons.append(button_view3) # adding this button to the buttons group button_switch = Button( x=0, # Start at furthest left y=BIG_BUTTON_Y, width=BIG_BUTTON_WIDTH, height=BIG_BUTTON_HEIGHT, label="Light Switch", label_font=font, label_color=0xFF7E00, fill_color=0x5C5B5C, outline_color=0x767676, selected_fill=0x1A1A1A, selected_outline=0x2E2E2E, selected_label=0x525252, ) buttons.append(button_switch) # adding this button to the buttons group button_2 = Button( x=BIG_BUTTON_WIDTH, # Starts just after button 1 width y=BIG_BUTTON_Y, width=BIG_BUTTON_WIDTH, height=BIG_BUTTON_HEIGHT, label="Light Color", label_font=font, label_color=0xFF7E00, fill_color=0x5C5B5C, outline_color=0x767676, selected_fill=0x1A1A1A, selected_outline=0x2E2E2E, selected_label=0x525252, ) buttons.append(button_2) # adding this button to the buttons group # Add all of the main buttons to the splash Group for b in buttons: splash.append(b) # Make a button to change the icon image on view2 button_icon = Button( x=150, y=60, width=BUTTON_WIDTH, height=BUTTON_HEIGHT, label="Icon", label_font=font, label_color=0xFFFFFF, fill_color=0x8900FF, outline_color=0xBC55FD, selected_fill=0x5A5A5A, selected_outline=0xFF6600, selected_label=0x525252, style=Button.ROUNDRECT, ) buttons.append(button_icon) # adding this button to the buttons group # Add this button to view2 Group view2.append(button_icon) # Make a button to play a sound on view2 button_sound = Button( x=150, y=170, width=BUTTON_WIDTH, height=BUTTON_HEIGHT, label="Sound", label_font=font, label_color=0xFFFFFF, fill_color=0x8900FF, outline_color=0xBC55FD, selected_fill=0x5A5A5A, selected_outline=0xFF6600, selected_label=0x525252, style=Button.ROUNDRECT, ) buttons.append(button_sound) # adding this button to the buttons group # Add this button to view2 Group view3.append(button_sound) # pylint: disable=global-statement def switch_view(what_view): global view_live if what_view == 1: button_view1.selected = False button_view2.selected = True button_view3.selected = True layerVisibility("hide", splash, view2) layerVisibility("hide", splash, view3) layerVisibility("show", splash, view1) elif what_view == 2: # global icon button_view1.selected = True button_view2.selected = False button_view3.selected = True layerVisibility("hide", splash, view1) layerVisibility("hide", splash, view3) layerVisibility("show", splash, view2) else: button_view1.selected = True button_view2.selected = True button_view3.selected = False layerVisibility("hide", splash, view1) layerVisibility("hide", splash, view2) layerVisibility("show", splash, view3) # Set global button state view_live = what_view print("View {view_num:.0f} On".format(view_num=what_view)) # pylint: enable=global-statement # Set veriables and startup states button_view1.selected = False button_view2.selected = True button_view3.selected = True button_switch.label = "OFF" button_switch.selected = True layerVisibility("show", splash, view1) layerVisibility("hide", splash, view2) layerVisibility("hide", splash, view3) # Update out Labels with display text. text_box( feed1_label, TABS_Y, "The text on this screen is wrapped so that all of it fits nicely into a " "text box that is {} x {}.".format( feed1_label.bounding_box[2], feed1_label.bounding_box[3] * 2 ), 30, ) text_box(feed2_label, TABS_Y, "Tap on the Icon button to meet a new friend.", 18) text_box( sensors_label, TABS_Y, "This screen can display sensor readings and tap Sound to play a WAV file.", 28, ) board.DISPLAY.root_group = splash # ------------- Code Loop ------------- # while True: touch = ts.touch_point light = light_sensor.value sensor_data.text = "Touch: {}\nLight: {}\nTemp: {:.0f}°F".format( touch, light, get_Temperature(adt) ) # Will also cause screen to dim when hand is blocking sensor to touch screen # # Adjust backlight # if light < 1500: # set_backlight(0.1) # elif light < 3000: # set_backlight(0.5) # else: # set_backlight(1) # ------------- Handle Button Press Detection ------------- # if touch: # Only do this if the screen is touched # loop with buttons using enumerate() to number each button group as i for i, b in enumerate(buttons): if b.contains(touch): # Test each button to see if it was pressed print("button{} pressed".format(i)) if i == 0 and view_live != 1: # only if view1 is visible pyportal.play_file(soundTab) switch_view(1) while ts.touch_point: pass if i == 1 and view_live != 2: # only if view2 is visible pyportal.play_file(soundTab) switch_view(2) while ts.touch_point: pass if i == 2 and view_live != 3: # only if view3 is visible pyportal.play_file(soundTab) switch_view(3) while ts.touch_point: pass if i == 3: pyportal.play_file(soundBeep) # Toggle switch button type if switch_state == 0: switch_state = 1 b.label = "ON" b.selected = False pixel.fill(WHITE) print("Switch ON") else: switch_state = 0 b.label = "OFF" b.selected = True pixel.fill(BLACK) print("Switch OFF") # for debounce while ts.touch_point: pass print("Switch Pressed") if i == 4: pyportal.play_file(soundBeep) # Momentary button type b.selected = True print("Button Pressed") button_mode = numberUP(button_mode, 5) if button_mode == 1: pixel.fill(RED) elif button_mode == 2: pixel.fill(YELLOW) elif button_mode == 3: pixel.fill(GREEN) elif button_mode == 4: pixel.fill(BLUE) elif button_mode == 5: pixel.fill(PURPLE) switch_state = 1 button_switch.label = "ON" button_switch.selected = False # for debounce while ts.touch_point: pass print("Button released") b.selected = False if i == 5 and view_live == 2: # only if view2 is visible pyportal.play_file(soundBeep) b.selected = True while ts.touch_point: pass print("Icon Button Pressed") icon = numberUP(icon, 3) if icon == 1: icon_name = "Ruby" elif icon == 2: icon_name = "Gus" elif icon == 3: icon_name = "Billie" b.selected = False text_box( feed2_label, TABS_Y, "Every time you tap the Icon button the icon image will " "change. Say hi to {}!".format(icon_name), 18, ) set_image(icon_group, "/images/" + icon_name + ".bmp") if i == 6 and view_live == 3: # only if view3 is visible b.selected = True while ts.touch_point: pass print("Sound Button Pressed") pyportal.play_file(soundDemo) b.selected = False
When you are finished the CIRCUITPY drive should look something like this:
Text editor powered by tinymce.