parent
2f4c123466
commit
36534325f0
@ -1,115 +1,213 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import io, ui
|
||||
from trezor.ui import Widget, contains, display, rotate
|
||||
from trezor import ui
|
||||
from trezor.ui import display, in_area
|
||||
|
||||
|
||||
class ButtonDefault:
|
||||
class normal:
|
||||
bg_color = ui.BLACKISH
|
||||
fg_color = ui.FG
|
||||
text_style = ui.BOLD
|
||||
border_color = ui.BG
|
||||
radius = ui.RADIUS
|
||||
|
||||
class active:
|
||||
bg_color = ui.FG
|
||||
fg_color = ui.BLACKISH
|
||||
text_style = ui.BOLD
|
||||
border_color = ui.FG
|
||||
radius = ui.RADIUS
|
||||
|
||||
class disabled:
|
||||
bg_color = ui.BG
|
||||
fg_color = ui.GREY
|
||||
text_style = ui.NORMAL
|
||||
border_color = ui.BG
|
||||
radius = ui.RADIUS
|
||||
|
||||
|
||||
class ButtonMono(ButtonDefault):
|
||||
class normal(ButtonDefault.normal):
|
||||
text_style = ui.MONO
|
||||
|
||||
class active(ButtonDefault.active):
|
||||
text_style = ui.MONO
|
||||
|
||||
class disabled(ButtonDefault.disabled):
|
||||
text_style = ui.MONO
|
||||
|
||||
|
||||
class ButtonMonoDark:
|
||||
class normal:
|
||||
bg_color = ui.DARK_BLACK
|
||||
fg_color = ui.DARK_WHITE
|
||||
text_style = ui.MONO
|
||||
border_color = ui.BG
|
||||
radius = ui.RADIUS
|
||||
|
||||
class active:
|
||||
bg_color = ui.FG
|
||||
fg_color = ui.DARK_BLACK
|
||||
text_style = ui.MONO
|
||||
border_color = ui.FG
|
||||
radius = ui.RADIUS
|
||||
|
||||
class disabled:
|
||||
bg_color = ui.DARK_BLACK
|
||||
fg_color = ui.GREY
|
||||
text_style = ui.MONO
|
||||
border_color = ui.BG
|
||||
radius = ui.RADIUS
|
||||
|
||||
|
||||
class ButtonConfirm(ButtonDefault):
|
||||
class normal(ButtonDefault.normal):
|
||||
bg_color = ui.GREEN
|
||||
|
||||
class active(ButtonDefault.active):
|
||||
fg_color = ui.GREEN
|
||||
|
||||
|
||||
class ButtonCancel(ButtonDefault):
|
||||
class normal(ButtonDefault.normal):
|
||||
bg_color = ui.RED
|
||||
|
||||
class active(ButtonDefault.active):
|
||||
fg_color = ui.RED
|
||||
|
||||
|
||||
class ButtonClear(ButtonDefault):
|
||||
class normal(ButtonDefault.normal):
|
||||
bg_color = ui.ORANGE
|
||||
|
||||
class active(ButtonDefault.active):
|
||||
fg_color = ui.ORANGE
|
||||
|
||||
|
||||
class ButtonMonoConfirm(ButtonDefault):
|
||||
class normal(ButtonDefault.normal):
|
||||
text_style = ui.MONO
|
||||
bg_color = ui.GREEN
|
||||
|
||||
class active(ButtonDefault.active):
|
||||
text_style = ui.MONO
|
||||
fg_color = ui.GREEN
|
||||
|
||||
class disabled(ButtonDefault.disabled):
|
||||
text_style = ui.MONO
|
||||
|
||||
# button events
|
||||
BTN_CLICKED = const(1)
|
||||
|
||||
# button states
|
||||
BTN_INITIAL = const(0)
|
||||
BTN_DISABLED = const(1)
|
||||
BTN_FOCUSED = const(2)
|
||||
BTN_ACTIVE = const(3)
|
||||
_INITIAL = const(0)
|
||||
_PRESSED = const(1)
|
||||
_RELEASED = const(2)
|
||||
_DISABLED = const(3)
|
||||
|
||||
# constants
|
||||
ICON = const(16) # icon size in pixels
|
||||
BORDER = const(4) # border size in pixels
|
||||
# button constants
|
||||
_ICON = const(16) # icon size in pixels
|
||||
_BORDER = const(4) # border size in pixels
|
||||
|
||||
|
||||
class Button(Widget):
|
||||
def __init__(self, area: tuple, content: str, style: dict = ui.BTN_KEY):
|
||||
class Button(ui.Control):
|
||||
def __init__(self, area, content, style=ButtonDefault):
|
||||
self.area = area
|
||||
self.content = content
|
||||
self.normal_style = style["normal"] or ui.BTN_KEY["normal"]
|
||||
self.active_style = style["active"] or ui.BTN_KEY["active"]
|
||||
self.disabled_style = style["disabled"] or ui.BTN_KEY["disabled"]
|
||||
self.state = BTN_INITIAL
|
||||
self.normal_style = style.normal
|
||||
self.active_style = style.active
|
||||
self.disabled_style = style.disabled
|
||||
self.state = _INITIAL
|
||||
self.repaint = True
|
||||
|
||||
def enable(self):
|
||||
if self.state == BTN_DISABLED:
|
||||
self.state = BTN_INITIAL
|
||||
self.tainted = True
|
||||
if self.state is not _INITIAL:
|
||||
self.state = _INITIAL
|
||||
self.repaint = True
|
||||
|
||||
def disable(self):
|
||||
if self.state != BTN_DISABLED:
|
||||
self.state = BTN_DISABLED
|
||||
self.tainted = True
|
||||
|
||||
def is_enabled(self):
|
||||
return self.state != BTN_DISABLED
|
||||
if self.state is not _DISABLED:
|
||||
self.state = _DISABLED
|
||||
self.repaint = True
|
||||
|
||||
def render(self):
|
||||
if not self.tainted:
|
||||
return
|
||||
state = self.state
|
||||
if state == BTN_DISABLED:
|
||||
def on_render(self):
|
||||
if self.repaint:
|
||||
if self.state is _DISABLED:
|
||||
s = self.disabled_style
|
||||
elif state == BTN_ACTIVE:
|
||||
elif self.state is _PRESSED:
|
||||
s = self.active_style
|
||||
else:
|
||||
s = self.normal_style
|
||||
ax, ay, aw, ah = self.area
|
||||
self.render_background(s, ax, ay, aw, ah)
|
||||
self.render_content(s, ax, ay, aw, ah)
|
||||
self.tainted = False
|
||||
self.repaint = False
|
||||
|
||||
def render_background(self, s, ax, ay, aw, ah):
|
||||
radius = s["radius"]
|
||||
bg_color = s["bg-color"]
|
||||
border_color = s["border-color"]
|
||||
if border_color != bg_color:
|
||||
radius = s.radius
|
||||
bg_color = s.bg_color
|
||||
border_color = s.border_color
|
||||
if border_color == bg_color:
|
||||
# we don't need to render the border
|
||||
display.bar_radius(ax, ay, aw, ah, bg_color, ui.BG, radius)
|
||||
else:
|
||||
# render border and background on top of it
|
||||
display.bar_radius(ax, ay, aw, ah, border_color, ui.BG, radius)
|
||||
display.bar_radius(
|
||||
ax + BORDER,
|
||||
ay + BORDER,
|
||||
aw - BORDER * 2,
|
||||
ah - BORDER * 2,
|
||||
ax + _BORDER,
|
||||
ay + _BORDER,
|
||||
aw - _BORDER * 2,
|
||||
ah - _BORDER * 2,
|
||||
bg_color,
|
||||
border_color,
|
||||
radius,
|
||||
)
|
||||
else:
|
||||
# render only the background
|
||||
display.bar_radius(ax, ay, aw, ah, bg_color, ui.BG, radius)
|
||||
|
||||
def render_content(self, s, ax, ay, aw, ah):
|
||||
c = self.content
|
||||
tx = ax + aw // 2
|
||||
ty = ay + ah // 2 + 8
|
||||
if isinstance(c, str):
|
||||
display.text_center(
|
||||
tx, ty, c, s["text-style"], s["fg-color"], s["bg-color"]
|
||||
)
|
||||
else:
|
||||
display.icon(tx - ICON // 2, ty - ICON, c, s["fg-color"], s["bg-color"])
|
||||
|
||||
def touch(self, event, pos):
|
||||
pos = rotate(pos)
|
||||
|
||||
state = self.state
|
||||
if state == BTN_DISABLED:
|
||||
t = self.content
|
||||
if isinstance(t, str):
|
||||
display.text_center(tx, ty, t, s.text_style, s.fg_color, s.bg_color)
|
||||
elif isinstance(t, bytes):
|
||||
display.icon(tx - _ICON // 2, ty - _ICON, t, s.fg_color, s.bg_color)
|
||||
|
||||
def on_touch_start(self, x, y):
|
||||
if self.state is _DISABLED:
|
||||
return
|
||||
if in_area(self.area, x, y):
|
||||
self.state = _PRESSED
|
||||
self.repaint = True
|
||||
self.on_press_start()
|
||||
|
||||
if event == io.TOUCH_START:
|
||||
if contains(self.area, pos):
|
||||
self.state = BTN_ACTIVE
|
||||
self.tainted = True
|
||||
|
||||
elif event == io.TOUCH_MOVE:
|
||||
if contains(self.area, pos):
|
||||
if state == BTN_FOCUSED:
|
||||
self.state = BTN_ACTIVE
|
||||
self.tainted = True
|
||||
def on_touch_move(self, x, y):
|
||||
if self.state is _DISABLED:
|
||||
return
|
||||
if in_area(self.area, x, y):
|
||||
if self.state is _RELEASED:
|
||||
self.state = _PRESSED
|
||||
self.repaint = True
|
||||
self.on_press_start()
|
||||
else:
|
||||
if state == BTN_ACTIVE:
|
||||
self.state = BTN_FOCUSED
|
||||
self.tainted = True
|
||||
|
||||
elif event == io.TOUCH_END:
|
||||
if state != BTN_INITIAL:
|
||||
self.state = BTN_INITIAL
|
||||
self.tainted = True
|
||||
if state == BTN_ACTIVE and contains(self.area, pos):
|
||||
return BTN_CLICKED
|
||||
if self.state is _PRESSED:
|
||||
self.state = _RELEASED
|
||||
self.repaint = True
|
||||
self.on_press_end()
|
||||
|
||||
def on_touch_end(self, x, y):
|
||||
state = self.state
|
||||
if state is not _INITIAL and state is not _DISABLED:
|
||||
self.state = _INITIAL
|
||||
self.repaint = True
|
||||
if in_area(self.area, x, y):
|
||||
if state is _PRESSED:
|
||||
self.on_press_end()
|
||||
self.on_click()
|
||||
|
||||
def on_press_start(self):
|
||||
pass
|
||||
|
||||
def on_press_end(self):
|
||||
pass
|
||||
|
||||
def on_click(self):
|
||||
pass
|
||||
|
@ -1,115 +1,106 @@
|
||||
from micropython import const
|
||||
from trezor import res, ui
|
||||
from trezor.ui.button import Button, ButtonCancel, ButtonConfirm
|
||||
from trezor.ui.loader import Loader, LoaderDefault
|
||||
|
||||
from trezor import loop, res, ui
|
||||
from trezor.ui import Widget
|
||||
from trezor.ui.button import BTN_ACTIVE, BTN_CLICKED, Button
|
||||
from trezor.ui.loader import Loader
|
||||
CONFIRMED = object()
|
||||
CANCELLED = object()
|
||||
|
||||
if __debug__:
|
||||
from apps.debug import confirm_signal
|
||||
|
||||
CONFIRMED = const(1)
|
||||
CANCELLED = const(2)
|
||||
class Confirm(ui.Layout):
|
||||
DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM)
|
||||
DEFAULT_CONFIRM_STYLE = ButtonConfirm
|
||||
DEFAULT_CANCEL = res.load(ui.ICON_CANCEL)
|
||||
DEFAULT_CANCEL_STYLE = ButtonCancel
|
||||
|
||||
|
||||
class ConfirmDialog(Widget):
|
||||
def __init__(
|
||||
self,
|
||||
content,
|
||||
confirm=DEFAULT_CONFIRM,
|
||||
confirm_style=DEFAULT_CONFIRM_STYLE,
|
||||
cancel=DEFAULT_CANCEL,
|
||||
confirm_style=ui.BTN_CONFIRM,
|
||||
cancel_style=ui.BTN_CANCEL,
|
||||
cancel_style=DEFAULT_CANCEL_STYLE,
|
||||
):
|
||||
self.content = content
|
||||
|
||||
if confirm is not None:
|
||||
if cancel is None:
|
||||
area = ui.grid(4, n_x=1)
|
||||
else:
|
||||
area = ui.grid(9, n_x=2)
|
||||
self.confirm = Button(area, confirm, confirm_style)
|
||||
self.confirm.on_click = self.on_confirm
|
||||
else:
|
||||
self.confirm = None
|
||||
|
||||
if cancel is not None:
|
||||
self.confirm = Button(ui.grid(9, n_x=2), confirm, style=confirm_style)
|
||||
self.cancel = Button(ui.grid(8, n_x=2), cancel, style=cancel_style)
|
||||
if confirm is None:
|
||||
area = ui.grid(4, n_x=1)
|
||||
else:
|
||||
area = ui.grid(8, n_x=2)
|
||||
self.cancel = Button(area, cancel, cancel_style)
|
||||
self.cancel.on_click = self.on_cancel
|
||||
else:
|
||||
self.confirm = Button(ui.grid(4, n_x=1), confirm, style=confirm_style)
|
||||
self.cancel = None
|
||||
|
||||
def render(self):
|
||||
self.confirm.render()
|
||||
def dispatch(self, event, x, y):
|
||||
self.content.dispatch(event, x, y)
|
||||
if self.confirm is not None:
|
||||
self.confirm.dispatch(event, x, y)
|
||||
if self.cancel is not None:
|
||||
self.cancel.render()
|
||||
self.cancel.dispatch(event, x, y)
|
||||
|
||||
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
|
||||
|
||||
async def __iter__(self):
|
||||
if __debug__:
|
||||
return await loop.spawn(super().__iter__(), self.content, confirm_signal)
|
||||
else:
|
||||
return await loop.spawn(super().__iter__(), self.content)
|
||||
def on_confirm(self):
|
||||
raise ui.Result(CONFIRMED)
|
||||
|
||||
def on_cancel(self):
|
||||
raise ui.Result(CANCELLED)
|
||||
|
||||
_STARTED = const(-1)
|
||||
_STOPPED = const(-2)
|
||||
|
||||
class HoldToConfirm(ui.Layout):
|
||||
DEFAULT_CONFIRM = "Hold To Confirm"
|
||||
DEFAULT_CONFIRM_STYLE = ButtonConfirm
|
||||
DEFAULT_LOADER_STYLE = LoaderDefault
|
||||
|
||||
class HoldToConfirmDialog(Widget):
|
||||
def __init__(
|
||||
self,
|
||||
content,
|
||||
hold="Hold to confirm",
|
||||
button_style=ui.BTN_CONFIRM,
|
||||
loader_style=ui.LDR_DEFAULT,
|
||||
confirm=DEFAULT_CONFIRM,
|
||||
confirm_style=DEFAULT_CONFIRM_STYLE,
|
||||
loader_style=DEFAULT_LOADER_STYLE,
|
||||
):
|
||||
self.content = content
|
||||
self.button = Button(ui.grid(4, n_x=1), hold, style=button_style)
|
||||
self.loader = Loader(style=loader_style)
|
||||
|
||||
if content.__class__.__iter__ is not Widget.__iter__:
|
||||
raise TypeError(
|
||||
"HoldToConfirmDialog does not support widgets with custom event loop"
|
||||
)
|
||||
|
||||
def taint(self):
|
||||
super().taint()
|
||||
self.button.taint()
|
||||
self.content.taint()
|
||||
|
||||
def render(self):
|
||||
self.button.render()
|
||||
if not self.loader.is_active():
|
||||
self.content.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
button = self.button
|
||||
was_active = button.state == BTN_ACTIVE
|
||||
button.touch(event, pos)
|
||||
is_active = button.state == BTN_ACTIVE
|
||||
if is_active and not was_active:
|
||||
ui.display.clear()
|
||||
|
||||
self.loader = Loader(loader_style)
|
||||
self.loader.on_start = self._on_loader_start
|
||||
|
||||
self.button = Button(ui.grid(4, n_x=1), confirm, confirm_style)
|
||||
self.button.on_press_start = self._on_press_start
|
||||
self.button.on_press_end = self._on_press_end
|
||||
self.button.on_click = self._on_click
|
||||
|
||||
def _on_press_start(self):
|
||||
self.loader.start()
|
||||
return _STARTED
|
||||
if was_active and not is_active:
|
||||
if self.loader.stop():
|
||||
return CONFIRMED
|
||||
else:
|
||||
return _STOPPED
|
||||
|
||||
async def __iter__(self):
|
||||
result = None
|
||||
while result is None or result < 0: # _STARTED or _STOPPED
|
||||
if self.loader.is_active():
|
||||
if __debug__:
|
||||
result = await loop.spawn(
|
||||
self.loader, super().__iter__(), confirm_signal
|
||||
)
|
||||
else:
|
||||
result = await loop.spawn(self.loader, super().__iter__())
|
||||
else:
|
||||
self.content.taint()
|
||||
if __debug__:
|
||||
result = await loop.spawn(super().__iter__(), confirm_signal)
|
||||
|
||||
def _on_press_end(self):
|
||||
self.loader.stop()
|
||||
|
||||
def _on_loader_start(self):
|
||||
# Loader has either started growing, or returned to the 0-position.
|
||||
# In the first case we need to clear the content leftovers, in the latter
|
||||
# we need to render the content again.
|
||||
ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT - 60, ui.BG)
|
||||
self.content.dispatch(ui.REPAINT, 0, 0)
|
||||
|
||||
def _on_click(self):
|
||||
if self.loader.elapsed_ms() >= self.loader.target_ms:
|
||||
self.on_confirm()
|
||||
|
||||
def dispatch(self, event, x, y):
|
||||
if self.loader.start_ms is not None:
|
||||
self.loader.dispatch(event, x, y)
|
||||
else:
|
||||
result = await super().__iter__()
|
||||
return result
|
||||
self.content.dispatch(event, x, y)
|
||||
self.button.dispatch(event, x, y)
|
||||
|
||||
def on_confirm(self):
|
||||
raise ui.Result(CONFIRMED)
|
||||
|
@ -1,21 +1,10 @@
|
||||
from trezor.ui import Widget
|
||||
from trezor import ui
|
||||
|
||||
|
||||
class Container(Widget):
|
||||
class Container(ui.Control):
|
||||
def __init__(self, *children):
|
||||
self.children = children
|
||||
|
||||
def taint(self):
|
||||
super().taint()
|
||||
def dispatch(self, event, x, y):
|
||||
for child in self.children:
|
||||
child.taint()
|
||||
|
||||
def render(self):
|
||||
for child in self.children:
|
||||
child.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
for child in self.children:
|
||||
result = child.touch(event, pos)
|
||||
if result is not None:
|
||||
return result
|
||||
child.dispatch(event, x, y)
|
||||
|
@ -1,33 +0,0 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import loop, ui
|
||||
from trezor.ui import Widget
|
||||
from trezor.ui.button import BTN_CLICKED, Button
|
||||
|
||||
DEVICE = const(0)
|
||||
HOST = const(1)
|
||||
|
||||
|
||||
class EntrySelector(Widget):
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), "Device")
|
||||
self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), "Host")
|
||||
|
||||
def taint(self):
|
||||
super().taint()
|
||||
self.device.taint()
|
||||
self.host.taint()
|
||||
|
||||
def render(self):
|
||||
self.device.render()
|
||||
self.host.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
if self.device.touch(event, pos) == BTN_CLICKED:
|
||||
return DEVICE
|
||||
if self.host.touch(event, pos) == BTN_CLICKED:
|
||||
return HOST
|
||||
|
||||
async def __iter__(self):
|
||||
return await loop.spawn(super().__iter__(), self.content)
|
@ -0,0 +1,17 @@
|
||||
from trezor import loop, ui
|
||||
|
||||
|
||||
class Popup(ui.Layout):
|
||||
def __init__(self, content, time_ms=0):
|
||||
self.content = content
|
||||
self.time_ms = time_ms
|
||||
|
||||
def dispatch(self, event, x, y):
|
||||
self.content.dispatch(event, x, y)
|
||||
|
||||
def create_tasks(self):
|
||||
return self.handle_input(), self.handle_rendering(), self.handle_timeout()
|
||||
|
||||
def handle_timeout(self):
|
||||
yield loop.sleep(self.time_ms * 1000)
|
||||
raise ui.Result(None)
|
@ -1,11 +1,12 @@
|
||||
from trezor import ui
|
||||
|
||||
|
||||
class Qr(ui.Widget):
|
||||
def __init__(self, data, pos, scale):
|
||||
class Qr(ui.Control):
|
||||
def __init__(self, data, x, y, scale):
|
||||
self.data = data
|
||||
self.pos = pos
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.scale = scale
|
||||
|
||||
def render(self):
|
||||
ui.display.qrcode(self.pos[0], self.pos[1], self.data, self.scale)
|
||||
def on_render(self):
|
||||
ui.display.qrcode(self.x, self.y, self.data, self.scale)
|
||||
|
@ -1,92 +1,180 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import loop, res, ui
|
||||
from trezor.ui.button import Button, ButtonCancel, ButtonConfirm, ButtonDefault
|
||||
from trezor.ui.confirm import CANCELLED, CONFIRMED
|
||||
from trezor.ui.swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
|
||||
|
||||
if __debug__:
|
||||
from apps.debug import swipe_signal
|
||||
|
||||
|
||||
async def change_page(page, page_count):
|
||||
while True:
|
||||
if page == 0:
|
||||
d = SWIPE_UP
|
||||
elif page == page_count - 1:
|
||||
d = SWIPE_DOWN
|
||||
else:
|
||||
d = SWIPE_VERTICAL
|
||||
swipe = Swipe(directions=d)
|
||||
if __debug__:
|
||||
s = await loop.spawn(swipe, swipe_signal)
|
||||
else:
|
||||
s = await swipe
|
||||
if s == SWIPE_UP:
|
||||
return min(page + 1, page_count - 1) # scroll down
|
||||
elif s == SWIPE_DOWN:
|
||||
return max(page - 1, 0) # scroll up
|
||||
|
||||
|
||||
async def paginate(render_page, page_count, page=0, *args):
|
||||
while True:
|
||||
changer = change_page(page, page_count)
|
||||
renderer = render_page(page, page_count, *args)
|
||||
waiter = loop.spawn(changer, renderer)
|
||||
result = await waiter
|
||||
if changer in waiter.finished:
|
||||
page = result
|
||||
def render_scrollbar(pages: int, page: int):
|
||||
BBOX = const(220)
|
||||
SIZE = const(8)
|
||||
|
||||
padding = 14
|
||||
if pages * padding > BBOX:
|
||||
padding = BBOX // pages
|
||||
|
||||
X = const(220)
|
||||
Y = (BBOX // 2) - (pages // 2) * padding
|
||||
|
||||
for i in range(0, pages):
|
||||
if i == page:
|
||||
fg = ui.FG
|
||||
else:
|
||||
return result
|
||||
fg = ui.GREY
|
||||
ui.display.bar_radius(X, Y + i * padding, SIZE, SIZE, fg, ui.BG, 4)
|
||||
|
||||
|
||||
def render_swipe_icon():
|
||||
DRAW_DELAY = const(200000)
|
||||
|
||||
icon = res.load(ui.ICON_SWIPE)
|
||||
t = ui.pulse(DRAW_DELAY)
|
||||
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||
ui.display.icon(70, 205, icon, c, ui.BG)
|
||||
|
||||
async def animate_swipe():
|
||||
time_delay = const(40000)
|
||||
draw_delay = const(200000)
|
||||
|
||||
def render_swipe_text():
|
||||
ui.display.text_center(130, 220, "Swipe", ui.BOLD, ui.GREY, ui.BG)
|
||||
|
||||
sleep = loop.sleep(time_delay)
|
||||
icon = res.load(ui.ICON_SWIPE)
|
||||
for t in ui.pulse(draw_delay):
|
||||
fg = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||
ui.display.icon(70, 205, icon, fg, ui.BG)
|
||||
yield sleep
|
||||
|
||||
class Paginated(ui.Layout):
|
||||
def __init__(self, pages, page=0, one_by_one=False):
|
||||
self.pages = pages
|
||||
self.page = page
|
||||
self.one_by_one = one_by_one
|
||||
self.repaint = True
|
||||
|
||||
def dispatch(self, event, x, y):
|
||||
pages = self.pages
|
||||
page = self.page
|
||||
pages[page].dispatch(event, x, y)
|
||||
|
||||
if event is ui.RENDER:
|
||||
length = len(pages)
|
||||
if page < length - 1:
|
||||
render_swipe_icon()
|
||||
if self.repaint:
|
||||
render_swipe_text()
|
||||
if self.repaint:
|
||||
render_scrollbar(length, page)
|
||||
self.repaint = False
|
||||
|
||||
async def handle_paging(self):
|
||||
if self.page == 0:
|
||||
directions = SWIPE_UP
|
||||
elif self.page == len(self.pages) - 1:
|
||||
directions = SWIPE_DOWN
|
||||
else:
|
||||
directions = SWIPE_VERTICAL
|
||||
|
||||
if __debug__:
|
||||
swipe = await loop.spawn(Swipe(directions), swipe_signal)
|
||||
else:
|
||||
swipe = await Swipe(directions)
|
||||
|
||||
def render_scrollbar(page, page_count):
|
||||
bbox = const(220)
|
||||
size = const(8)
|
||||
if swipe is SWIPE_UP:
|
||||
self.page = min(self.page + 1, len(self.pages) - 1)
|
||||
elif swipe is SWIPE_DOWN:
|
||||
self.page = max(self.page - 1, 0)
|
||||
|
||||
padding = 14
|
||||
if page_count * padding > bbox:
|
||||
padding = bbox // page_count
|
||||
self.pages[self.page].dispatch(ui.REPAINT, 0, 0)
|
||||
self.repaint = True
|
||||
|
||||
x = const(220)
|
||||
y = (bbox // 2) - (page_count // 2) * padding
|
||||
self.on_change()
|
||||
|
||||
for i in range(0, page_count):
|
||||
if i != page:
|
||||
ui.display.bar_radius(x, y + i * padding, size, size, ui.GREY, ui.BG, 4)
|
||||
ui.display.bar_radius(x, y + page * padding, size, size, ui.FG, ui.BG, 4)
|
||||
def create_tasks(self):
|
||||
return self.handle_input(), self.handle_rendering(), self.handle_paging()
|
||||
|
||||
def on_change(self):
|
||||
if self.one_by_one:
|
||||
raise ui.Result(self.page)
|
||||
|
||||
class Scrollpage(ui.Widget):
|
||||
def __init__(self, content, page, page_count):
|
||||
self.content = content
|
||||
self.page = page
|
||||
self.page_count = page_count
|
||||
|
||||
if content.__class__.__iter__ is not ui.Widget.__iter__:
|
||||
raise TypeError(
|
||||
"Scrollpage does not support widgets with custom event loop"
|
||||
)
|
||||
class PageWithButtons(ui.Control):
|
||||
def __init__(self, content, paginated, index, count):
|
||||
self.content = content
|
||||
self.paginated = paginated
|
||||
self.index = index
|
||||
self.count = count
|
||||
|
||||
if self.index == 0:
|
||||
# first page, we can cancel or go down
|
||||
left = res.load(ui.ICON_CANCEL)
|
||||
left_style = ButtonCancel
|
||||
right = res.load(ui.ICON_CLICK)
|
||||
right_style = ButtonDefault
|
||||
elif self.index == count - 1:
|
||||
# last page, we can go up or confirm
|
||||
left = res.load(ui.ICON_BACK)
|
||||
left_style = ButtonDefault
|
||||
right = res.load(ui.ICON_CONFIRM)
|
||||
right_style = ButtonConfirm
|
||||
else:
|
||||
# somewhere in the middle, we can go up or down
|
||||
left = res.load(ui.ICON_BACK)
|
||||
left_style = ButtonDefault
|
||||
right = res.load(ui.ICON_CLICK)
|
||||
right_style = ButtonDefault
|
||||
|
||||
self.left = Button(ui.grid(8, n_x=2), left, left_style)
|
||||
self.left.on_click = self.on_left
|
||||
|
||||
self.right = Button(ui.grid(9, n_x=2), right, right_style)
|
||||
self.right.on_click = self.on_right
|
||||
|
||||
def dispatch(self, event, x, y):
|
||||
self.content.dispatch(event, x, y)
|
||||
self.left.dispatch(event, x, y)
|
||||
self.right.dispatch(event, x, y)
|
||||
|
||||
def on_left(self):
|
||||
if self.index == 0:
|
||||
self.paginated.on_cancel()
|
||||
else:
|
||||
self.paginated.on_down()
|
||||
|
||||
def taint(self):
|
||||
super().taint()
|
||||
self.content.taint()
|
||||
def on_right(self):
|
||||
if self.index == self.count - 1:
|
||||
self.paginated.on_confirm()
|
||||
else:
|
||||
self.paginated.on_up()
|
||||
|
||||
def render(self):
|
||||
self.content.render()
|
||||
render_scrollbar(self.page, self.page_count)
|
||||
|
||||
def touch(self, event, pos):
|
||||
return self.content.touch(event, pos)
|
||||
class PaginatedWithButtons(ui.Layout):
|
||||
def __init__(self, pages, page=0, one_by_one=False):
|
||||
self.pages = [
|
||||
PageWithButtons(p, self, i, len(pages)) for i, p in enumerate(pages)
|
||||
]
|
||||
self.page = page
|
||||
self.one_by_one = one_by_one
|
||||
|
||||
def dispatch(self, event, x, y):
|
||||
pages = self.pages
|
||||
page = self.page
|
||||
pages[page].dispatch(event, x, y)
|
||||
if event is ui.RENDER:
|
||||
render_scrollbar(len(pages), page)
|
||||
|
||||
def on_up(self):
|
||||
self.page = max(self.page - 1, 0)
|
||||
self.pages[self.page].dispatch(ui.REPAINT, 0, 0)
|
||||
self.on_change()
|
||||
|
||||
def on_down(self):
|
||||
self.page = min(self.page + 1, len(self.pages) - 1)
|
||||
self.pages[self.page].dispatch(ui.REPAINT, 0, 0)
|
||||
self.on_change()
|
||||
|
||||
def on_confirm(self):
|
||||
raise ui.Result(CONFIRMED)
|
||||
|
||||
def on_cancel(self):
|
||||
raise ui.Result(CANCELLED)
|
||||
|
||||
def on_change(self):
|
||||
if self.one_by_one:
|
||||
raise ui.Result(self.page)
|
||||
|
@ -1,56 +1,28 @@
|
||||
from micropython import const
|
||||
from trezor import ui
|
||||
from trezor.ui.button import Button
|
||||
|
||||
from trezor import loop, ui
|
||||
from trezor.ui import Widget
|
||||
from trezor.ui.button import BTN_CLICKED, Button
|
||||
|
||||
if __debug__:
|
||||
from apps.debug import input_signal
|
||||
|
||||
|
||||
_W12 = const(12)
|
||||
_W18 = const(18)
|
||||
_W24 = const(24)
|
||||
|
||||
|
||||
class WordSelector(Widget):
|
||||
class WordSelector(ui.Layout):
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
self.w12 = Button(
|
||||
ui.grid(6, n_y=4, n_x=3, cells_y=2), str(_W12), style=ui.BTN_KEY
|
||||
)
|
||||
self.w18 = Button(
|
||||
ui.grid(7, n_y=4, n_x=3, cells_y=2), str(_W18), style=ui.BTN_KEY
|
||||
)
|
||||
self.w24 = Button(
|
||||
ui.grid(8, n_y=4, n_x=3, cells_y=2), str(_W24), style=ui.BTN_KEY
|
||||
)
|
||||
|
||||
def taint(self):
|
||||
super().taint()
|
||||
self.w12.taint()
|
||||
self.w18.taint()
|
||||
self.w24.taint()
|
||||
|
||||
def render(self):
|
||||
self.w12.render()
|
||||
self.w18.render()
|
||||
self.w24.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
if self.w12.touch(event, pos) == BTN_CLICKED:
|
||||
return _W12
|
||||
if self.w18.touch(event, pos) == BTN_CLICKED:
|
||||
return _W18
|
||||
if self.w24.touch(event, pos) == BTN_CLICKED:
|
||||
return _W24
|
||||
|
||||
async def __iter__(self):
|
||||
if __debug__:
|
||||
result = await loop.spawn(super().__iter__(), self.content, input_signal)
|
||||
if isinstance(result, str):
|
||||
return int(result)
|
||||
else:
|
||||
return result
|
||||
else:
|
||||
return await loop.spawn(super().__iter__(), self.content)
|
||||
self.w12 = Button(ui.grid(6, n_y=4, n_x=3, cells_y=2), "12")
|
||||
self.w12.on_click = self.on_w12
|
||||
self.w18 = Button(ui.grid(7, n_y=4, n_x=3, cells_y=2), "18")
|
||||
self.w18.on_click = self.on_w18
|
||||
self.w24 = Button(ui.grid(8, n_y=4, n_x=3, cells_y=2), "24")
|
||||
self.w24.on_click = self.on_w24
|
||||
|
||||
def dispatch(self, event, x, y):
|
||||
self.content.dispatch(event, x, y)
|
||||
self.w12.dispatch(event, x, y)
|
||||
self.w18.dispatch(event, x, y)
|
||||
self.w24.dispatch(event, x, y)
|
||||
|
||||
def on_w12(self):
|
||||
raise ui.Result(12)
|
||||
|
||||
def on_w18(self):
|
||||
raise ui.Result(18)
|
||||
|
||||
def on_w24(self):
|
||||
raise ui.Result(24)
|
||||
|
Loading…
Reference in new issue