ui: request_words, keyboard ui

This commit is contained in:
Peter Jensen 2017-11-03 17:14:01 +01:00 committed by Jan Pochyla
parent 95db112d10
commit ed9e63142d
23 changed files with 242 additions and 120 deletions

BIN
assets/5390-200.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/recovery-old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/send-old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,30 @@
from trezor import wire, ui, loop
from trezor.utils import unimport
# used to confirm/cancel the dialogs from outside of this module (i.e.
# through debug link)
if __debug__:
signal = loop.signal()
@ui.layout
@unimport
async def request_words(ctx, content, code=None, *args, **kwargs):
from trezor.ui.word_select import WordSelector
from trezor.messages.ButtonRequest import ButtonRequest
from trezor.messages.ButtonRequestType import Other
from trezor.messages.wire_types import ButtonAck
ui.display.clear()
dialog = WordSelector(content, *args, **kwargs)
dialog.render()
if code is None:
code = Other
await ctx.call(ButtonRequest(code=code), ButtonAck)
if __debug__:
waiter = loop.wait(signal, dialog)
else:
waiter = dialog
return await waiter

View File

@ -11,15 +11,20 @@ async def layout_recovery_device(ctx, msg):
from trezor.ui.text import Text
from apps.common import storage
from apps.common.confirm import require_confirm
from apps.common.request_words import request_words
if storage.is_initialized():
raise wire.FailureError(UnexpectedMessage, 'Already initialized')
words = []
wc = await request_words(ctx, Text(
'Device recovery', ui.ICON_RECOVERY, 'Number of words?'))
msg.word_count = int(wc)
ui.display.clear()
kbd = KeyboardMultiTap()
for i in range(0, msg.word_count):
kbd.prompt = '%s. ' % (i + 1)
kbd.prompt = 'Type %s. word' % (i + 1)
word = await kbd
words.append(word)

BIN
src/trezor/res/click.toig Normal file

Binary file not shown.

Binary file not shown.

BIN
src/trezor/res/cross2.toig Normal file

Binary file not shown.

BIN
src/trezor/res/left.toig Normal file

Binary file not shown.

Binary file not shown.

BIN
src/trezor/res/send2.toig Normal file

Binary file not shown.

View File

@ -106,11 +106,10 @@ def layout(f):
return inner
def header(title: str, icon: bytes=ICON_RESET, fg: int=BG, bg: int=BG):
display.bar(0, 0, 240, 32, bg)
def header(title: str, icon: bytes=ICON_RESET, fg: int=BG, bg: int=BG, ifg: int=BG):
if icon is not None:
display.icon(8, 4, res.load(icon), fg, bg)
display.text(8 + 24 + 2, 24, title, BOLD, fg, bg)
display.icon(14, 14, res.load(icon), ifg, bg)
display.text(44, 35, title, BOLD, fg, bg)
class Widget:

View File

