trezor/ui: extract styles

This commit is contained in:
Jan Pochyla 2017-09-25 17:23:24 +02:00
parent e9b66dab7e
commit f2e53ab2eb
8 changed files with 224 additions and 235 deletions

View File

@ -12,83 +12,54 @@ from trezor import res
display = Display()
# for desktop platforms, we need to refresh the display after each frame
if sys.platform != 'trezor':
loop.after_step_hook = display.refresh
def rgbcolor(r: int, g: int, b: int) -> int:
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
LIGHT_RED = rgbcolor(0xFF, 0x00, 0x00)
# RED E4572E
RED = rgbcolor(0xE4, 0x57, 0x2E)
# ACTIVE DARK RED A64022
ACTIVE_RED = rgbcolor(0xA6, 0x40, 0x22)
PINK = rgbcolor(0xE9, 0x1E, 0x63)
PURPLE = rgbcolor(0x9C, 0x27, 0xB0)
DEEP_PURPLE = rgbcolor(0x67, 0x3A, 0xB7)
INDIGO = rgbcolor(0x3F, 0x51, 0xB5)
BLUE = rgbcolor(0x21, 0x96, 0xF3)
LIGHT_BLUE = rgbcolor(0x03, 0xA9, 0xF4)
CYAN = rgbcolor(0x00, 0xBC, 0xD4)
TEAL = rgbcolor(0x00, 0x96, 0x88)
# GREEN 4CC148
GREEN = rgbcolor(0x4C, 0xC1, 0x48)
# ACTIVE DARK GREEN 1A8C14
ACTIVE_GREEN = rgbcolor(0x1A, 0x8C, 0x14)
LIGHT_GREEN = rgbcolor(0x87, 0xCE, 0x26)
LIME = rgbcolor(0xCD, 0xDC, 0x39)
YELLOW = rgbcolor(0xFF, 0xEB, 0x3B)
AMBER = rgbcolor(0xFF, 0xC1, 0x07)
ORANGE = rgbcolor(0xFF, 0x98, 0x00)
DEEP_ORANGE = rgbcolor(0xFF, 0x57, 0x22)
BROWN = rgbcolor(0x79, 0x55, 0x48)
LIGHT_GREY = rgbcolor(0xDA, 0xDD, 0xD8)
GREY = rgbcolor(0x9E, 0x9E, 0x9E)
DARK_GREY = rgbcolor(0x3E, 0x3E, 0x3E)
BLUE_GRAY = rgbcolor(0x60, 0x7D, 0x8B)
BLACK = rgbcolor(0x00, 0x00, 0x00)
WHITE = rgbcolor(0xFA, 0xFA, 0xFA)
BLACKISH = rgbcolor(0x20, 0x20, 0x20)
MONO = Display.FONT_MONO
# font styles
NORMAL = Display.FONT_NORMAL
BOLD = Display.FONT_BOLD
# radius for buttons and other elements
BTN_RADIUS = const(2)
BACKLIGHT_NORMAL = const(60)
BACKLIGHT_DIM = const(5)
BACKLIGHT_NONE = const(2)
BACKLIGHT_MAX = const(255)
BOLD = Display.FONT_BOLD
MONO = Display.FONT_MONO
# display width and height
SCREEN = const(240)
# 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'
def contains(pos: tuple, area: tuple) -> bool:
x, y = pos
ax, ay, aw, ah = area
return ax <= x <= ax + aw and ay <= y <= ay + ah
def lerpi(a: int, b: int, t: float) -> int:
return int(a + t * (b - a))
def rgb(r: int, g: int, b: int) -> int:
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
def blend(ca: int, cb: int, t: float) -> int:
return rgbcolor(lerpi((ca >> 8) & 0xF8, (cb >> 8) & 0xF8, t),
lerpi((ca >> 3) & 0xFC, (cb >> 3) & 0xFC, t),
lerpi((ca << 3) & 0xF8, (cb << 3) & 0xF8, t))
return rgb(
lerpi((ca >> 8) & 0xF8, (cb >> 8) & 0xF8, t),
lerpi((ca >> 3) & 0xFC, (cb >> 3) & 0xFC, t),
lerpi((ca << 3) & 0xF8, (cb << 3) & 0xF8, t))
from trezor.ui.style import *
def contains(area: tuple, pos: tuple) -> bool:
x, y = pos
ax, ay, aw, ah = area
return ax <= x <= ax + aw and ay <= y <= ay + ah
def rotate(pos: tuple) -> tuple:
r = display.orientation()
if r == 0:
return pos
x, y = pos
if r == 90:
return (y, 240 - x)
if r == 180:
return (240 - x, 240 - y)
if r == 270:
return (240 - y, x)
def pulse(delay):
@ -119,19 +90,6 @@ async def backlight_slide(val, delay=20000):
await sleep
def rotate(pos: tuple) -> tuple:
r = display.orientation()
if r == 0:
return pos
x, y = pos
if r == 90:
return (y, 240 - x)
if r == 180:
return (240 - x, 240 - y)
if r == 270:
return (240 - y, x)
def header(title, icon=ICON_RESET, fg=BLACK, bg=BLACK):
display.bar(0, 0, 240, 32, bg)
if icon is not None:
@ -140,7 +98,6 @@ def header(title, icon=ICON_RESET, fg=BLACK, bg=BLACK):
class Widget:
def render(self):
pass

