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.
trezor-firmware/core/src/trezor/ui/loader.py

133 lines
3.5 KiB

import utime
from micropython import const
from typing import TYPE_CHECKING
from trezor import res, ui, utils
from trezor.ui import display
class LoaderDefault:
class normal:
bg_color = ui.BG
fg_color = ui.GREEN
icon: str | None = None
icon_fg_color: int | None = None
class active(normal):
bg_color = ui.BG
fg_color = ui.GREEN
icon: str | None = ui.ICON_CHECK
icon_fg_color: int | None = ui.WHITE
class LoaderNeutral(LoaderDefault):
class normal(LoaderDefault.normal):
fg_color = ui.FG
class active(LoaderDefault.active):
fg_color = ui.FG
if TYPE_CHECKING:
LoaderStyleType = type[LoaderDefault]
_TARGET_MS = const(1000)
_OFFSET_Y = const(-24)
_REVERSE_SPEEDUP = const(2)
class Loader(ui.Component):
def __init__(
self,
style: LoaderStyleType = LoaderDefault,
target_ms: int = _TARGET_MS,
offset_y: int = _OFFSET_Y,
reverse_speedup: int = _REVERSE_SPEEDUP,
) -> None:
super().__init__()
self.normal_style = style.normal
self.active_style = style.active
self.target_ms = target_ms
self.start_ms: int | None = None
self.stop_ms: int | None = None
self.offset_y = offset_y
self.reverse_speedup = reverse_speedup
def start(self) -> None:
if self.start_ms is not None and self.stop_ms is not None:
self.start_ms = utime.ticks_ms() - self.elapsed_ms()
else:
self.start_ms = utime.ticks_ms()
self.stop_ms = None
self.on_start()
def stop(self) -> None:
self.stop_ms = utime.ticks_ms()
def elapsed_ms(self) -> int:
start = self.start_ms
stop = self.stop_ms
now = utime.ticks_ms()
if start is None:
return 0
elif stop is not None:
return max(stop - start + (stop - now) * self.reverse_speedup, 0)
else:
return min(now - start, self.target_ms)
def on_render(self) -> None:
if self.start_ms is None:
return
target = self.target_ms
r = self.elapsed_ms()
if r != target:
s = self.normal_style
else:
s = self.active_style
progress = r * 1000 // target # scale to 0-1000
if s.icon is None:
display.loader(progress, False, self.offset_y, s.fg_color, s.bg_color)
else:
display.loader(
progress,
False,
self.offset_y,
s.fg_color,
s.bg_color,
res.load(s.icon),
s.icon_fg_color,
)
if (r == 0) and (self.stop_ms is not None):
self.start_ms = None
self.stop_ms = None
self.on_start()
if r == target:
self.on_finish()
def on_start(self) -> None:
pass
def on_finish(self) -> None:
pass
class LoadingAnimation(ui.Layout):
def __init__(self, style: LoaderStyleType = LoaderDefault) -> None:
super().__init__()
self.loader = Loader(style)
self.loader.on_finish = self.on_finish
self.loader.start()
def dispatch(self, event: int, x: int, y: int) -> None:
if not self.loader.elapsed_ms():
self.loader.start()
self.loader.dispatch(event, x, y)
if utils.DISABLE_ANIMATION:
self.on_finish()
def on_finish(self) -> None:
raise ui.Result(None)