From f13c30acffa1d99b98ac80a920970ae5861ca40f Mon Sep 17 00:00:00 2001 From: alvroble <50918598+alvroble@users.noreply.github.com> Date: Sat, 9 Nov 2024 10:14:39 +0100 Subject: [PATCH 1/3] Toast refactor - Messaging Fixes / required changes: * Toast refactor as of #606 guidelines - Add toast messaging categories - General sizing refactor * Unbind icon color from text color * Dynamically adjust toast height --- src/seedsigner/gui/components.py | 3 +- src/seedsigner/gui/toast.py | 125 +++++++++++++++++++++++++++---- 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 9193e451b..4ca586951 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -25,9 +25,10 @@ class GUIConstants: BACKGROUND_COLOR = "black" WARNING_COLOR = "#FFD60A" - DIRE_WARNING_COLOR = "#FF453A" + DIRE_WARNING_COLOR = "#FF5700" SUCCESS_COLOR = "#30D158" INFO_COLOR = "#0084FF" + ERROR_COLOR = "#AD2013" ACCENT_COLOR = "#FF9F0A" BITCOIN_ORANGE = "#FF9416" TESTNET_COLOR = "#00F100" diff --git a/src/seedsigner/gui/toast.py b/src/seedsigner/gui/toast.py index d21ff02c9..c1b64dc6f 100644 --- a/src/seedsigner/gui/toast.py +++ b/src/seedsigner/gui/toast.py @@ -11,6 +11,7 @@ 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 @@ -19,29 +20,37 @@ class ToastOverlay(BaseComponent): def __post_init__(self): super().__post_init__() - 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 - 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 = 0 + if self.icon_name: + self.icon = Icon( + image_draw=self.image_draw, + canvas=self.canvas, + 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 + ) + icon_delta_x = self.icon.width + self.icon.screen_x + self.icon.screen_y = self.canvas_height - self.height + int((self.height - self.icon.height)/2) + + 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, ) + + if self.label.height > GUIConstants.ICON_FONT_SIZE: + self.height = self.label.height + GUIConstants.EDGE_PADDING * 2 # Vertically center the message within the toast (for single- or multi-line # messages). @@ -59,7 +68,9 @@ def render(self): ) # Draw the toast visual elements - self.icon.render() + if self.icon_name: + self.icon.render() + self.label.render() self.renderer.show_image() @@ -123,6 +134,7 @@ def toggle_renderer_lock(self): 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 @@ -230,3 +242,88 @@ def instantiate_toast(self) -> ToastOverlay: 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: + return ToastOverlay( + label_text=self.label_text, + color=GUIConstants.BODY_FONT_COLOR, + font_color=GUIConstants.BODY_FONT_COLOR, + font_size=GUIConstants.BODY_FONT_SIZE, + ) + + + +class InfoToast(DefaultToast): + def instantiate_toast(self) -> ToastOverlay: + return ToastOverlay( + icon_name=SeedSignerIconConstants.WARNING, + label_text=self.label_text, + color=GUIConstants.INFO_COLOR, + font_color=GUIConstants.BODY_FONT_COLOR, + font_size=GUIConstants.BODY_FONT_SIZE, + ) + + + +class SuccessToast(DefaultToast): + def instantiate_toast(self) -> ToastOverlay: + return ToastOverlay( + icon_name=SeedSignerIconConstants.SUCCESS, + label_text=self.label_text, + color=GUIConstants.SUCCESS_COLOR, + font_color=GUIConstants.BODY_FONT_COLOR, + font_size=GUIConstants.BODY_FONT_SIZE, + ) + + + +class WarningToast(DefaultToast): + def instantiate_toast(self) -> ToastOverlay: + return ToastOverlay( + icon_name=SeedSignerIconConstants.WARNING, + label_text=self.label_text, + color=GUIConstants.WARNING_COLOR, + font_color=GUIConstants.BODY_FONT_COLOR, + font_size=GUIConstants.BODY_FONT_SIZE, + ) + + + +class DireWarningToast(DefaultToast): + def instantiate_toast(self) -> ToastOverlay: + return ToastOverlay( + icon_name=SeedSignerIconConstants.WARNING, + label_text=self.label_text, + color=GUIConstants.DIRE_WARNING_COLOR, + font_color=GUIConstants.BODY_FONT_COLOR, + font_size=GUIConstants.BODY_FONT_SIZE, + ) + + + +class ErrorToast(DefaultToast): + def instantiate_toast(self) -> ToastOverlay: + return ToastOverlay( + icon_name=SeedSignerIconConstants.ERROR, + label_text=self.label_text, + color=GUIConstants.ERROR_COLOR, + font_color=GUIConstants.BODY_FONT_COLOR, + font_size=GUIConstants.BODY_FONT_SIZE, + ) \ No newline at end of file From e62a355a18d3f3b76682c319eb0de36a4bf5ce0b Mon Sep 17 00:00:00 2001 From: alvroble <50918598+alvroble@users.noreply.github.com> Date: Sat, 9 Nov 2024 10:18:11 +0100 Subject: [PATCH 2/3] Screenshot generator tests for messaging toasts --- tests/screenshot_generator/generator.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index 4c0c092a8..d53340dcb 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -28,6 +28,7 @@ from seedsigner.controller import Controller from seedsigner.gui.renderer import Renderer from seedsigner.gui.toast import BaseToastOverlayManagerThread, RemoveSDCardToastManagerThread, SDCardStateChangeToastManagerThread +from seedsigner.gui.toast import DefaultToast, InfoToast, SuccessToast, WarningToast, ErrorToast, DireWarningToast from seedsigner.hardware.microsd import MicroSD from seedsigner.models.decode_qr import DecodeQR from seedsigner.models.qr_type import QRType @@ -165,6 +166,12 @@ def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes): (MainMenuView, {}, 'MainMenuView_SDCardStateChangeToast_removed', SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__REMOVED)), (MainMenuView, {}, 'MainMenuView_SDCardStateChangeToast_inserted', SDCardStateChangeToastManagerThread(action=MicroSD.ACTION__INSERTED)), (MainMenuView, {}, 'MainMenuView_RemoveSDCardToast', RemoveSDCardToastManagerThread(activation_delay=0)), + (MainMenuView, {}, 'MainMenuView_DefaultToast', DefaultToast("This is a default text toast!")), + (MainMenuView, {}, 'MainMenuView_InfoToast', InfoToast("This is an info toast!")), + (MainMenuView, {}, 'MainMenuView_SuccessToast', SuccessToast("This is a success toast!")), + (MainMenuView, {}, 'MainMenuView_WarningToast', WarningToast("This is a warning toast!")), + (MainMenuView, {}, 'MainMenuView_DireWarningToast', DireWarningToast("This is a dire warning toast!")), + (MainMenuView, {}, 'MainMenuView_ErrorToast', ErrorToast("This is an error toast!")), PowerOptionsView, RestartView, PowerOffView, From 64830eee12f91829f3f7b6730201fe4d1dead09c Mon Sep 17 00:00:00 2001 From: alvroble <50918598+alvroble@users.noreply.github.com> Date: Sat, 9 Nov 2024 10:48:47 +0100 Subject: [PATCH 3/3] Bug fix: center icon --- src/seedsigner/gui/toast.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/gui/toast.py b/src/seedsigner/gui/toast.py index c1b64dc6f..b5ff43915 100644 --- a/src/seedsigner/gui/toast.py +++ b/src/seedsigner/gui/toast.py @@ -31,8 +31,6 @@ def __post_init__(self): icon_color=self.color ) icon_delta_x = self.icon.width + self.icon.screen_x - self.icon.screen_y = self.canvas_height - self.height + int((self.height - self.icon.height)/2) - self.label = TextArea( image_draw=self.image_draw, @@ -52,9 +50,12 @@ def __post_init__(self): if self.label.height > GUIConstants.ICON_FONT_SIZE: self.height = self.label.height + GUIConstants.EDGE_PADDING * 2 - # Vertically center the message within the toast (for single- or multi-line + # 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):