View File

@ -3,79 +3,11 @@ from micropython import const
from trezor import io
from trezor import ui
from trezor.ui import display
from trezor.ui import contains
from trezor.ui import display
from trezor.ui import rotate
from trezor.ui import Widget
DEFAULT_BUTTON = {
'bg-color': ui.BLACK,
'fg-color': ui.WHITE,
'text-style': ui.NORMAL,
'border-color': ui.BLACK,
'radius': ui.BTN_RADIUS,
}
DEFAULT_BUTTON_ACTIVE = {
'bg-color': ui.GREY,
'fg-color': ui.BLACK,
'text-style': ui.BOLD,
'border-color': ui.GREY,
'radius': ui.BTN_RADIUS,
}
DEFAULT_BUTTON_DISABLED = {
'bg-color': ui.BLACK,
'fg-color': ui.GREY,
'text-style': ui.NORMAL,
'border-color': ui.BLACK,
'radius': ui.BTN_RADIUS,
}
CANCEL_BUTTON = {
'bg-color': ui.RED,
'fg-color': ui.WHITE,
'text-style': ui.BOLD,
'border-color': ui.RED,
'radius': ui.BTN_RADIUS,
}
CANCEL_BUTTON_ACTIVE = {
'bg-color': ui.ACTIVE_RED,
'fg-color': ui.WHITE,
'text-style': ui.BOLD,
'border-color': ui.ACTIVE_RED,
'radius': ui.BTN_RADIUS,
}
CONFIRM_BUTTON = {
'bg-color': ui.GREEN,
'fg-color': ui.WHITE,
'text-style': ui.BOLD,
'border-color': ui.GREEN,
'radius': ui.BTN_RADIUS,
}
CONFIRM_BUTTON_ACTIVE = {
'bg-color': ui.ACTIVE_GREEN,
'fg-color': ui.WHITE,
'text-style': ui.BOLD,
'border-color': ui.ACTIVE_GREEN,
'radius': ui.BTN_RADIUS,
}
CLEAR_BUTTON = {
'bg-color': ui.BLACK,
'fg-color': ui.WHITE,
'text-style': ui.NORMAL,
'border-color': ui.BLACK,
'radius': ui.BTN_RADIUS,
}
CLEAR_BUTTON_ACTIVE = {
'bg-color': ui.BLACK,
'fg-color': ui.GREY,
'text-style': ui.NORMAL,
'border-color': ui.BLACK,
'radius': ui.BTN_RADIUS,
}
BTN_CLICKED = const(1)
BTN_STARTED = const(1)
@ -93,9 +25,9 @@ class Button(Widget):
absolute=False):
self.area = area
self.content = content
self.normal_style = normal_style or DEFAULT_BUTTON
self.active_style = active_style or DEFAULT_BUTTON_ACTIVE
self.disabled_style = disabled_style or DEFAULT_BUTTON_DISABLED
self.normal_style = normal_style or ui.BTN_DEFAULT
self.active_style = active_style or ui.BTN_DEFAULT_ACTIVE
self.disabled_style = disabled_style or ui.BTN_DEFAULT_DISABLED
self.absolute = absolute
self.state = BTN_DIRTY
@ -114,33 +46,33 @@ class Button(Widget):
return
state = self.state & ~BTN_DIRTY
if state & BTN_DISABLED:
style = self.disabled_style
s = self.disabled_style
elif state & BTN_ACTIVE:
style = self.active_style
s = self.active_style
else:
style = self.normal_style
s = self.normal_style
ax, ay, aw, ah = self.area
tx = ax + aw // 2
ty = ay + ah // 2 + 8
display.bar_radius(ax, ay, aw, ah,
style['border-color'],
s['border-color'],
ui.BLACK,
style['radius'])
s['radius'])
display.bar_radius(ax + 1, ay + 1, aw - 2, ah - 2,
style['bg-color'],
style['border-color'],
style['radius'])
s['bg-color'],
s['border-color'],
s['radius'])
if isinstance(self.content, str):
display.text_center(tx, ty, self.content,
style['text-style'],
style['fg-color'],
style['bg-color'])
s['text-style'],
s['fg-color'],
s['bg-color'])
else:
display.icon(ax, ay, self.content,
style['fg-color'],
style['bg-color'])
s['fg-color'],
s['bg-color'])
self.state = state
@ -150,10 +82,10 @@ class Button(Widget):
if not self.absolute:
pos = rotate(pos)
if event == io.TOUCH_START:
if contains(pos, self.area):
if contains(self.area, pos):
self.state = BTN_STARTED | BTN_DIRTY | BTN_ACTIVE
elif event == io.TOUCH_MOVE and self.state & BTN_STARTED:
if contains(pos, self.area):
if contains(self.area, pos):
if not self.state & BTN_ACTIVE:
self.state = BTN_STARTED | BTN_DIRTY | BTN_ACTIVE
else:
@ -161,5 +93,5 @@ class Button(Widget):
self.state = BTN_STARTED | BTN_DIRTY
elif event == io.TOUCH_END and self.state & BTN_STARTED:
self.state = BTN_DIRTY
if contains(pos, self.area):
if contains(self.area, pos):
return BTN_CLICKED

