To get set up, we will need CircuitPython, a few libraries and the PyPortal Calculator Source Code and font downloaded from Github.
CircuitPython
First, make sure you are running the latest version of Adafruit CircuitPython for your board. Because of the speed-ups that were added, the PyPortal Calculator requires at least CircuitPython 4.1.0.
To download the CircuitPython beta, visit the following link for the PyPortal and download the UF2 for CircuitPython beta. You must be using CircuitPython 4.1.0 or later for PyPortal Calculator to work fast enough!
Required CircuitPython Libraries
Next, you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle. Our introduction guide has a great page on how to install the library bundle for both express and non-express boards.
Since the full set of libraries take up a good deal of space, we recommend that you manually install only the necessary libraries from the bundle:
- adafruit_bitmap_font
- adafruit_display_shapes
- adafruit_display_text
- adafruit_button
- adafruit_touchscreen
Before continuing make sure your board's lib folder or root filesystem have the adafruit_bitmap_font, adafruit_display_shapes, adafruit_display_text, adafruit_button, and adafruit_touchscreen files and folders copied over.
All of the files should go in a subdirectory of your PyPortal's main CIRCUITPY.
Here is the main CircuitPython code in its entirety. If you click on Download Project Zip, it will download all files used in this tutorial also.
# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries # # SPDX-License-Identifier: MIT """ PyPortal Calculator Demo """ import time from collections import namedtuple import board import displayio from adafruit_display_text.label import Label from adafruit_bitmap_font import bitmap_font from adafruit_display_shapes.rect import Rect from adafruit_button import Button from calculator import Calculator import adafruit_touchscreen Coords = namedtuple("Point", "x y") ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, board.TOUCH_YD, board.TOUCH_YU, calibration=((5200, 59000), (5800, 57000)), size=(320, 240)) # Settings BUTTON_WIDTH = 60 BUTTON_HEIGHT = 30 BUTTON_MARGIN = 8 MAX_DIGITS = 29 BLACK = 0x0 ORANGE = 0xFF8800 WHITE = 0xFFFFFF GRAY = 0x888888 LABEL_OFFSET = 290 # Make the display context calc_group = displayio.Group() board.DISPLAY.root_group = calc_group # Make a background color fill color_bitmap = displayio.Bitmap(320, 240, 1) color_palette = displayio.Palette(1) color_palette[0] = GRAY bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0) calc_group.append(bg_sprite) # Load the font font = bitmap_font.load_font("/fonts/Arial-12.bdf") buttons = [] # Some button functions def button_grid(row, col): return Coords(BUTTON_MARGIN * (row + 1) + BUTTON_WIDTH * row + 20, BUTTON_MARGIN * (col + 1) + BUTTON_HEIGHT * col + 40) def add_button(row, col, label, width=1, color=WHITE, text_color=BLACK): pos = button_grid(row, col) new_button = Button(x=pos.x, y=pos.y, width=BUTTON_WIDTH * width + BUTTON_MARGIN * (width - 1), height=BUTTON_HEIGHT, label=label, label_font=font, label_color=text_color, fill_color=color, style=Button.ROUNDRECT) buttons.append(new_button) return new_button def find_button(label): result = None for _, btn in enumerate(buttons): if btn.label == label: result = btn return result border = Rect(20, 8, 280, 35, fill=WHITE, outline=BLACK, stroke=2) calc_display = Label(font, text="0", color=BLACK) calc_display.y = 25 clear_button = add_button(0, 0, "AC") add_button(1, 0, "+/-") add_button(2, 0, "%") add_button(3, 0, "/", 1, ORANGE, WHITE) add_button(0, 1, "7") add_button(1, 1, "8") add_button(2, 1, "9") add_button(3, 1, "x", 1, ORANGE, WHITE) add_button(0, 2, "4") add_button(1, 2, "5") add_button(2, 2, "6") add_button(3, 2, "-", 1, ORANGE, WHITE) add_button(0, 3, "1") add_button(1, 3, "2") add_button(2, 3, "3") add_button(3, 3, "+", 1, ORANGE, WHITE) add_button(0, 4, "0", 2) add_button(2, 4, ".") add_button(3, 4, "=", 1, ORANGE, WHITE) # Add the display and buttons to the main calc group calc_group.append(border) calc_group.append(calc_display) for b in buttons: calc_group.append(b) calculator = Calculator(calc_display, clear_button, LABEL_OFFSET) button = "" while True: point = ts.touch_point if point is not None: # Button Down Events for _, b in enumerate(buttons): if b.contains(point) and button == "": b.selected = True button = b.label elif button != "": # Button Up Events last_op = calculator.get_current_operator() op_button = find_button(last_op) # Deselect the last operation when certain buttons are pressed if op_button is not None: if button in ('=', 'AC', 'CE'): op_button.selected = False elif button in ('+', '-', 'x', '/') and button != last_op: op_button.selected = False calculator.add_input(button) b = find_button(button) if b is not None: if button not in ('+', '-', 'x', '/') or button != calculator.get_current_operator(): b.selected = False button = "" time.sleep(0.05)
Here is the CircuitPython calculator class in its entirety. If you click on Download Project Zip, it will download all files used in this tutorial also.
# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries # # SPDX-License-Identifier: MIT """ CircuitPython library to handle the input and calculations * Author(s): Melissa LeBlanc-Williams """ # pylint: disable=eval-used def calculate(number_one, operator, number_two): result = eval(number_one + operator + number_two) if int(result) == result: result = int(result) return str(result) class Calculator: def __init__(self, calc_display, clear_button, label_offset): self._error = False self._calc_display = calc_display self._clear_button = clear_button self._label_offset = label_offset self._accumulator = "0" self._operator = None self._equal_pressed = False self._operand = None self._all_clear() def get_current_operator(self): operator = self._operator if operator == "*": operator = "x" return operator def _all_clear(self): self._accumulator = "0" self._operator = None self._equal_pressed = False self._clear_entry() def _clear_entry(self): self._operand = None self._error = False self._set_button_ce(False) self._set_text("0") def _set_button_ce(self, entry_only): self._clear_button.selected = False if entry_only: self._clear_button.label = "CE" else: self._clear_button.label = "AC" def _set_text(self, text): self._calc_display.text = text _, _, screen_w, _ = self._calc_display.bounding_box self._calc_display.x = self._label_offset - screen_w def _get_text(self): return self._calc_display.text def _handle_number(self, input_key): display_text = self._get_text() if self._operand is None and self._operator is not None: display_text = "" elif self._operand is not None and self._operator is not None and self._equal_pressed: self._accumulator = self._operand self._operator = None self._operand = None display_text = "" elif display_text == "0": display_text = "" display_text += input_key self._set_text(display_text) if self._operator is not None: self._operand = display_text self._set_button_ce(True) self._equal_pressed = False def _handle_operator(self, input_key): if input_key == "x": input_key = "*" if self._equal_pressed: self._operand = None if self._operator is None: self._operator = input_key else: # Perform current calculation before changing input_keys if self._operand is not None: self._accumulator = calculate(self._accumulator, self._operator, self._operand) self._set_text(self._accumulator) self._operand = None self._operator = input_key self._accumulator = self._get_text() self._equal_pressed = False def _handle_equal(self): if self._operator is not None: if self._operand is None: self._operand = self._get_text() self._accumulator = calculate(self._accumulator, self._operator, self._operand) self._set_text(self._accumulator) self._equal_pressed = True def _update_operand(self): if self._operand is not None: self._operand = self._get_text() def add_input(self, input_key): try: if self._error: self._clear_entry() elif input_key == "AC": self._all_clear() elif input_key == "CE": self._clear_entry() elif self._operator is None and input_key == "0": pass elif len(input_key) == 1 and 48 <= ord(input_key) <= 57: self._handle_number(input_key) elif input_key in ('+', '-', '/', 'x'): self._handle_operator(input_key) elif input_key == ".": if not input_key in self._get_text(): self._set_text(self._get_text() + input_key) self._set_button_ce(True) self._equal_pressed = False elif input_key == "+/-": self._set_text(calculate(self._get_text(), "*", "-1")) self._update_operand() elif input_key == "%": self._set_text(calculate(self._get_text(), "/", "100")) self._update_operand() elif input_key == "=": self._handle_equal() except (ZeroDivisionError, RuntimeError): self._all_clear() self._error = True self._set_text("Error")
After copying everything over, your PyPortal should display a Calculator on it. In the next few sections, we'll go over the User Interface Elements and examine the code more closely.