here is an example of how to go about writing a custom widget for the keyboard in MicroPython.
This is not complete and is not going to work. It has most of the mechanics there but not all. You should be able to get a good idea of what to do using this…
import lvgl as lv
UNUSED_KEY = '\x00'
TAB_KEY = '\t'
CAPS_KEY = ['CAPS', 'CAPS']
ENTER_KEY = lv.SYMBOL.NEW_LINE
BACKSPACE_KEY = lv.SYMBOL.BACKSPACE
L_SHIFT_KEY = ['SHIFT', 'SHIFT']
R_SHIFT_KEY = L_SHIFT_KEY
L_ALT_KEY = lv.SYMBOL.CLOSE
R_ALT_KEY = lv.SYMBOL.SETTINGS
L_START_KEY = lv.SYMBOL.SAVE
R_START_KEY = lv.SYMBOL.TRASH
L_CTRL_KEY = lv.SYMBOL.KEYBOARD
R_CTRL_KEY = lv.SYMBOL.PLAY
MENU_KEY = lv.SYMBOL.BARS
PART_KEY = 0x100000
# Sum of the min & max of (a, b, c)
def _hilo(a, b, c):
if c < b:
b, c = c, b
if b < a:
a, b = b, a
if c < b:
b, c = c, b
return a + c
def _complement(r, g, b):
k = _hilo(r, g, b)
return tuple(k - u for u in (r, g, b))
class Key(lv.button):
def __init__(self, parent):
super().__init__(parent)
self._latched = False
self._choices = None
self._special_key = None
self._lower_keycode = None
self._upper_keycode = None
self._shifted_keycode = None
self._shifted = False
self._capslock = False
self._label = lv.label(self)
self._label.set_text(' ')
self._label.center()
idle_color = self.get_text_color(0)
r, g, b = _complement(idle_color.red, idle_color.green, idle_color.blue)
lv.obj.set_text_color(self, lv.color_t(dict(red=r, green=g, blue=b)), lv.STATE.PRESSED)
def set_text_color(self, color, selector):
if selector == 0:
idle_color = self.get_text_color(0)
curr_press_color = _complement(idle_color.red, idle_color.green, idle_color.blue)
press_color = self.get_text_color(lv.STATE.PRESSED)
if (press_color.red, press_color.green, press_color.blue) == curr_press_color:
r, g, b = _complement(color.red, color.green. color.blue)
lv.obj.set_text_color(self, lv.color_t(dict(red=r, green=g, blue=b)), lv.STATE.PRESSED)
lv.obj.set_text_color(self, color, 0)
def set_keycode(self, keycode):
if isinstance(keycode, (list, tuple)):
self._choices = keycode
self._label.set_text(keycode[0])
elif len(keycode) == 2:
self._lower_keycode = keycode[0]
self._upper_keycode = None
self._shifted_keycode = keycode[1]
elif keycode == '\t':
self._special_key = keycode
self._label.set_text('TAB')
elif keycode == UNUSED_KEY:
self._special_key = keycode
self._label.set_text(' ')
else:
self._lower_keycode = keycode.lower()
self._upper_keycode = keycode.upper()
self._shifted_keycode = None
self._label.set_text(keycode.lower())
self._label.center()
def set_state(self, state, value):
if self._choices is not None and state == lv.STATE.PRESSED:
self._label.set_text(self._choices[int(value)])
self._label.layout()
self._label.set_align(self._label.get_align())
lv.obj.set_state(self, state, value)
def set_shift(self, value):
self._shifted = value
if value:
if self._choices in (L_SHIFT_KEY, R_SHIFT_KEY):
self.set_state(lv.STATE.PRESSED, True)
if self._shifted_keycode is not None:
self._label.set_text(self._shifted_key)
elif self._upper_keycode is not None:
self._label.set_text(self._upper_keycode)
else:
if self._choices in (L_SHIFT_KEY, R_SHIFT_KEY):
self.set_state(lv.STATE.PRESSED, False)
if self._capslock:
if self._upper_keycode is not None:
self._label.set_text(self._upper_keycode)
else:
self._label.set_text(self._lower_keycode)
self._label.layout()
self._label.set_align(self._label.get_align())
def get_shift(self):
return self._shifted
def set_capslock(self, value):
self._capslock = value
if value:
if self._choices == CAPS_KEY:
self.set_state(lv.STATE.PRESSED, True)
if not self._shifted and self._upper_keycode is not None:
self._label.set_text(self._upper_keycode)
else:
if self._choices == CAPS_KEY:
self.set_state(lv.STATE.PRESSED, False)
if not self._shifted and self._upper_keycode is not None:
self._label.set_text(self._lower_keycode)
def get_capslock(self):
return self._capslock
def get_keycode(self):
if self._special_key is not None:
return self._special_key
if self._choices is not None:
return self._choices
return self._label.get_text()
class Keyboard(lv.obj):
def __init__(self, parent):
# you can only use super to inherit the parents
# classes methods and attributes
super().__init__(parent)
self._pressed_button = None
self._hor_key_pad = 0.02
self._ver_key_pad = 0.02
self._key_maps = {}
self._key_factors = {}
self._active_map = None
self._buffer_maps = False
self._loaded_maps = {}
def __caps_callback(self):
for key in self._loaded_maps.get(self._active_map, []):
def set_size(self, width, height):
lv.obj.set_size(self, width, height)
self.__set_key_sizes()
def set_keys(self, mapping_number, keycodes, key_width_factors, hor_key_pad, ver_key_pad):
self._hor_key_pad = hor_key_pad
self._ver_key_pad = ver_key_pad
self._key_maps[mapping_number] = keycodes[:]
self._key_factors[mapping_number] = key_width_factors[:]
def buffer_maps(self, value):
self._buffer_maps = value
def set_active_map(self, map_number):
if map_number not in self._key_maps:
raise RuntimeError(f'keymap {map_number} does not exist')
if self._buffer_maps:
for key in self._loaded_maps.get(self._active_map, []):
key.add_flag(lv.obj.FLAG.HIDDEN)
if map_number in self._loaded_maps:
for key in self._loaded_maps[map_number]:
key.remove_flag(lv.obj.FLAG.HIDDEN)
else:
for key in self._loaded_maps.get(self._active_map, []):
key.delete()
if self._active_map in self._loaded_maps:
del self._loaded_maps[self._active_map]
self._active_map = map_number
keymap = self._key_maps[map_number]
active_map = []
self._loaded_maps[self._active_map] = active_map
for row in keymap:
for keycode in row:
key = Key(self)
key.set_keycode(keycode)
active_map.append(key)
def __set_key_sizes(self):
self.layout()
width = self.get_width()
height = self.get_height()
keys = self._loaded_maps[self._active_map]
factors = self._key_factors[self._active_map]
num_rows = len(factors)
keys_per_row = int(max([sum(row) for row in key_spacing]))
hor_key_pad = int(self._hor_key_pad * width)
ver_key_pad = int(self._ver_key_pad * height)
if hor_key_pad % 2:
hor_key_pad -= 1
if ver_key_pad % 2:
ver_key_pad -= 1
key_width = int(width / keys_per_row) - hor_key_pad
key_height = int(height / num_rows) - ver_key_pad
hor_key_pad = int(hor_key_pad / 2)
ver_key_pad = int(ver_key_pad / 2)
key_count = 0
for row in factors:
for factor in row:
key = keys[key_count]
key.set_style_pad_left(hor_key_pad, 0)
key.set_style_pad_right(hor_key_pad, 0)
key.set_style_pad_top(ver_key_pad, 0)
key.set_style_pad_bottom(ver_key_pad, 0)
key.set_size(int(key_width * factor), key_height)
key_count += 1
# this will handle all of the get_style and set_style methods for the keys
# you will need to passed an OR'ed selector value that has PART_KEY to
# target the keys
def __getattribute__(self, item):
if hasattr(Test, item):
attr = getattr(Test, item)
if 'bound_method' in str(type(attr)):
if item.startswith('set_style'):
def func_wrapper(*args):
args = list(args)
if args[-1] & PART_KEY:
args[-1] |= ~PART_KEY
for key in self._loaded_maps.get(self._active_map, []):
getattr(key, item)(*args)
else:
getattr(lv.obj, item)(self, *args)
elif item.startswith('get_style'):
def func_wrapper(*args):
args = list(args)
if args[-1] & PART_KEY:
args[-1] |= ~PART_KEY
key = self._loaded_maps.get(self._active_map, [])[0]
return getattr(key, item)(*args)
else:
return getattr(lv.obj, item)(self, *args)
else:
def func_wrapper(*args, **kwargs):
return attr(self, *args, **kwargs)
return func_wrapper
raise AttributeError(item)
def _pressed_cb(self, evt):
# register each key to call this callback passing the index of the key as user data
pass
# width factors for the buttons. These will allow the buttons to be dynamically sized
# tab 1.56 73px
# caps lock 2.13 100px
# left shift 2.70 127px
# left ctrl 2.13 100px
# left start 1.56 73px
# left alt 1.56 73px
# space 5.52 259px
# right alt 1.58 74px
# right start 1.58 74px
# right shift 2.69 126px
# enter 2.13 100px wide @ bottom 1.58 74px width @ top 86px tall
# how the button widths are calculated is by using int(sum(row)) which will return
# the largest number of "keys" in a single row. a key is defined as a 1.0 factor.
# in the case below a row is 15 keys wide. The width of the screen is then divided
#15 and rounded down to the closest whole int. that width is then multiplied by the factors
# seen below to determine what the width should be for the key.
# this allows for dynamic resizing of the keyboard. Think display orientation.
key_sizes = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1.56, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.58],
[2.13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.13],
[2.70, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.58],
[2.13, 1.56, 1.56, 5.52, 1.58, 1.58, 1, 1]
]
# There are 4 types of keys,
# keys controlled by shift, keys that are strings that have either 1 or 2 characters.
# keys controlled by caps, keys that are strings and only have a single character
# keys that "latch", keys that are lists or tuples of strings
# keys that are special, keys that are assigned to LV_SYMBOL_* constants
keys =[
['`~', '1!', '2@', '3#', '4$', '5%', '6^', '7&', '8*', '9(', '0)', '-_', '=+', '\\|', BACKSPACE_KEY],
[TAB_KEY, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[{', ']}', ENTER_KEY],
[CAPS_KEY, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';:', '\'"', ENTER_KEY],
[L_SHIFT_KEY, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',<', '.>', '/?', R_SHIFT_KEY],
[L_CTRL_KEY, L_START_KEY, L_ALT_KEY, ' ', R_ALT_KEY, R_START_KEY, MENU_KEY, R_CTRL_KEY],
]
# These factors set the spacing between the keys. they are multiplied to the width and the height
# and rounded down to the nearest even integer. so if you want a 6 pixel space between keys at
# the tops and bottoms and the keyboard takes up 1/2 of a 480pixel tall display. you have
# `6 / 240` to come up with the factor of 0.025. You may have to nudge the factor up a slight bit
# like 0.026 to get it over the number you want. You are able to control the rounding this way
# since it is always going to round down to the closest even integer.
hor_key_pad = 0.0075
ver_key_pad = 0.025
set_keys(0, keys, key_sizes, hor_key_pad, ver_key_pad)
set_active_map(0)
# you can supply multiple key maps and sizes but the pads must always remain the same
# remember to supply a new id for each new map.