View File

@ -1,10 +1,9 @@
from micropython import const
from trezor import loop
from trezor import ui
from trezor.ui import Widget
from .button import Button, BTN_CLICKED, BTN_STARTED
from .button import CONFIRM_BUTTON, CONFIRM_BUTTON_ACTIVE
from .button import CANCEL_BUTTON, CANCEL_BUTTON_ACTIVE
from .loader import Loader
from trezor.ui.button import Button, BTN_CLICKED, BTN_STARTED
from trezor.ui.loader import Loader
CONFIRMED = const(1)
CANCELLED = const(2)
@ -16,16 +15,16 @@ class ConfirmDialog(Widget):
self.content = content
if cancel is not None:
self.confirm = Button((121, 240 - 48, 119, 48), confirm,
normal_style=CONFIRM_BUTTON,
active_style=CONFIRM_BUTTON_ACTIVE)
normal_style=ui.BTN_CONFIRM,
active_style=ui.BTN_CONFIRM_ACTIVE)
self.cancel = Button((0, 240 - 48, 119, 48), cancel,
normal_style=CANCEL_BUTTON,
active_style=CANCEL_BUTTON_ACTIVE)
normal_style=ui.BTN_CANCEL,
active_style=ui.BTN_CANCEL_ACTIVE)
else:
self.cancel = None
self.confirm = Button((0, 240 - 48, 240, 48), confirm,
normal_style=CONFIRM_BUTTON,
active_style=CONFIRM_BUTTON_ACTIVE)
normal_style=ui.BTN_CONFIRM,
active_style=ui.BTN_CONFIRM_ACTIVE)
def render(self):
self.confirm.render()
@ -35,7 +34,6 @@ class ConfirmDialog(Widget):
def touch(self, event, pos):
if self.confirm.touch(event, pos) == BTN_CLICKED:
return CONFIRMED
if self.cancel is not None:
if self.cancel.touch(event, pos) == BTN_CLICKED:
return CANCELLED
@ -53,8 +51,8 @@ class HoldToConfirmDialog(Widget):
def __init__(self, content, hold='Hold to confirm', *args, **kwargs):
self.content = content
self.button = Button((0, 240 - 48, 240, 48), hold,
normal_style=CONFIRM_BUTTON,
active_style=CONFIRM_BUTTON_ACTIVE)
normal_style=ui.BTN_CONFIRM,
active_style=ui.BTN_CONFIRM_ACTIVE)
self.loader = Loader(*args, **kwargs)
def render(self):