@ -32,11 +32,13 @@ class Button(Widget):
self.state = BTN_DIRTY
def enable(self):
self.state &= ~BTN_DISABLED
self.state |= BTN_DIRTY
if self.state & BTN_DISABLED:
self.state &= ~BTN_DISABLED
self.state |= BTN_DIRTY
def disable(self):
self.state |= BTN_DISABLED | BTN_DIRTY
if not self.state & BTN_DISABLED:
self.state |= BTN_DISABLED | BTN_DIRTY
def taint(self):
self.state |= BTN_DIRTY
@ -54,6 +56,7 @@ class Button(Widget):
ax, ay, aw, ah = self.area
tx = ax + aw // 2
ty = ay + ah // 2 + 8
display.bar_radius(ax, ay, aw, ah,
s['border-color'],
ui.BG,
@ -70,7 +73,7 @@ class Button(Widget):
s['bg-color'])
else:
display.icon(tx - 15, ty - 20, self.content,
display.icon(tx - 8, ty - 16, self.content,
s['fg-color'],
s['bg-color'])

View File

@ -4,7 +4,7 @@ from trezor.ui import display
from trezor.ui.button import Button, BTN_CLICKED
def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240, spacing=0):
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
@ -17,7 +17,8 @@ def key_buttons():
# keys = [' ', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
return [Button(cell_area(i), k,
normal_style=ui.BTN_KEY,
active_style=ui.BTN_KEY_ACTIVE) for i, k in enumerate(keys)]
active_style=ui.BTN_KEY_ACTIVE,
disabled_style=ui.BTN_KEY_DISABLED) for i, k in enumerate(keys)]
def compute_mask(text):
@ -41,38 +42,52 @@ class KeyboardMultiTap(ui.Widget):
self.pending_index = 0
self.key_buttons = key_buttons()
self.sugg_button = Button((5, 5, 240 - 35, 30), '')
self.bs_button = Button((240 - 35, 5, 30, 30),
res.load('trezor/res/pin_close.toig'),
self.sugg_button = Button((63, 0, 240 - 65, 57), '')
self.bs_button = Button((6, 5, 57, 60),
res.load('trezor/res/left.toig'),
normal_style=ui.BTN_CLEAR,
active_style=ui.BTN_CLEAR_ACTIVE)
def render(self):
# clear canvas under input line
display.bar(0, 0, 205, 40, ui.BG)
# input line
content_width = display.text_width(self.prompt + self.content, ui.BOLD)
display.text(20, 30, self.prompt + self.content, ui.BOLD, ui.FG, ui.BG)
# pending marker
if self.pending_button is not None:
pending_width = display.text_width(self.content[-1:], ui.BOLD)
pending_x = 20 + content_width - pending_width
display.bar(pending_x, 33, pending_width + 2, 3, ui.FG)
# auto-suggest
if self.sugg_word is not None:
sugg_rest = self.sugg_word[len(self.content):]
sugg_x = 20 + content_width
display.text(sugg_x, 30, sugg_rest, ui.BOLD, ui.GREY, ui.BG)
# render backspace button
if self.content:
display.bar(62, 8, 168, 54, ui.BG)
content_width = display.text_width(self.content, ui.BOLD)
offset_x = 78
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:
display.bar(240 - 48, 0, 48, 42, ui.BG)
# prompt
display.bar(0, 8, 240, 60, ui.BG)
display.text(20, 40, self.prompt, ui.BOLD, ui.GREY, ui.BG)
# key buttons
for btn in self.key_buttons:
@ -87,10 +102,12 @@ class KeyboardMultiTap(ui.Widget):
self._update_buttons()
return
if self.sugg_button.touch(event, pos) == BTN_CLICKED:
if self.content == bip39.find_word(self.content):
if not self.content or self.sugg_word is None:
return None
if self.content == self.sugg_word:
result = self.content
self.content = ''
elif self.sugg_word is not None:
else:
result = None
self.content = self.sugg_word
self.pending_button = None
@ -128,19 +145,22 @@ class KeyboardMultiTap(ui.Widget):
else:
btn.disable()
def __iter__(self):
async def __iter__(self):
timeout = loop.sleep(1000 * 1000 * 1)
touch = loop.select(io.TOUCH)
wait_timeout = loop.wait(touch, timeout)
wait_touch = loop.wait(touch)
content = None
self.bs_button.taint()
while content is None:
self.render()
if self.pending_button is not None:
wait = wait_timeout
else:
wait = wait_touch
result = yield wait
result = await wait
if touch in wait.finished:
event, *pos = result
content = self.touch(event, pos)
@ -154,90 +174,90 @@ class KeyboardMultiTap(ui.Widget):
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)]
# 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):
# class KeyboardZooming(ui.Widget):
def __init__(self, content='', uppercase=True):
self.content = content
self.uppercase = uppercase
# 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)
# 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(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 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(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_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 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()
# 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

View File

