You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
8.4 KiB
258 lines
8.4 KiB
from micropython import const
|
|
from typing import TYPE_CHECKING
|
|
|
|
from trezor import loop, res, ui, utils
|
|
from trezor.ui.loader import Loader, LoaderDefault
|
|
|
|
from ..common.confirm import CANCELLED, CONFIRMED, INFO, ConfirmBase, Pageable
|
|
from .button import Button, ButtonAbort, ButtonCancel, ButtonConfirm, ButtonDefault
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any
|
|
from .button import ButtonContent, ButtonStyleType
|
|
from trezor.ui.loader import LoaderStyleType
|
|
|
|
|
|
class Confirm(ConfirmBase):
|
|
DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM)
|
|
DEFAULT_CONFIRM_STYLE = ButtonConfirm
|
|
DEFAULT_CANCEL = res.load(ui.ICON_CANCEL)
|
|
DEFAULT_CANCEL_STYLE = ButtonCancel
|
|
|
|
def __init__(
|
|
self,
|
|
content: ui.Component,
|
|
confirm: ButtonContent | None = DEFAULT_CONFIRM,
|
|
confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE,
|
|
cancel: ButtonContent | None = DEFAULT_CANCEL,
|
|
cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE,
|
|
major_confirm: bool = False,
|
|
) -> None:
|
|
self.content = content
|
|
button_confirm: Button | None = None
|
|
button_cancel: Button | None = None
|
|
|
|
if confirm is not None:
|
|
if cancel is None:
|
|
area = ui.grid(4, n_x=1)
|
|
elif major_confirm:
|
|
area = ui.grid(13, cells_x=2)
|
|
else:
|
|
area = ui.grid(9, n_x=2)
|
|
button_confirm = Button(area, confirm, confirm_style)
|
|
button_confirm.on_click = self.on_confirm
|
|
|
|
if cancel is not None:
|
|
if confirm is None:
|
|
area = ui.grid(4, n_x=1)
|
|
elif major_confirm:
|
|
area = ui.grid(12, cells_x=1)
|
|
else:
|
|
area = ui.grid(8, n_x=2)
|
|
button_cancel = Button(area, cancel, cancel_style)
|
|
button_cancel.on_click = self.on_cancel
|
|
|
|
super().__init__(content, button_confirm, button_cancel)
|
|
|
|
|
|
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 .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
|
|
|
|
if __debug__:
|
|
from apps.debug import swipe_signal
|
|
|
|
swipe = await loop.race(Swipe(directions), swipe_signal())
|
|
else:
|
|
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.AwaitableTask, ...]:
|
|
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(1_200_000)
|
|
|
|
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)
|
|
if utils.DISABLE_ANIMATION:
|
|
ui.display.icon(18, 68, icon, ui.GREY, ui.BG)
|
|
else:
|
|
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)
|
|
if utils.DISABLE_ANIMATION:
|
|
ui.display.icon(205, 68, icon, ui.GREY, ui.BG)
|
|
else:
|
|
ui.display.icon(205, 68, icon, c, ui.BG)
|
|
|
|
|
|
class InfoConfirm(ui.Layout):
|
|
DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM)
|
|
DEFAULT_CONFIRM_STYLE = ButtonConfirm
|
|
DEFAULT_CANCEL = res.load(ui.ICON_CANCEL)
|
|
DEFAULT_CANCEL_STYLE = ButtonCancel
|
|
DEFAULT_INFO = res.load(ui.ICON_CLICK) # TODO: this should be (i) icon, not click
|
|
DEFAULT_INFO_STYLE = ButtonDefault
|
|
|
|
def __init__(
|
|
self,
|
|
content: ui.Component,
|
|
confirm: ButtonContent = DEFAULT_CONFIRM,
|
|
confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE,
|
|
cancel: ButtonContent = DEFAULT_CANCEL,
|
|
cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE,
|
|
info: ButtonContent = DEFAULT_INFO,
|
|
info_style: ButtonStyleType = DEFAULT_INFO_STYLE,
|
|
) -> None:
|
|
super().__init__()
|
|
self.content = content
|
|
|
|
self.confirm = Button(ui.grid(14), confirm, confirm_style)
|
|
self.confirm.on_click = self.on_confirm
|
|
|
|
self.info = Button(ui.grid(13), info, info_style)
|
|
self.info.on_click = self.on_info
|
|
|
|
self.cancel = Button(ui.grid(12), cancel, cancel_style)
|
|
self.cancel.on_click = self.on_cancel
|
|
|
|
def dispatch(self, event: int, x: int, y: int) -> None:
|
|
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.dispatch(event, x, y)
|
|
if self.info is not None:
|
|
self.info.dispatch(event, x, y)
|
|
|
|
def on_confirm(self) -> None:
|
|
raise ui.Result(CONFIRMED)
|
|
|
|
def on_cancel(self) -> None:
|
|
raise ui.Result(CANCELLED)
|
|
|
|
def on_info(self) -> None:
|
|
raise ui.Result(INFO)
|
|
|
|
if __debug__:
|
|
|
|
def read_content(self) -> list[str]:
|
|
return self.content.read_content()
|
|
|
|
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
|
from apps.debug import confirm_signal
|
|
|
|
return super().create_tasks() + (confirm_signal(),)
|
|
|
|
|
|
class HoldToConfirm(ui.Layout):
|
|
DEFAULT_CONFIRM = "Hold to confirm"
|
|
DEFAULT_CONFIRM_STYLE = ButtonConfirm
|
|
DEFAULT_LOADER_STYLE = LoaderDefault
|
|
|
|
def __init__(
|
|
self,
|
|
content: ui.Component,
|
|
confirm: str = DEFAULT_CONFIRM,
|
|
confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE,
|
|
loader_style: LoaderStyleType = DEFAULT_LOADER_STYLE,
|
|
cancel: bool = True,
|
|
):
|
|
super().__init__()
|
|
self.content = content
|
|
|
|
self.loader = Loader(loader_style)
|
|
self.loader.on_start = self._on_loader_start
|
|
|
|
if cancel:
|
|
self.confirm = Button(ui.grid(17, n_x=4, cells_x=3), confirm, confirm_style)
|
|
else:
|
|
self.confirm = Button(ui.grid(4, n_x=1), confirm, confirm_style)
|
|
self.confirm.on_press_start = self._on_press_start
|
|
self.confirm.on_press_end = self._on_press_end
|
|
self.confirm.on_click = self._on_click
|
|
|
|
self.cancel = None
|
|
if cancel:
|
|
self.cancel = Button(
|
|
ui.grid(16, n_x=4), res.load(ui.ICON_CANCEL), ButtonAbort
|
|
)
|
|
self.cancel.on_click = self.on_cancel
|
|
|
|
def _on_press_start(self) -> None:
|
|
self.loader.start()
|
|
|
|
def _on_press_end(self) -> None:
|
|
self.loader.stop()
|
|
|
|
def _on_loader_start(self) -> None:
|
|
# 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 - 58, ui.BG)
|
|
self.content.dispatch(ui.REPAINT, 0, 0)
|
|
|
|
def _on_click(self) -> None:
|
|
if self.loader.elapsed_ms() >= self.loader.target_ms:
|
|
self.on_confirm()
|
|
|
|
def dispatch(self, event: int, x: int, y: int) -> None:
|
|
if self.loader.start_ms is not None:
|
|
if utils.DISABLE_ANIMATION:
|
|
self.on_confirm()
|
|
self.loader.dispatch(event, x, y)
|
|
else:
|
|
self.content.dispatch(event, x, y)
|
|
self.confirm.dispatch(event, x, y)
|
|
if self.cancel:
|
|
self.cancel.dispatch(event, x, y)
|
|
|
|
def on_confirm(self) -> None:
|
|
raise ui.Result(CONFIRMED)
|
|
|
|
def on_cancel(self) -> None:
|
|
raise ui.Result(CANCELLED)
|
|
|
|
if __debug__:
|
|
|
|
def read_content(self) -> list[str]:
|
|
return self.content.read_content()
|
|
|
|
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
|
from apps.debug import confirm_signal
|
|
|
|
return super().create_tasks() + (confirm_signal(),)
|