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, ): self.content = content self.loader = Loader(loader_style) self.loader.on_start = self._on_loader_start # type: ignore self.button = Button(ui.grid(4, n_x=1), confirm, confirm_style) self.button.on_press_start = self._on_press_start # type: ignore self.button.on_press_end = self._on_press_end # type: ignore self.button.on_click = self._on_click # type: ignore 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: self.loader.dispatch(event, x, y) else: self.content.dispatch(event, x, y) self.button.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) if __debug__: def read_content(self) -> List[str]: return self.content.read_content() def create_tasks(self) -> Tuple[loop.Task, ...]: 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, confirm=DEFAULT_CONFIRM, confirm_style=DEFAULT_CONFIRM_STYLE, loader_style=DEFAULT_LOADER_STYLE, ): self.content = content 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() 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: self.content.dispatch(event, x, y) self.button.dispatch(event, x, y) def on_confirm(self): raise ui.Result(CONFIRMED)
class HoldToConfirmDialog(Widget): def __init__( self, content, hold="Hold to confirm", button_style=ui.BTN_CONFIRM, loader_style=ui.LDR_DEFAULT, ): 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.start() return _STARTED if was_active and not is_active: self.content.taint() 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: if __debug__: result = await loop.spawn(super().__iter__(), confirm_signal) else: result = await super().__iter__() return result
class HoldToConfirmDialog(Widget): def __init__( self, content, hold="Hold to confirm", button_style=ui.BTN_CONFIRM, loader_style=ui.LDR_DEFAULT, ): self.content = content self.button = Button(ui.grid(4, n_x=1), hold, style=button_style) self.loader = Loader(style=loader_style) def taint(self): super().taint() self.button.taint() self.content.taint() def render(self): self.button.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.start() return _STARTED if was_active and not is_active: self.content.taint() 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(): content_loop = self.loader else: content_loop = self.content confirm_loop = super().__iter__() # default loop (render on touch) if __debug__: result = await loop.spawn(content_loop, confirm_loop, confirm_signal) else: result = await loop.spawn(content_loop, confirm_loop) return result
class HoldToConfirmDialog(Widget): def __init__(self, content, hold='Hold to confirm', *args, **kwargs): self.content = content self.button = Button(ui.grid(4, n_x=1), hold, normal_style=ui.BTN_CONFIRM, active_style=ui.BTN_CONFIRM_ACTIVE) self.loader = Loader(*args, **kwargs) def render(self): self.button.render() def touch(self, event, pos): button = self.button was_started = button.state & BTN_STARTED and button.state & BTN_ACTIVE button.touch(event, pos) is_started = button.state & BTN_STARTED and button.state & BTN_ACTIVE if is_started and not was_started: ui.display.clear() self.loader.start() return _STARTED if was_started and not is_started: 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(): content_loop = self.loader else: content_loop = self.content confirm_loop = super().__iter__() # default loop (render on touch) result = await loop.wait(content_loop, confirm_loop) return result
class Homescreen(HomescreenBase): def __init__(self) -> None: super().__init__() if not storage.device.is_initialized(): self.label = "Go to trezor.io/start" self.loader = Loader( style=LoaderNeutral, target_ms=_LOADER_TOTAL_MS - _LOADER_DELAY_MS, offset_y=-10, reverse_speedup=3, ) self.touch_ms: Optional[int] = None def on_render(self) -> None: if not self.repaint: return # warning bar on top if storage.device.is_initialized() and storage.device.no_backup(): ui.header_error("SEEDLESS") elif storage.device.is_initialized() and storage.device.unfinished_backup(): ui.header_error("BACKUP FAILED!") elif storage.device.is_initialized() and storage.device.needs_backup(): ui.header_warning("NEEDS BACKUP!") elif storage.device.is_initialized() and not config.has_pin(): ui.header_warning("PIN NOT SET!") elif storage.device.get_experimental_features(): ui.header_warning("EXPERIMENTAL MODE!") else: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) # homescreen with shifted avatar and text on bottom ui.display.avatar(48, 48 - 10, self.image, ui.WHITE, ui.BLACK) ui.display.text_center(ui.WIDTH // 2, 220, self.label, ui.BOLD, ui.FG, ui.BG) self.repaint = False def on_touch_start(self, _x: int, _y: int) -> None: if self.loader.start_ms is not None: self.loader.start() elif config.has_pin(): self.touch_ms = utime.ticks_ms() def on_touch_end(self, _x: int, _y: int) -> None: if self.loader.start_ms is not None: self.repaint = True self.loader.stop() self.touch_ms = None # raise here instead of self.loader.on_finish so as not to send TOUCH_END to the lockscreen if self.loader.elapsed_ms() >= self.loader.target_ms: raise ui.Result(None) def _loader_start(self) -> None: ui.display.clear() ui.display.text_center(ui.WIDTH // 2, 35, "Hold to lock", ui.BOLD, ui.FG, ui.BG) self.loader.start() def dispatch(self, event: int, x: int, y: int) -> None: if ( self.touch_ms is not None and self.touch_ms + _LOADER_DELAY_MS < utime.ticks_ms() ): self.touch_ms = None self._loader_start() if event is ui.RENDER and self.loader.start_ms is not None: self.loader.dispatch(event, x, y) else: super().dispatch(event, x, y)
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 # type: ignore 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 # type: ignore self.confirm.on_press_end = self._on_press_end # type: ignore self.confirm.on_click = self._on_click # type: ignore 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 # type: ignore 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.Task, ...]: from apps.debug import confirm_signal return super().create_tasks() + (confirm_signal(), )
class Homescreen(HomescreenBase): RENDER_INDICATOR = storage.cache.HOMESCREEN_ON def __init__(self) -> None: super().__init__() if not storage.device.is_initialized(): self.label = "Go to trezor.io/start" self.loader = Loader( style=LoaderNeutral, target_ms=_LOADER_TOTAL_MS - _LOADER_DELAY_MS, offset_y=-10, reverse_speedup=3, ) self.touch_ms: int | None = None def do_render(self) -> None: # warning bar on top if storage.device.is_initialized() and storage.device.no_backup(): ui.header_error("SEEDLESS") elif storage.device.is_initialized( ) and storage.device.unfinished_backup(): ui.header_error("BACKUP FAILED!") elif storage.device.is_initialized() and storage.device.needs_backup(): ui.header_warning("NEEDS BACKUP!") elif storage.device.is_initialized() and not config.has_pin(): ui.header_warning("PIN NOT SET!") elif storage.device.get_experimental_features(): ui.header_warning("EXPERIMENTAL MODE!") else: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) # homescreen with shifted avatar and text on bottom # Differs for each model # TODO: support homescreen avatar change for R and 1 if utils.MODEL in ("T", ): ui.display.avatar(48, 48 - 10, self.get_image(), ui.WHITE, ui.BLACK) elif utils.MODEL in ("R", ): icon = "trezor/res/homescreen_model_r.toif" # 92x92 px ui.display.icon(18, 18, ui.res.load(icon), ui.style.FG, ui.style.BG) elif utils.MODEL in ("1", ): icon = "trezor/res/homescreen_model_1.toif" # 64x36 px ui.display.icon(33, 14, ui.res.load(icon), ui.style.FG, ui.style.BG) label_heights = {"1": 60, "R": 120, "T": 220} ui.display.text_center(ui.WIDTH // 2, label_heights[utils.MODEL], self.label, ui.BOLD, ui.FG, ui.BG) def on_touch_start(self, _x: int, _y: int) -> None: if self.loader.start_ms is not None: self.loader.start() elif config.has_pin(): self.touch_ms = utime.ticks_ms() def on_touch_end(self, _x: int, _y: int) -> None: if self.loader.start_ms is not None: self.set_repaint(True) self.loader.stop() self.touch_ms = None # raise here instead of self.loader.on_finish so as not to send TOUCH_END to the lockscreen if self.loader.elapsed_ms() >= self.loader.target_ms: raise ui.Result(None) def _loader_start(self) -> None: ui.display.clear() ui.display.text_center(ui.WIDTH // 2, 35, "Hold to lock", ui.BOLD, ui.FG, ui.BG) self.loader.start() def dispatch(self, event: int, x: int, y: int) -> None: if (self.touch_ms is not None and self.touch_ms + _LOADER_DELAY_MS < utime.ticks_ms()): self.touch_ms = None self._loader_start() if event is ui.RENDER and self.loader.start_ms is not None: self.loader.dispatch(event, x, y) else: super().dispatch(event, x, y)