View File

@ -1,20 +1,7 @@
from trezor import ui, res, loop, io
from trezor.crypto import bip39
from trezor.ui import display
from trezor.ui.button import Button, BTN_CLICKED, CLEAR_BUTTON, CLEAR_BUTTON_ACTIVE
KEY_BUTTON = {
'bg-color': ui.BLACKISH,
'fg-color': ui.WHITE,
'text-style': ui.MONO,
'border-color': ui.BLACK,
}
KEY_BUTTON_ACTIVE = {
'bg-color': ui.GREY,
'fg-color': ui.BLACK,
'text-style': ui.MONO,
'border-color': ui.GREY,
}
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 - 48, spacing=0):
@ -29,8 +16,8 @@ def key_buttons():
keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
# keys = [' ', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
return [Button(cell_area(i), k,
normal_style=KEY_BUTTON,
active_style=KEY_BUTTON_ACTIVE) for i, k in enumerate(keys)]
normal_style=ui.BTN_KEY,
active_style=ui.BTN_KEY_ACTIVE) for i, k in enumerate(keys)]
def compute_mask(text):
@ -56,8 +43,8 @@ class KeyboardMultiTap(ui.Widget):
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'),
normal_style=CLEAR_BUTTON,
active_style=CLEAR_BUTTON_ACTIVE)
normal_style=ui.BTN_CLEAR,
active_style=ui.BTN_CLEAR_ACTIVE)
def render(self):
@ -172,8 +159,8 @@ class KeyboardZooming(ui.Widget):
self.key_buttons = key_buttons()
self.bs_button = Button((240 - 35, 5, 30, 30),
res.load('trezor/res/pin_close.toig'),
normal_style=CLEAR_BUTTON,
active_style=CLEAR_BUTTON_ACTIVE)
normal_style=ui.BTN_CLEAR,
active_style=ui.BTN_CLEAR_ACTIVE)
def render(self):
self.render_input()
@ -208,8 +195,8 @@ class KeyboardZooming(ui.Widget):
if btn.touch(event, pos) == BTN_CLICKED:
self.content += btn.content
self.zoom_buttons = None
for btn in self.key_buttons:
btn.taint()
for b in self.key_buttons:
b.taint()
self.bs_button.taint()
break
@ -217,8 +204,8 @@ class KeyboardZooming(ui.Widget):
for btn in self.key_buttons:
if btn.touch(event, pos) == BTN_CLICKED:
self.zoom_buttons = zoom_buttons(btn.content, self.uppercase)
for btn in self.zoom_buttons:
btn.taint()
for b in self.zoom_buttons:
b.taint()
self.bs_button.taint()
break

View File

