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/components/tt/button.py

254 lines
6.7 KiB

from micropython import const
from typing import TYPE_CHECKING
from trezor import ui
from trezor.ui import display, in_area
if TYPE_CHECKING:
ButtonContent = str | bytes
ButtonStyleType = type["ButtonDefault"]
ButtonStyleStateType = type["ButtonDefault.normal"]
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(normal):
bg_color = ui.FG
fg_color = ui.BLACKISH
text_style = ui.BOLD
border_color = ui.FG
radius = ui.RADIUS
class disabled(normal):
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(ButtonDefault):
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(normal):
bg_color = ui.FG
fg_color = ui.DARK_BLACK
text_style = ui.MONO
border_color = ui.FG
radius = ui.RADIUS
class disabled(normal):
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 ButtonAbort(ButtonDefault):
class normal(ButtonDefault.normal):
bg_color = ui.DARK_GREY
class active(ButtonDefault.active):
fg_color = ui.DARK_GREY
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 states
_INITIAL = const(0)
_PRESSED = const(1)
_RELEASED = const(2)
_DISABLED = const(3)
# button constants
_ICON = const(16) # icon size in pixels
_BORDER = const(4) # border size in pixels
class Button(ui.Component):
def __init__(
self,
area: ui.Area,
content: ButtonContent,
style: ButtonStyleType = ButtonDefault,
) -> None:
super().__init__()
if isinstance(content, str):
self.text = content
self.icon = b""
elif isinstance(content, bytes):
self.icon = content
self.text = ""
else:
raise TypeError
self.area = area
self.normal_style = style.normal
self.active_style = style.active
self.disabled_style = style.disabled
self.state = _INITIAL
def enable(self) -> None:
if self.state is not _INITIAL:
self.state = _INITIAL
self.repaint = True
def disable(self) -> None:
if self.state is not _DISABLED:
self.state = _DISABLED
self.repaint = True
def on_render(self) -> None:
if self.repaint:
if self.state is _INITIAL or self.state is _RELEASED:
s = self.normal_style
elif self.state is _DISABLED:
s = self.disabled_style
elif self.state is _PRESSED:
s = self.active_style
else:
raise RuntimeError # invalid state
ax, ay, aw, ah = self.area
self.render_background(s, ax, ay, aw, ah)
self.render_content(s, ax, ay, aw, ah)
self.repaint = False
def render_background(
self, s: ButtonStyleStateType, ax: int, ay: int, aw: int, ah: int
) -> None:
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,
bg_color,
border_color,
radius,
)
def render_content(
self, s: ButtonStyleStateType, ax: int, ay: int, aw: int, ah: int
) -> None:
tx = ax + aw // 2
ty = ay + ah // 2 + 8
t = self.text
if t:
display.text_center(tx, ty, t, s.text_style, s.fg_color, s.bg_color)
return
i = self.icon
if i:
display.icon(tx - _ICON // 2, ty - _ICON, i, s.fg_color, s.bg_color)
return
def on_touch_start(self, x: int, y: int) -> None:
if self.state is _DISABLED:
return
if in_area(self.area, x, y):
self.state = _PRESSED
self.repaint = True
self.on_press_start()
def on_touch_move(self, x: int, y: int) -> None:
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 self.state is _PRESSED:
self.state = _RELEASED
self.repaint = True
self.on_press_end()
def on_touch_end(self, x: int, y: int) -> None:
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) -> None:
pass
def on_press_end(self) -> None:
pass
def on_click(self) -> None:
pass
if __debug__:
def read_content(self) -> list[str]:
return [f"<Button: {self.text}>"]