Skip to content

Adding a standalone calculator extension #1109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/en/calculator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Calculator

This extension adds a basic 4-function (+,-.*,/) calculator used with an attached display.

## Adding the calculator extension

You'll need to make sure you have a [display](Display.md) working first. Then import the calculator functions:

`from kmk.extensions.calculator import Calc, InitCalc`

Initialise the calculator, point it to the display, and tell it the layer you want your calculator functions associated with. For example, if your display is called "display" and your calculator buttons are going to be on layer 1, use:

`InitCalc(display,1)`

This will prepare a "calculator ready" screen on that layer.

Now you need to create the keys for your calculator. Normally there will be 17 of these: one for each digit, one for each arithmetic function, one for the decimal point, one for the equals/enter button, one to clear, and one to send the current result to the connected computer. Each one needs to be pointed to the display and layer like the init function above:

```
KC_C0 = Calc("0",display,1)
KC_C1 = Calc("1",display,1)
KC_C2 = Calc("2",display,1)
KC_C3 = Calc("3",display,1)
KC_C4 = Calc("4",display,1)
KC_C5 = Calc("5",display,1)
KC_C6 = Calc("6",display,1)
KC_C7 = Calc("7",display,1)
KC_C8 = Calc("8",display,1)
KC_C9 = Calc("9",display,1)
KC_CEQ = Calc("=",display,1)
KC_CADD = Calc("+",display,1)
KC_CSUB = Calc("-",display,1)
KC_CMUL = Calc("*",display,1)
KC_CDIV = Calc("/",display,1)
KC_CPT = Calc(".",display,1)
KC_CCL = Calc("c",display,1) #clear
KC_CSND = Calc("s",display,1) #send result
```

Now you can add these new keycodes to your keymap and you'll have a working desktop calculator!
1 change: 1 addition & 0 deletions docs/en/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ extensions are
be treated the same as underglow.
- [Status LED](extension_statusled.md): Indicates which layer you are on with an array of single leds.
- [Stringy Keymaps](extension_stringy_keymaps): Enables referring to keys by `'NAME'` rather than `KC.NAME`
- [Calculator](calculator.md): Adds a simple standalone 4-function calculator
125 changes: 125 additions & 0 deletions kmk/extensions/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from kmk.extensions.display import Display, TextEntry, ImageEntry

from kmk.keys import Key

from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC

import time

from kmk.modules.macros import Macros

macros=Macros()

binary = {
'+': (lambda a, b: a+b, lambda a, b: a * (1+b/100)),
'-': (lambda a, b: a-b, lambda a, b: a * (1-b/100)),
'*': (lambda a, b: a*b, lambda a, b: a * (b/100)),
'/': (lambda a, b: a/b, lambda a, b: a / (b/100)),
}

equation = ""
entry = ""
number1 = None
number2 = None
trail = ["Ready."]
op = None

class Calc(Key):
def __init__(self, key, display, layer):
self.key = key
self.display = display
self.layer = layer
return

def on_press(self, keyboard, coord_int=None):
global entry
global number1
global number2
global trail
global op
global binary
global equation

if len(self.key) == 1 and self.key in "0123456789":
if equation == "":
number1 = None
number2 = None
entry = entry + self.key
equation = equation + self.key
self.display.entries = [TextEntry(equation,x=10,y=10,y_anchor="T",layer=self.layer)]

if self.key == "." and not "." in entry:
if entry == "":
entry = "0"
entry = entry + self.key
equation = equation + self.key
self.display.entries = [TextEntry(equation,x=10,y=10,y_anchor="T",layer=self.layer)]

if self.key == "c":
entry = ""
equation = ""
number1 = None
number2 = None
op = None
self.display.entries= [TextEntry("= Calculator Ready =",x=64,y=10,x_anchor="M",layer=self.layer)]

if self.key == "=" and op is not None:
if equation == "":
equation = format_number(str(number1)) + op + format_number(str(number2))
if entry is not "":
number2 = float(entry)
entry = ""
print("doing binary operation with " + str(number1) + ", " + op + ", " + str(number2))
self.do_binary_op()
self.display.entries=[TextEntry(equation,x=10,y=10,y_anchor="T",layer=self.layer),
TextEntry(format_number(str(number1)),x=118,y=54,x_anchor="R",y_anchor="B",layer=self.layer)]
equation = ""

if self.key in binary:
self.display.entries=[TextEntry(equation,x=10,y=10,y_anchor="T",layer=self.layer)]
if entry is not "":
if number1 is None:
number1 = float(entry)
entry = ""
else:
number2 = float(entry)
entry = ""
print("doing binary operation with " + str(number1) + ", " + op + ", " + str(number2))
self.do_binary_op()
op = self.key
if equation == "":
equation = equation + format_number(str(number1))
equation = equation + self.key
self.display.entries=[TextEntry(equation,x=10,y=10,y_anchor="T",layer=self.layer)]

if self.key == "s" and number1 is not None:
keyboard.tap_key(KC.MACRO(format_number(str(number1))))

self.display.render(self.layer)

def on_release(self, keyboard, coord_int=None):
return

def do_binary_op(self):
global number1
global number2
global op
global binary
if op and number2 is not None:
number1 = binary[op][0](number1, number2)

class InitCalc:
def __init__(self,display,layer):
display.entries += [TextEntry("= Calculator Ready =",x=64,y=10,x_anchor="M",layer=layer)]

def format_number(num_str):
if "." in num_str:
parts = num_str.split(".")
integer_part = parts[0]
decimal_part = parts[1].rstrip("0")
if decimal_part:
return f"{integer_part}.{decimal_part}"
else:
return integer_part
return num_str