mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 12:28:09 +00:00
ui/keyboard: refactor
This commit is contained in:
parent
4a83864593
commit
cd0fa4df4a
@ -1,27 +1,18 @@
|
|||||||
from trezor import ui, res, loop, io
|
from trezor import ui, res, loop, io
|
||||||
from trezor.crypto import bip39
|
from trezor.crypto import bip39
|
||||||
from trezor.ui import display
|
from trezor.ui import display
|
||||||
from trezor.ui.button import Button, BTN_CLICKED
|
from trezor.ui.button import Button, BTN_CLICKED, ICON
|
||||||
|
|
||||||
|
|
||||||
def cell_area(i, n_x=3, n_y=3, start_x=6, start_y=66, end_x=234, end_y=237, spacing=0):
|
|
||||||
w = (end_x - start_x) // n_x
|
|
||||||
h = (end_y - start_y) // n_y
|
|
||||||
x = (i % n_x) * w
|
|
||||||
y = (i // n_x) * h
|
|
||||||
return (x + start_x, y + start_y, w - spacing, h - spacing)
|
|
||||||
|
|
||||||
|
|
||||||
def key_buttons():
|
def key_buttons():
|
||||||
keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
|
keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
|
||||||
# keys = [' ', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
|
return [Button(ui.grid(i + 3, n_y=4), k,
|
||||||
return [Button(cell_area(i), k,
|
|
||||||
normal_style=ui.BTN_KEY,
|
normal_style=ui.BTN_KEY,
|
||||||
active_style=ui.BTN_KEY_ACTIVE,
|
active_style=ui.BTN_KEY_ACTIVE,
|
||||||
disabled_style=ui.BTN_KEY_DISABLED) for i, k in enumerate(keys)]
|
disabled_style=ui.BTN_KEY_DISABLED) for i, k in enumerate(keys)]
|
||||||
|
|
||||||
|
|
||||||
def compute_mask(text):
|
def compute_mask(text: str) -> int:
|
||||||
mask = 0
|
mask = 0
|
||||||
for c in text:
|
for c in text:
|
||||||
shift = ord(c) - 97 # ord('a') == 97
|
shift = ord(c) - 97 # ord('a') == 97
|
||||||
@ -31,116 +22,127 @@ def compute_mask(text):
|
|||||||
return mask
|
return mask
|
||||||
|
|
||||||
|
|
||||||
class KeyboardMultiTap(ui.Widget):
|
class Input(Button):
|
||||||
|
def __init__(self, area: tuple, content: str='', word: str=''):
|
||||||
|
super().__init__(area, content)
|
||||||
|
self.word = word
|
||||||
|
self.icon = None
|
||||||
|
self.pending = False
|
||||||
|
|
||||||
def __init__(self, content='', prompt=''):
|
def edit(self, content: str, word: str, pending: bool):
|
||||||
self.content = content
|
self.content = content
|
||||||
self.prompt = prompt
|
self.word = word
|
||||||
self.sugg_mask = 0xffffffff
|
self.pending = pending
|
||||||
self.sugg_word = None
|
self.taint()
|
||||||
self.pending_button = None
|
if content == word: # confirm button
|
||||||
self.pending_index = 0
|
self.enable()
|
||||||
|
self.normal_style = ui.BTN_CONFIRM
|
||||||
|
self.active_style = ui.BTN_CONFIRM_ACTIVE
|
||||||
|
self.icon = ui.ICON_CONFIRM
|
||||||
|
elif word: # auto-complete button
|
||||||
|
self.enable()
|
||||||
|
self.normal_style = ui.BTN_KEY
|
||||||
|
self.active_style = ui.BTN_KEY_ACTIVE
|
||||||
|
self.icon = ui.ICON_CLICK
|
||||||
|
else: # disabled button
|
||||||
|
self.disable()
|
||||||
|
self.icon = None
|
||||||
|
|
||||||
self.key_buttons = key_buttons()
|
def render_content(self, s, ax, ay, aw, ah):
|
||||||
self.sugg_button = Button((63, 0, 240 - 65, 57), '')
|
text_style = s['text-style']
|
||||||
self.bs_button = Button((6, 5, 57, 60),
|
fg_color = s['fg-color']
|
||||||
res.load('trezor/res/left.toig'),
|
bg_color = s['bg-color']
|
||||||
normal_style=ui.BTN_CLEAR,
|
|
||||||
active_style=ui.BTN_CLEAR_ACTIVE)
|
p = self.pending # should we draw the pending marker?
|
||||||
|
t = self.content # input content
|
||||||
|
w = self.word[len(t):] # suggested word
|
||||||
|
i = self.icon # rendered icon
|
||||||
|
|
||||||
|
tx = ax + 24 # x-offset of the content
|
||||||
|
ty = ay + ah//2 + 8 # y-offset of the content
|
||||||
|
|
||||||
|
# input content and the suggested word
|
||||||
|
display.text(tx, ty, t, text_style, fg_color, bg_color)
|
||||||
|
width = display.text_width(t, text_style)
|
||||||
|
display.text(tx + width, ty, w, text_style, ui.GREY, bg_color)
|
||||||
|
|
||||||
|
if p: # pending marker
|
||||||
|
pw = display.text_width(t[-1:], text_style)
|
||||||
|
px = tx + width - pw
|
||||||
|
display.bar(px, ty, pw + 2, 3, fg_color)
|
||||||
|
|
||||||
|
if i: # icon
|
||||||
|
ix = ax + aw - ICON*2
|
||||||
|
iy = ty - ICON
|
||||||
|
display.icon(ix, iy, res.load(i), fg_color, bg_color)
|
||||||
|
|
||||||
|
|
||||||
|
class MnemonicKeyboard(ui.Widget):
|
||||||
|
def __init__(self, prompt: str=''):
|
||||||
|
self.prompt = prompt
|
||||||
|
self.input = Input(ui.grid(1, n_x=4, n_y=4, cells_x=3), '', '')
|
||||||
|
self.back = Button(ui.grid(0, n_x=4, n_y=4),
|
||||||
|
res.load(ui.ICON_BACK),
|
||||||
|
normal_style=ui.BTN_CLEAR,
|
||||||
|
active_style=ui.BTN_CLEAR_ACTIVE)
|
||||||
|
self.keys = key_buttons()
|
||||||
|
self.pbutton = None # pending key button
|
||||||
|
self.pindex = 0 # index of current pending char in pbutton
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
if self.content:
|
if self.input.content:
|
||||||
display.bar(62, 8, 168, 54, ui.BG)
|
# content button and backspace
|
||||||
content_width = display.text_width(self.content, ui.BOLD)
|
self.input.render()
|
||||||
offset_x = 78
|
self.back.render()
|
||||||
if self.content == self.sugg_word:
|
|
||||||
# confirm button + content
|
|
||||||
display.bar_radius(67, 8, 164, 54, ui.GREEN, ui.BG, ui.RADIUS)
|
|
||||||
type_icon = res.load(ui.ICON_CONFIRM2)
|
|
||||||
display.icon(228 - 30, 28, type_icon, ui.WHITE, ui.GREEN)
|
|
||||||
display.text(offset_x, 40, self.content, ui.BOLD, ui.WHITE, ui.GREEN)
|
|
||||||
|
|
||||||
elif self.sugg_word is not None:
|
|
||||||
# auto-suggest button + content + suggestion
|
|
||||||
display.bar_radius(67, 8, 164, 54, ui.BLACKISH, ui.BG, ui.RADIUS)
|
|
||||||
display.text(offset_x, 40, self.content, ui.BOLD, ui.FG, ui.BLACKISH)
|
|
||||||
sugg_text = self.sugg_word[len(self.content):]
|
|
||||||
sugg_x = offset_x + content_width
|
|
||||||
type_icon = res.load(ui.ICON_CLICK)
|
|
||||||
display.icon(228 - 30, 24, type_icon, ui.GREY, ui.BLACKISH)
|
|
||||||
display.text(sugg_x, 40, sugg_text, ui.BOLD, ui.GREY, ui.BLACKISH)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# content
|
|
||||||
display.bar(63, 8, 168, 54, ui.BG)
|
|
||||||
display.text(offset_x, 40, self.content, ui.BOLD, ui.FG, ui.BG)
|
|
||||||
|
|
||||||
# backspace button
|
|
||||||
self.bs_button.render()
|
|
||||||
|
|
||||||
# pending marker
|
|
||||||
if self.pending_button is not None:
|
|
||||||
pending_width = display.text_width(self.content[-1:], ui.BOLD)
|
|
||||||
pending_x = offset_x + content_width - pending_width
|
|
||||||
display.bar(pending_x, 42, pending_width + 2, 3, ui.FG)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# prompt
|
# prompt
|
||||||
display.bar(0, 8, 240, 60, ui.BG)
|
display.bar(0, 8, 240, 60, ui.BG)
|
||||||
display.text(20, 40, self.prompt, ui.BOLD, ui.GREY, ui.BG)
|
display.text(20, 40, self.prompt, ui.BOLD, ui.GREY, ui.BG)
|
||||||
|
|
||||||
# key buttons
|
# key buttons
|
||||||
for btn in self.key_buttons:
|
for btn in self.keys:
|
||||||
btn.render()
|
btn.render()
|
||||||
|
|
||||||
def touch(self, event, pos):
|
def touch(self, event, pos):
|
||||||
if self.bs_button.touch(event, pos) == BTN_CLICKED:
|
content = self.input.content
|
||||||
self.content = self.content[:-1]
|
word = self.input.word
|
||||||
self.pending_button = None
|
|
||||||
self.pending_index = 0
|
if self.back.touch(event, pos) == BTN_CLICKED:
|
||||||
self._update_suggestion()
|
# backspace, delete the last character of input
|
||||||
self._update_buttons()
|
self.edit(content[:-1])
|
||||||
return
|
return
|
||||||
if self.sugg_button.touch(event, pos) == BTN_CLICKED:
|
|
||||||
if not self.content or self.sugg_word is None:
|
if self.input.touch(event, pos) == BTN_CLICKED:
|
||||||
return None
|
# input press, either auto-complete or confirm
|
||||||
if self.content == self.sugg_word:
|
if content == word:
|
||||||
result = self.content
|
self.edit('')
|
||||||
self.content = ''
|
return content
|
||||||
else:
|
else:
|
||||||
result = None
|
self.edit(word)
|
||||||
self.content = self.sugg_word
|
|
||||||
self.pending_button = None
|
|
||||||
self.pending_index = 0
|
|
||||||
self._update_suggestion()
|
|
||||||
self._update_buttons()
|
|
||||||
return result
|
|
||||||
for btn in self.key_buttons:
|
|
||||||
if btn.touch(event, pos) == BTN_CLICKED:
|
|
||||||
if self.pending_button is btn:
|
|
||||||
self.pending_index = (
|
|
||||||
self.pending_index + 1) % len(btn.content)
|
|
||||||
self.content = self.content[:-1]
|
|
||||||
self.content += btn.content[self.pending_index]
|
|
||||||
self._update_suggestion()
|
|
||||||
else:
|
|
||||||
self.content += btn.content[0]
|
|
||||||
self._update_suggestion()
|
|
||||||
self.pending_button = btn
|
|
||||||
self.pending_index = 0
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _update_suggestion(self):
|
for btn in self.keys:
|
||||||
if self.content:
|
if btn.touch(event, pos) == BTN_CLICKED:
|
||||||
self.sugg_word = bip39.find_word(self.content)
|
# key press, add new char to input or cycle the pending button
|
||||||
self.sugg_mask = bip39.complete_word(self.content)
|
if self.pbutton is btn:
|
||||||
else:
|
index = (self.pindex + 1) % len(btn.content)
|
||||||
self.sugg_word = None
|
content = content[:-1] + btn.content[index]
|
||||||
self.sugg_mask = 0xffffffff
|
else:
|
||||||
|
index = 0
|
||||||
|
content += btn.content[0]
|
||||||
|
self.edit(content, btn, index)
|
||||||
|
return
|
||||||
|
|
||||||
def _update_buttons(self):
|
def edit(self, content, button=None, index=0):
|
||||||
for btn in self.key_buttons:
|
word = bip39.find_word(content) or ''
|
||||||
if btn is self.pending_button or compute_mask(btn.content) & self.sugg_mask:
|
mask = bip39.complete_word(content)
|
||||||
|
|
||||||
|
self.pbutton = button
|
||||||
|
self.pindex = index
|
||||||
|
self.input.edit(content, word, button is not None)
|
||||||
|
|
||||||
|
# enable or disable key buttons
|
||||||
|
for btn in self.keys:
|
||||||
|
if btn is button or compute_mask(btn.content) & mask:
|
||||||
btn.enable()
|
btn.enable()
|
||||||
else:
|
else:
|
||||||
btn.disable()
|
btn.disable()
|
||||||
@ -152,11 +154,12 @@ class KeyboardMultiTap(ui.Widget):
|
|||||||
wait_touch = loop.wait(touch)
|
wait_touch = loop.wait(touch)
|
||||||
content = None
|
content = None
|
||||||
|
|
||||||
self.bs_button.taint()
|
self.back.taint()
|
||||||
|
self.input.taint()
|
||||||
|
|
||||||
while content is None:
|
while content is None:
|
||||||
self.render()
|
self.render()
|
||||||
if self.pending_button is not None:
|
if self.pbutton is not None:
|
||||||
wait = wait_timeout
|
wait = wait_timeout
|
||||||
else:
|
else:
|
||||||
wait = wait_touch
|
wait = wait_touch
|
||||||
@ -165,99 +168,10 @@ class KeyboardMultiTap(ui.Widget):
|
|||||||
event, *pos = result
|
event, *pos = result
|
||||||
content = self.touch(event, pos)
|
content = self.touch(event, pos)
|
||||||
else:
|
else:
|
||||||
self.pending_button = None
|
if self.input.word:
|
||||||
self.pending_index = 0
|
# just reset the pending state
|
||||||
if self.sugg_word is None:
|
self.edit(self.input.content)
|
||||||
self.content = self.content[:-1]
|
else:
|
||||||
self._update_suggestion()
|
# invalid character, backspace it
|
||||||
self._update_buttons()
|
self.edit(self.input.content[:-1])
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
# def zoom_buttons(keys, upper=False):
|
|
||||||
# n_x = len(keys)
|
|
||||||
# if upper:
|
|
||||||
# keys = keys + keys.upper()
|
|
||||||
# n_y = 2
|
|
||||||
# else:
|
|
||||||
# n_y = 1
|
|
||||||
# return [Button(cell_area(i, n_x, n_y), key) for i, key in enumerate(keys)]
|
|
||||||
|
|
||||||
|
|
||||||
# class KeyboardZooming(ui.Widget):
|
|
||||||
|
|
||||||
# def __init__(self, content='', uppercase=True):
|
|
||||||
# self.content = content
|
|
||||||
# self.uppercase = uppercase
|
|
||||||
|
|
||||||
# self.zoom_buttons = None
|
|
||||||
# self.key_buttons = key_buttons()
|
|
||||||
# self.bs_button = Button((240 - 35, 5, 30, 30),
|
|
||||||
# res.load('trezor/res/pin_close.toig'),
|
|
||||||
# normal_style=ui.BTN_CLEAR,
|
|
||||||
# active_style=ui.BTN_CLEAR_ACTIVE)
|
|
||||||
|
|
||||||
# def render(self):
|
|
||||||
# self.render_input()
|
|
||||||
# if self.zoom_buttons:
|
|
||||||
# for btn in self.zoom_buttons:
|
|
||||||
# btn.render()
|
|
||||||
# else:
|
|
||||||
# for btn in self.key_buttons:
|
|
||||||
# btn.render()
|
|
||||||
|
|
||||||
# def render_input(self):
|
|
||||||
# if self.content:
|
|
||||||
# display.bar(0, 0, 200, 40, ui.BG)
|
|
||||||
# else:
|
|
||||||
# display.bar(0, 0, 240, 40, ui.BG)
|
|
||||||
# display.text(20, 30, self.content, ui.BOLD, ui.GREY, ui.BG)
|
|
||||||
# if self.content:
|
|
||||||
# self.bs_button.render()
|
|
||||||
|
|
||||||
# def touch(self, event, pos):
|
|
||||||
# if self.bs_button.touch(event, pos) == BTN_CLICKED:
|
|
||||||
# self.content = self.content[:-1]
|
|
||||||
# self.bs_button.taint()
|
|
||||||
# return
|
|
||||||
# if self.zoom_buttons:
|
|
||||||
# return self.touch_zoom(event, pos)
|
|
||||||
# else:
|
|
||||||
# return self.touch_keyboard(event, pos)
|
|
||||||
|
|
||||||
# def touch_zoom(self, event, pos):
|
|
||||||
# for btn in self.zoom_buttons:
|
|
||||||
# if btn.touch(event, pos) == BTN_CLICKED:
|
|
||||||
# self.content += btn.content
|
|
||||||
# self.zoom_buttons = None
|
|
||||||
# for b in self.key_buttons:
|
|
||||||
# b.taint()
|
|
||||||
# self.bs_button.taint()
|
|
||||||
# break
|
|
||||||
|
|
||||||
# def touch_keyboard(self, event, pos):
|
|
||||||
# for btn in self.key_buttons:
|
|
||||||
# if btn.touch(event, pos) == BTN_CLICKED:
|
|
||||||
# self.zoom_buttons = zoom_buttons(btn.content, self.uppercase)
|
|
||||||
# for b in self.zoom_buttons:
|
|
||||||
# b.taint()
|
|
||||||
# self.bs_button.taint()
|
|
||||||
# break
|
|
||||||
|
|
||||||
# def __iter__(self):
|
|
||||||
# timeout = loop.sleep(1000 * 1000 * 1)
|
|
||||||
# touch = loop.select(io.TOUCH)
|
|
||||||
# wait = loop.wait(touch, timeout)
|
|
||||||
# while True:
|
|
||||||
# self.render()
|
|
||||||
# result = yield wait
|
|
||||||
# if touch in wait.finished:
|
|
||||||
# event, *pos = result
|
|
||||||
# self.touch(event, pos)
|
|
||||||
# elif self.zoom_buttons:
|
|
||||||
# self.zoom_buttons = None
|
|
||||||
# for btn in self.key_buttons:
|
|
||||||
# btn.taint()
|
|
||||||
|
|
||||||
|
|
||||||
Keyboard = KeyboardMultiTap
|
|
||||||
|
Loading…
Reference in New Issue
Block a user