diff --git a/core/src/trezor/res/swipe_left.toif b/core/src/trezor/res/swipe_left.toif new file mode 100644 index 000000000..65f05dc86 Binary files /dev/null and b/core/src/trezor/res/swipe_left.toif differ diff --git a/core/src/trezor/res/swipe_right.toif b/core/src/trezor/res/swipe_right.toif new file mode 100644 index 000000000..8bc8b1626 Binary files /dev/null and b/core/src/trezor/res/swipe_right.toif differ diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py index cffd0859b..f1a747fa3 100644 --- a/core/src/trezor/ui/__init__.py +++ b/core/src/trezor/ui/__init__.py @@ -6,7 +6,7 @@ from trezorui import Display from trezor import io, loop, res, utils if False: - from typing import Any, Generator, Iterable, Tuple, TypeVar + from typing import Any, Generator, Tuple, TypeVar Pos = Tuple[int, int] Area = Tuple[int, int, int, int] @@ -276,7 +276,7 @@ class Layout(Component): def __await__(self) -> Generator[Any, Any, ResultValue]: return self.__iter__() # type: ignore - def create_tasks(self) -> Iterable[loop.Task]: + def create_tasks(self) -> Tuple[loop.Task, ...]: """ Called from `__iter__`. Creates and returns a sequence of tasks that run this layout. Tasks are executed in parallel. When one of them diff --git a/core/src/trezor/ui/confirm.py b/core/src/trezor/ui/confirm.py index 4b4814cad..f7e96bf36 100644 --- a/core/src/trezor/ui/confirm.py +++ b/core/src/trezor/ui/confirm.py @@ -1,9 +1,11 @@ -from trezor import res, ui +from micropython import const + +from trezor import loop, res, ui from trezor.ui.button import Button, ButtonCancel, ButtonConfirm from trezor.ui.loader import Loader, LoaderDefault if False: - from typing import Optional + from typing import Any, Optional, Tuple from trezor.ui.button import ButtonContent, ButtonStyleType from trezor.ui.loader import LoaderStyleType @@ -55,6 +57,7 @@ class Confirm(ui.Layout): self.cancel = None def dispatch(self, event: int, x: int, y: int) -> None: + super().dispatch(event, x, y) self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) @@ -68,6 +71,82 @@ class Confirm(ui.Layout): raise ui.Result(CANCELLED) +class Pageable: + def __init__(self) -> None: + self._page = 0 + + def page(self) -> int: + return self._page + + def page_count(self) -> int: + raise NotImplementedError + + def is_first(self) -> bool: + return self._page == 0 + + def is_last(self) -> bool: + return self._page == self.page_count() - 1 + + def next(self) -> None: + self._page = min(self._page + 1, self.page_count() - 1) + + def prev(self) -> None: + self._page = max(self._page - 1, 0) + + +class ConfirmPageable(Confirm): + def __init__(self, pageable: Pageable, *args: Any, **kwargs: Any): + super().__init__(*args, **kwargs) + self.pageable = pageable + + async def handle_paging(self) -> None: + from trezor.ui.swipe import SWIPE_HORIZONTAL, SWIPE_LEFT, SWIPE_RIGHT, Swipe + + if self.pageable.is_first(): + directions = SWIPE_LEFT + elif self.pageable.is_last(): + directions = SWIPE_RIGHT + else: + directions = SWIPE_HORIZONTAL + + swipe = await Swipe(directions) + + if swipe == SWIPE_LEFT: + self.pageable.next() + else: + self.pageable.prev() + + self.content.repaint = True + if self.confirm is not None: + self.confirm.repaint = True + if self.cancel is not None: + self.cancel.repaint = True + + def create_tasks(self) -> Tuple[loop.Task, ...]: + tasks = super().create_tasks() + if self.pageable.page_count() > 1: + return tasks + (self.handle_paging(),) + else: + return tasks + + def on_render(self) -> None: + PULSE_PERIOD = const(1200000) + + super().on_render() + + if not self.pageable.is_first(): + t = ui.pulse(PULSE_PERIOD) + c = ui.blend(ui.GREY, ui.DARK_GREY, t) + icon = res.load(ui.ICON_SWIPE_RIGHT) + ui.display.icon(18, 68, icon, c, ui.BG) + + if not self.pageable.is_last(): + t = ui.pulse(PULSE_PERIOD, PULSE_PERIOD // 2) + c = ui.blend(ui.GREY, ui.DARK_GREY, t) + icon = res.load(ui.ICON_SWIPE_LEFT) + ui.display.icon(205, 68, icon, c, ui.BG) + + class HoldToConfirm(ui.Layout): DEFAULT_CONFIRM = "Hold To Confirm" DEFAULT_CONFIRM_STYLE = ButtonConfirm diff --git a/core/src/trezor/ui/passphrase.py b/core/src/trezor/ui/passphrase.py index cc344e737..7dc66e637 100644 --- a/core/src/trezor/ui/passphrase.py +++ b/core/src/trezor/ui/passphrase.py @@ -7,7 +7,7 @@ from trezor.ui.button import Button, ButtonClear, ButtonConfirm from trezor.ui.swipe import SWIPE_HORIZONTAL, SWIPE_LEFT, Swipe if False: - from typing import List, Iterable, Optional + from typing import Iterable, List, Optional, Tuple from trezor.ui.button import ButtonContent, ButtonStyleStateType SPACE = res.load(ui.ICON_SPACE) @@ -244,7 +244,7 @@ class PassphraseKeyboard(ui.Layout): def on_confirm(self) -> None: raise ui.Result(self.input.text) - def create_tasks(self) -> Iterable[loop.Task]: + def create_tasks(self) -> Tuple[loop.Task, ...]: return self.handle_input(), self.handle_rendering(), self.handle_paging() diff --git a/core/src/trezor/ui/popup.py b/core/src/trezor/ui/popup.py index b9ce61e31..19f5ea19a 100644 --- a/core/src/trezor/ui/popup.py +++ b/core/src/trezor/ui/popup.py @@ -1,7 +1,7 @@ from trezor import loop, ui if False: - from typing import Iterable + from typing import Tuple class Popup(ui.Layout): @@ -12,7 +12,7 @@ class Popup(ui.Layout): def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) - def create_tasks(self) -> Iterable[loop.Task]: + def create_tasks(self) -> Tuple[loop.Task, ...]: return self.handle_input(), self.handle_rendering(), self.handle_timeout() def handle_timeout(self) -> loop.Task: # type: ignore diff --git a/core/src/trezor/ui/scroll.py b/core/src/trezor/ui/scroll.py index dd15f6287..37bf8e68f 100644 --- a/core/src/trezor/ui/scroll.py +++ b/core/src/trezor/ui/scroll.py @@ -9,7 +9,7 @@ if __debug__: from apps.debug import swipe_signal if False: - from typing import Iterable, Sequence + from typing import Tuple, Sequence def render_scrollbar(pages: int, page: int) -> None: @@ -91,7 +91,7 @@ class Paginated(ui.Layout): self.on_change() - def create_tasks(self) -> Iterable[loop.Task]: + def create_tasks(self) -> Tuple[loop.Task, ...]: return self.handle_input(), self.handle_rendering(), self.handle_paging() def on_change(self) -> None: diff --git a/core/src/trezor/ui/style.py b/core/src/trezor/ui/style.py index 420819f79..7955834c4 100644 --- a/core/src/trezor/ui/style.py +++ b/core/src/trezor/ui/style.py @@ -64,5 +64,7 @@ ICON_LOCK = "trezor/res/lock.toif" ICON_CLICK = "trezor/res/click.toif" ICON_BACK = "trezor/res/left.toif" ICON_SWIPE = "trezor/res/swipe.toif" +ICON_SWIPE_LEFT = "trezor/res/swipe_left.toif" +ICON_SWIPE_RIGHT = "trezor/res/swipe_right.toif" ICON_CHECK = "trezor/res/check.toif" ICON_SPACE = "trezor/res/space.toif"