# CircuitPython Code

Now let's take a look at the CircuitPython code for the calculator program. It's divided into two main files. The Main file sets everything up and runs the main loop for the calculator. The Calculator class handles all of the piecing together the inputs and performing the calculations. It does a little bit of button handling such as changing the All Clear to Clear Entry and back again. You can find the complete code listings on the Initial Setup page.

## Main File

We start by importing everything we'll need for the calculator.

```import time
from collections import namedtuple
import board
import displayio
from calculator import Calculator

Next, we create a Named Tuple called Coords. This allows us easily generate a grid and assign buttons into that grid.

`Coords = namedtuple("Point", "x y")`

Next we setup the touchscreen. The calibration values provided are the default ones that seem to work well with the touchscreen library.

```ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
board.TOUCH_YD, board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(320, 240))```

Next we have settings to make it easy to make small adjustments to the calculator.

```# Settings
BUTTON_WIDTH = 60
BUTTON_HEIGHT = 30
BUTTON_MARGIN = 8
MAX_DIGITS = 29
BLACK = 0x0
ORANGE = 0xFF8800
WHITE = 0xFFFFFF
GRAY = 0x888888
LABEL_OFFSET = 290```

Then we create our main group as described on the UI Elements page.

```# Make the display context
calc_group = displayio.Group(max_size=25)
board.DISPLAY.show(calc_group)```

Now we create a solid colored background and add it to the group we created.

```# Make a background color fill
color_bitmap = displayio.Bitmap(320, 240, 1)
color_palette = displayio.Palette(1)
color_palette = GRAY
bg_sprite = displayio.TileGrid(color_bitmap,
x=0, y=0)
calc_group.append(bg_sprite)```

Next we'll create the bitmap font and create an empty buttons list.

```# Load the font
buttons = []```

Let's now define a few button functions that make setting up a button grid easy.

button_grid is responsible for calculating the x and y position of the button based on the grid square where we want to add the button.

add_button is a convenience function that will take a few parameters and handle all of the common button parameters itself. Most buttons have a width on 1 meaning it only takes up 1 grid square. However, the 0 button takes up 2 grid squares.

We can also easily set the button colors through here. This automatically adds the new button to the `buttons` list. We also pass back the reference to the button.

find_button allows us to iterate through the `buttons` list and find the button object by the label.

```# 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```

Now let's create a border using a rectangle since a label doesn't have any borders and a label for the display.

```border = Rect(20, 8, 280, 35, fill=WHITE, outline=BLACK, stroke=2)
calc_display = Label(font, text="0", color=BLACK, max_glyphs=MAX_DIGITS)
calc_display.y = 25```

Now we'll create all of the buttons using those handy functions.

```clear_button = add_button(0, 0, "AC")
add_button(3, 0, "/", 1, ORANGE, WHITE)
add_button(3, 1, "x", 1, ORANGE, WHITE)
add_button(3, 2, "-", 1, ORANGE, WHITE)
add_button(3, 3, "+", 1, ORANGE, WHITE)
add_button(3, 4, "=", 1, ORANGE, WHITE)```

After that, we'll add all of those items to our main group.

```calc_group.append(border)
calc_group.append(calc_display)
for b in buttons:
calc_group.append(b.group)```

Now it's time to create the calculator object based on the Calculator class that we'll discuss below. We pass in a reference to a few UI elements that the class will handle. Label Offset is used because the label does not automatically right-align, so we need to adjust it every time we change the label text.

`calculator = Calculator(calc_display, clear_button, LABEL_OFFSET)`

Finally, we have our main loop, which mostly handles scanning buttons when pressed and selecting/deselecting the buttons. There is a small delay at the end to allow help debounce the touchscreen presses. The `_` is used in the for loop as a placeholder since we won't be using the variable returned.

```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
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)```

## Calculator Class

For the Calculator class, we'll take a look at each of the functions. The first function actually sits outside of the class and passes the calculating over to CircuitPython using the `eval()` function. This could optionally be changed to just use simple math, but this way we don't need to do any string parsing.

```def calculate(number_one, operator, number_two):
result = eval(number_one + operator + number_two)
if int(result) == result:
result = int(result)
return str(result)```

The `__init__()` function just initializes all of the parameters and places the calculator in an All Clear state.

```    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()```

The `get_current_operator()` function is used by the main loop to assist with selecting and deselecting the current operator buttons on the calculator.

```    def get_current_operator(self):
operator = self._operator
if operator == "*":
operator = "x"
return operator```

The `_all_clear()` function sets all of the variables to their initial state. Note that the functions that start with an underscore are private functions and only meant to be used inside the class.

```    def _all_clear(self):
self._accumulator = "0"
self._operator = None
self._equal_pressed = False
self._clear_entry()```

The `_clear_entry()` function sets current clears the current operand to its initial state and changes the CE button to AC.

```    def _clear_entry(self):
self._operand = None
self._error = False
self._set_button_ce(False)
self._set_text("0")```

While not strictly necessary, the `_set_button_ce()` function makes it easy to change the CE/AC button text. Deselecting the clear button was done here because it would sometimes get confused with the button label changing.

```    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"```

The `_set_text()` function sets the display text and shifts it over to allow right-alignment of text. The `_` is used as a placeholder since we won't be using the variables returned.

```    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```

The `_get_text()` function is the counterpart to the _set_text() function and makes the code more readable.

```    def _get_text(self):
return self._calc_display.text```

The `_handle_number()` function handles the 0-9 digits as input. The variables that are set depend on the current state of other variables.

```    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```

Likewise, the `_handle_operator()` function handles the `+`, `-`, `x`, and `/` operator buttons. Since `x` isn't a valid operation, we just check for that and change it to `*` for multiplication.

```    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```

The `_handle_equal()` function handles the equal button. This also handles pressing equal multiple times to continue the current operation.

```    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```

The `_update_operand()` makes sure the operand matches the number displayed for a couple of operations.

```    def _update_operand(self):
if self._operand is not None:
self._operand = self._get_text()```

Finally we have the `add_input()` function, which handles sending the input to the appropriate handler function. It also handles the error state such as a Divide by Zero error or displaying too many numbers.

```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")```

## Conclusion

We hope you enjoyed this guide and are able to make use of either a Python-based Calculator, the User Interface Elements, or both.

This guide was first published on Jul 13, 2019. It was last updated on Jul 13, 2019. This page (CircuitPython Code) was last updated on Jan 21, 2020.