@ -4,27 +4,13 @@ from trezor import loop
from trezor import ui
DEFAULT_LOADER = {
'bg-color': ui.BLACK,
'fg-color': ui.WHITE,
'icon': None,
'icon-fg-color': None,
}
DEFAULT_LOADER_ACTIVE = {
'bg-color': ui.BLACK,
'fg-color': ui.ACTIVE_GREEN,
'icon': None,
'icon-fg-color': None,
}
class Loader(ui.Widget):
def __init__(self, target_ms=1000, normal_style=None, active_style=None):
self.target_ms = target_ms
self.start_ticks_ms = None
self.normal_style = normal_style or DEFAULT_LOADER
self.active_style = active_style or DEFAULT_LOADER_ACTIVE
self.normal_style = normal_style or ui.LDR_DEFAULT
self.active_style = active_style or ui.LDR_DEFAULT_ACTIVE
def start(self):
self.start_ticks_ms = utime.ticks_ms()
@ -42,18 +28,18 @@ class Loader(ui.Widget):
def render(self):
progress = min(utime.ticks_ms() - self.start_ticks_ms, self.target_ms)
if progress == self.target_ms:
style = self.active_style
s = self.active_style
else:
style = self.normal_style
if style['icon'] is None:
s = self.normal_style
if s['icon'] is None:
ui.display.loader(
progress, -8, style['fg-color'], style['bg-color'])
elif style['icon-fg-color'] is None:
progress, -8, s['fg-color'], s['bg-color'])
elif s['icon-fg-color'] is None:
ui.display.loader(
progress, -8, style['fg-color'], style['bg-color'], style['icon'])
progress, -8, s['fg-color'], s['bg-color'], s['icon'])
else:
ui.display.loader(
progress, -8, style['fg-color'], style['bg-color'], style['icon'], style['icon-fg-color'])
progress, -8, s['fg-color'], s['bg-color'], s['icon'], s['icon-fg-color'])
def __iter__(self):
sleep = loop.sleep(1000000 // 60) # 60 fps

View File

@ -1,5 +1,5 @@
from micropython import const
from trezor import ui, res
from trezor import ui
from trezor.crypto import random
from trezor.ui import display
from trezor.ui.button import Button, BTN_CLICKED
@ -54,14 +54,6 @@ class PinMatrix(ui.Widget):
for btn in self.pin_buttons:
btn.render()
# vertical border bars
# display.bar(79, 48, 2, 143, ui.blend(ui.BLACK, ui.WHITE, 0.25))
# display.bar(158, 48, 2, 143, ui.blend(ui.BLACK, ui.WHITE, 0.25))
# horizontal border bars
# display.bar(0, 95, 240, 2, ui.blend(ui.BLACK, ui.WHITE, 0.25))
# display.bar(0, 142, 240, 2, ui.blend(ui.BLACK, ui.WHITE, 0.25))
def touch(self, event, pos):
for btn in self.pin_buttons:
if btn.touch(event, pos) == BTN_CLICKED:

137
src/trezor/ui/style.py Normal file
View File

@ -0,0 +1,137 @@
from micropython import const
from trezor.ui import rgb
from trezor.ui import NORMAL, BOLD, MONO
# radius for buttons and other elements
RADIUS = const(2)
# backlight brightness
BACKLIGHT_NORMAL = const(60)
BACKLIGHT_DIM = const(5)
BACKLIGHT_NONE = const(2)
BACKLIGHT_MAX = const(255)
# colors
LIGHT_RED = rgb(0xFF, 0x00, 0x00)
RED = rgb(0xE4, 0x57, 0x2E) # RED E4572E
ACTIVE_RED = rgb(0xA6, 0x40, 0x22) # ACTIVE DARK RED A64022
PINK = rgb(0xE9, 0x1E, 0x63)
PURPLE = rgb(0x9C, 0x27, 0xB0)
DEEP_PURPLE = rgb(0x67, 0x3A, 0xB7)
INDIGO = rgb(0x3F, 0x51, 0xB5)
BLUE = rgb(0x21, 0x96, 0xF3)
LIGHT_BLUE = rgb(0x03, 0xA9, 0xF4)
CYAN = rgb(0x00, 0xBC, 0xD4)
TEAL = rgb(0x00, 0x96, 0x88)
GREEN = rgb(0x4C, 0xC1, 0x48) # GREEN 4CC148
ACTIVE_GREEN = rgb(0x1A, 0x8C, 0x14) # ACTIVE DARK GREEN 1A8C14
LIGHT_GREEN = rgb(0x87, 0xCE, 0x26)
LIME = rgb(0xCD, 0xDC, 0x39)
YELLOW = rgb(0xFF, 0xEB, 0x3B)
AMBER = rgb(0xFF, 0xC1, 0x07)
ORANGE = rgb(0xFF, 0x98, 0x00)
DEEP_ORANGE = rgb(0xFF, 0x57, 0x22)
BROWN = rgb(0x79, 0x55, 0x48)
LIGHT_GREY = rgb(0xDA, 0xDD, 0xD8)
GREY = rgb(0x9E, 0x9E, 0x9E)
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)
# 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'
# buttons
BTN_DEFAULT = {
'bg-color': BLACK,
'fg-color': WHITE,
'text-style': NORMAL,
'border-color': BLACK,
'radius': RADIUS,
}
BTN_DEFAULT_ACTIVE = {
'bg-color': GREY,
'fg-color': BLACK,
'text-style': BOLD,
'border-color': GREY,
'radius': RADIUS,
}
BTN_DEFAULT_DISABLED = {
'bg-color': BLACK,
'fg-color': GREY,
'text-style': NORMAL,
'border-color': BLACK,
'radius': RADIUS,
}
BTN_CANCEL = {
'bg-color': RED,
'fg-color': WHITE,
'text-style': BOLD,
'border-color': RED,
'radius': RADIUS,
}
BTN_CANCEL_ACTIVE = {
'bg-color': ACTIVE_RED,
'fg-color': WHITE,
'text-style': BOLD,
'border-color': ACTIVE_RED,
'radius': RADIUS,
}
BTN_CONFIRM = {
'bg-color': GREEN,
'fg-color': WHITE,
'text-style': BOLD,
'border-color': GREEN,
'radius': RADIUS,
}
BTN_CONFIRM_ACTIVE = {
'bg-color': ACTIVE_GREEN,
'fg-color': WHITE,
'text-style': BOLD,
'border-color': ACTIVE_GREEN,
'radius': RADIUS,
}
BTN_CLEAR = {
'bg-color': BLACK,
'fg-color': WHITE,
'text-style': NORMAL,
'border-color': BLACK,
'radius': RADIUS,
}
BTN_CLEAR_ACTIVE = {
'bg-color': BLACK,
'fg-color': GREY,
'text-style': NORMAL,
'border-color': BLACK,
'radius': RADIUS,
}
BTN_KEY = {
'bg-color': BLACKISH,
'fg-color': WHITE,
'text-style': MONO,
'border-color': BLACK,
}
BTN_KEY_ACTIVE = {
'bg-color': GREY,
'fg-color': BLACK,
'text-style': MONO,
'border-color': GREY,
}
# loader
LDR_DEFAULT = {
'bg-color': BLACK,
'fg-color': WHITE,
'icon': None,
'icon-fg-color': None,
}
LDR_DEFAULT_ACTIVE = {
'bg-color': BLACK,
'fg-color': ACTIVE_GREEN,
'icon': None,
'icon-fg-color': None,
}

View File

@ -52,7 +52,7 @@ class Swipe(ui.Widget):
else:
ui.display.backlight(self.light_target)
elif event == io.TOUCH_START and contains(pos, self.area):
elif event == io.TOUCH_START and contains(self.area, pos):
self.start_time = temp_time
self.start_pos = pos
self.light_origin = ui.BACKLIGHT_NORMAL