Merge pull request #625 from alvroble/messaging_toasts

[Enhancement] Messaging toast refactor
This commit is contained in:
Nick Klockenga
2025-02-28 15:53:36 -05:00
committed by GitHub
2 changed files with 126 additions and 15 deletions
+110 -6
View File
@@ -13,6 +13,7 @@ logger = logging.getLogger(__name__)
class ToastOverlay(BaseComponent):
icon_name: str = None
color: str = GUIConstants.NOTIFICATION_COLOR
font_color: str = GUIConstants.NOTIFICATION_COLOR
label_text: str = None
height: int = GUIConstants.ICON_TOAST_FONT_SIZE + 2*GUIConstants.EDGE_PADDING
font_size: int = 19
@@ -21,34 +22,43 @@ class ToastOverlay(BaseComponent):
def __post_init__(self):
super().__post_init__()
icon_delta_x = 0
if self.icon_name:
self.icon = Icon(
image_draw=self.image_draw,
canvas=self.canvas,
screen_x=self.outline_thickness + 2*GUIConstants.EDGE_PADDING, # Push the icon further from the left edge than strictly necessary
screen_x=self.outline_thickness + GUIConstants.EDGE_PADDING, # Push the icon further from the left edge than strictly necessary
icon_name=self.icon_name,
icon_size=GUIConstants.ICON_TOAST_FONT_SIZE,
icon_color=self.color
)
self.icon.screen_y = self.canvas_height - self.height + int((self.height - self.icon.height)/2)
icon_delta_x = self.icon.width + self.icon.screen_x
self.label = TextArea(
image_draw=self.image_draw,
canvas=self.canvas,
text=self.label_text,
font_size=self.font_size,
font_color=self.color,
font_color=self.font_color,
edge_padding=0,
is_text_centered=False,
auto_line_break=True,
width=self.canvas_width - self.icon.screen_x - self.icon.width - GUIConstants.COMPONENT_PADDING - self.outline_thickness,
screen_x=self.icon.screen_x + self.icon.width + GUIConstants.COMPONENT_PADDING,
width=self.canvas_width - icon_delta_x - 2 * GUIConstants.COMPONENT_PADDING - 2 * self.outline_thickness,
screen_x=icon_delta_x + GUIConstants.COMPONENT_PADDING,
allow_text_overflow=False,
height_ignores_below_baseline=True,
)
# Vertically center the message within the toast (for single- or multi-line
if self.label.height > GUIConstants.ICON_FONT_SIZE:
self.height = self.label.height + GUIConstants.EDGE_PADDING * 2
# Vertically center the message and icon within the toast (for single- or multi-line
# messages).
self.label.screen_y = self.canvas_height - self.height + self.outline_thickness + int((self.height - 2*self.outline_thickness - self.label.height)/2)
if self.icon_name:
self.icon.screen_y = self.canvas_height - self.height + int((self.height - self.icon.height)/2)
def render(self):
# Render the toast's solid background
@@ -61,7 +71,9 @@ class ToastOverlay(BaseComponent):
)
# Draw the toast visual elements
if self.icon_name:
self.icon.render()
self.label.render()
self.renderer.show_image()
@@ -125,6 +137,7 @@ class BaseToastOverlayManagerThread(BaseThread):
def run(self):
logger.info(f"{self.__class__.__name__}: started")
start = time.time()
time.sleep(0.2)
while time.time() - start < self.activation_delay:
if self.hw_inputs.has_any_input():
# User has pressed a button, cancel the toast
@@ -237,3 +250,94 @@ class SDCardStateChangeToastManagerThread(BaseToastOverlayManagerThread):
icon_name=SeedSignerIconConstants.MICROSD,
label_text=self.message,
)
"""****************************************************************************
Messaging toasts
****************************************************************************"""
class DefaultToast(BaseToastOverlayManagerThread):
def __init__(self, label_text="This is a notification toast", activation_delay=0, duration=3):
# Note: activation_delay is configurable so the screenshot generator can get the
# toast to immediately render.
self.label_text = label_text
super().__init__(
activation_delay=activation_delay, # seconds
duration=duration # seconds
)
def instantiate_toast(self) -> ToastOverlay:
body_font_size = GUIConstants.get_body_font_size()
return ToastOverlay(
label_text=self.label_text,
color=GUIConstants.BODY_FONT_COLOR,
font_color=GUIConstants.BODY_FONT_COLOR,
font_size=body_font_size,
)
class InfoToast(DefaultToast):
def instantiate_toast(self) -> ToastOverlay:
body_font_size = GUIConstants.get_body_font_size()
return ToastOverlay(
icon_name=SeedSignerIconConstants.INFO,
label_text=self.label_text,
color=GUIConstants.INFO_COLOR,
font_color=GUIConstants.BODY_FONT_COLOR,
font_size=body_font_size,
)
class SuccessToast(DefaultToast):
def instantiate_toast(self) -> ToastOverlay:
body_font_size = GUIConstants.get_body_font_size()
return ToastOverlay(
icon_name=SeedSignerIconConstants.SUCCESS,
label_text=self.label_text,
color=GUIConstants.SUCCESS_COLOR,
font_color=GUIConstants.BODY_FONT_COLOR,
font_size=body_font_size,
)
class WarningToast(DefaultToast):
def instantiate_toast(self) -> ToastOverlay:
body_font_size = GUIConstants.get_body_font_size()
return ToastOverlay(
icon_name=SeedSignerIconConstants.WARNING,
label_text=self.label_text,
color=GUIConstants.WARNING_COLOR,
font_color=GUIConstants.BODY_FONT_COLOR,
font_size=body_font_size,
)
class DireWarningToast(DefaultToast):
def instantiate_toast(self) -> ToastOverlay:
body_font_size = GUIConstants.get_body_font_size()
return ToastOverlay(
icon_name=SeedSignerIconConstants.WARNING,
label_text=self.label_text,
color=GUIConstants.DIRE_WARNING_COLOR,
font_color=GUIConstants.BODY_FONT_COLOR,
font_size=body_font_size,
)
class ErrorToast(DefaultToast):
def instantiate_toast(self) -> ToastOverlay:
body_font_size = GUIConstants.get_body_font_size()
return ToastOverlay(
icon_name=SeedSignerIconConstants.ERROR,
label_text=self.label_text,
color=GUIConstants.ERROR_COLOR,
font_color=GUIConstants.BODY_FONT_COLOR,
font_size=body_font_size,
)
+7
View File
@@ -27,6 +27,7 @@ from seedsigner.controller import Controller
from seedsigner.gui.renderer import Renderer
from seedsigner.gui.screens.seed_screens import SeedAddPassphraseScreen
from seedsigner.gui.toast import RemoveSDCardToastManagerThread, SDCardStateChangeToastManagerThread
from seedsigner.gui.toast import DefaultToast, InfoToast, SuccessToast, WarningToast, ErrorToast, DireWarningToast
from seedsigner.hardware.microsd import MicroSD
from seedsigner.helpers import embit_utils
from seedsigner.models.decode_qr import DecodeQR
@@ -243,6 +244,12 @@ def generate_screenshots(locale):
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SDCardStateChangeToast_removed', toast_thread=SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__REMOVED, activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SDCardStateChangeToast_inserted', toast_thread=SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__INSERTED, activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_RemoveSDCardToast', toast_thread=RemoveSDCardToastManagerThread(activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_DefaultToast', toast_thread=DefaultToast("This is a default text toast!", activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_InfoToast', toast_thread=InfoToast("This is an info toast!", activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_SuccessToast', toast_thread=SuccessToast("This is a success toast!", activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_WarningToast', toast_thread=WarningToast("This is a warning toast!", activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_DireWarningToast', toast_thread=DireWarningToast("This is a dire warning toast!", activation_delay=0, duration=0)),
ScreenshotConfig(MainMenuView, screenshot_name='MainMenuView_ErrorToast', toast_thread=ErrorToast("This is an error toast!", activation_delay=0, duration=0)),
ScreenshotConfig(PowerOptionsView),
ScreenshotConfig(RestartView),
ScreenshotConfig(PowerOffView),