@ -38,7 +38,10 @@ DARK_GREY = rgb(0x3E, 0x3E, 0x3E)
BLUE_GRAY = rgb(0x60, 0x7D, 0x8B)
BLACK = rgb(0x00, 0x00, 0x00)
WHITE = rgb(0xFA, 0xFA, 0xFA)
BLACKISH = rgb(0x20, 0x20, 0x20)
BLACKISH = rgb(0x30, 0x30, 0x30)
TITLE_GREY = rgb(0x9B, 0x9B, 0x9B)
ORANGE_ICON = rgb(0xF5, 0xA6, 0x23)
# common color styles
BG = BLACK
@ -47,11 +50,13 @@ FG = WHITE
# icons
ICON_RESET = 'trezor/res/header_icons/reset.toig'
ICON_WIPE = 'trezor/res/header_icons/wipe.toig'
ICON_RECOVERY = 'trezor/res/header_icons/recovery.toig'
ICON_RECOVERY = 'trezor/res/header_icons/reset.toig'
ICON_CLEAR = 'trezor/res/clear.toig'
ICON_CONFIRM = 'trezor/res/confirm.toig'
ICON_CONFIRM2 = 'trezor/res/confirm2.toig'
ICON_LOCK = 'trezor/res/lock.toig'
ICON_SEND = 'trezor/res/send.toig'
ICON_CLICK = 'trezor/res/click.toig'
# buttons
BTN_DEFAULT = {
@ -104,7 +109,7 @@ BTN_CONFIRM_ACTIVE = {
'radius': RADIUS,
}
BTN_CLEAR = {
'bg-color': BG,
'bg-color': ORANGE,
'fg-color': FG,
'text-style': NORMAL,
'border-color': BG,
@ -131,6 +136,13 @@ BTN_KEY_ACTIVE = {
'border-color': FG,
'radius': RADIUS,
}
BTN_KEY_DISABLED = {
'bg-color': BG,
'fg-color': GREY,
'text-style': MONO,
'border-color': BG,
'radius': RADIUS,
}
# loader
LDR_DEFAULT = {

View File

@ -1,9 +1,9 @@
from micropython import const
from trezor import ui
TEXT_HEADER_HEIGHT = const(32)
TEXT_HEADER_HEIGHT = const(51)
TEXT_LINE_HEIGHT = const(23)
TEXT_MARGIN_LEFT = const(10)
TEXT_MARGIN_LEFT = const(14)
class Text(ui.Widget):
@ -19,7 +19,7 @@ class Text(ui.Widget):
style = ui.NORMAL
fg = ui.FG
bg = ui.BG
ui.header(self.header_text, self.header_icon, ui.GREEN, ui.BG)
ui.header(self.header_text, self.header_icon, ui.TITLE_GREY, ui.BG, ui.ORANGE_ICON)
for item in self.content:
if isinstance(item, str):

View File

@ -0,0 +1,53 @@
from micropython import const
from trezor import loop
from trezor import ui, res
from trezor.ui import Widget
from trezor.ui.button import Button, BTN_CLICKED, BTN_STARTED, BTN_ACTIVE
W12 = '12'
W15 = '15'
W18 = '18'
W24 = '24'
class WordSelector(Widget):
def __init__(self, content):
self.content = content
self.w12 = Button((6, 135, 114, 51), W12,
normal_style=ui.BTN_KEY,
active_style=ui.BTN_KEY_ACTIVE)
self.w15 = Button((120, 135, 114, 51), W15,
normal_style=ui.BTN_KEY,
active_style=ui.BTN_KEY_ACTIVE)
self.w18 = Button((6, 186, 114, 51), W18,
normal_style=ui.BTN_KEY,
active_style=ui.BTN_KEY_ACTIVE)
self.w24 = Button((120, 186, 114, 51), W24,
normal_style=ui.BTN_KEY,
active_style=ui.BTN_KEY_ACTIVE)
def render(self):
self.w12.render()
self.w15.render()
self.w18.render()
self.w24.render()
def touch(self, event, pos):
if self.w12.touch(event, pos) == BTN_CLICKED:
return W12
if self.w15.touch(event, pos) == BTN_CLICKED:
return W15
if self.w18.touch(event, pos) == BTN_CLICKED:
return W18
if self.w24.touch(event, pos) == BTN_CLICKED:
return W24
async def __iter__(self):
return await loop.wait(super().__iter__(), self.content)
_STARTED = const(-1)
_STOPPED = const(-2)