From 4833bffdcac069c45bc3fe43b7c73ccdd836bd3e Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Tue, 13 Jun 2023 10:24:05 +0800 Subject: [PATCH 1/5] Add QR brightness tips screen --- src/seedsigner/gui/screens/screen.py | 26 ++++++++++++++++++++ src/seedsigner/models/settings_definition.py | 7 ++++++ tests/test_controller.py | 1 + 3 files changed, 34 insertions(+) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index 50a94e70..4727c352 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -660,6 +660,21 @@ class LargeButtonScreen(BaseTopNavScreen): class QRDisplayScreen(BaseScreen): qr_encoder: EncodeQR = None + class AdjustBrightnessTipsScreen(BaseScreen): + def __post_init__(self): + super().__post_init__() + + text_area = TextArea( + text="Use joystick Up/Down for adjusting brightness", + is_text_centered=True, + screen_y=self.canvas_height // 2, + ) + self.components.append(text_area) + + def _run(self): + # show the screen for 2 seconds + time.sleep(2) + class QRDisplayThread(BaseThread): def __init__(self, qr_encoder: EncodeQR, qr_brightness: ThreadsafeCounter, renderer: Renderer): super().__init__() @@ -669,6 +684,17 @@ class QRDisplayScreen(BaseScreen): def run(self): + from seedsigner.models.settings import Settings + + # Display the brightness tip screen + show_brightness_tips = ( + Settings.get_instance().get_value( + SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS + ) + == SettingsConstants.OPTION__ENABLED + ) + if show_brightness_tips: + QRDisplayScreen.AdjustBrightnessTipsScreen().display() # Loop whether the QR is a single frame or animated; each loop might adjust # brightness setting. while self.keep_running: diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 6c2af745..49d2a6ea 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -160,6 +160,7 @@ class SettingsConstants: SETTING__BIP85_CHILD_SEEDS = "bip85_child_seeds" SETTING__PRIVACY_WARNINGS = "privacy_warnings" SETTING__DIRE_WARNINGS = "dire_warnings" + SETTING__QR_BRIGHTNESS_TIPS = "qr_brightness_tips" SETTING__PARTNER_LOGOS = "partner_logos" SETTING__DEBUG = "debug" @@ -465,6 +466,12 @@ class SettingsDefinition: visibility=SettingsConstants.VISIBILITY__ADVANCED, default_value=SettingsConstants.OPTION__ENABLED), + SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, + attr_name=SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS, + display_name="Show QR brightness tips", + visibility=SettingsConstants.VISIBILITY__ADVANCED, + default_value=SettingsConstants.OPTION__ENABLED), + SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, attr_name=SettingsConstants.SETTING__PARTNER_LOGOS, display_name="Show partner logos", diff --git a/tests/test_controller.py b/tests/test_controller.py index bd8b2c18..064f1069 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -68,6 +68,7 @@ def test_missing_settings_get_defaults(reset_controller): assert controller.settings.get_value(SettingsConstants.SETTING__BIP85_CHILD_SEEDS) == SettingsConstants.OPTION__DISABLED assert controller.settings.get_value(SettingsConstants.SETTING__PRIVACY_WARNINGS) == SettingsConstants.OPTION__ENABLED assert controller.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS) == SettingsConstants.OPTION__ENABLED assert controller.settings.get_value(SettingsConstants.SETTING__PARTNER_LOGOS) == SettingsConstants.OPTION__ENABLED # Hidden Settings defaults From 49aeaa85370c9f69b9d22e35ba53fbbedbf44d95 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Wed, 14 Jun 2023 09:32:52 +0800 Subject: [PATCH 2/5] Set the displayed time to 3 seconds. --- src/seedsigner/gui/screens/screen.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index 4727c352..440ad2b4 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -672,8 +672,8 @@ class QRDisplayScreen(BaseScreen): self.components.append(text_area) def _run(self): - # show the screen for 2 seconds - time.sleep(2) + # show the screen for 3 seconds + time.sleep(3) class QRDisplayThread(BaseThread): def __init__(self, qr_encoder: EncodeQR, qr_brightness: ThreadsafeCounter, renderer: Renderer): @@ -686,7 +686,7 @@ class QRDisplayScreen(BaseScreen): def run(self): from seedsigner.models.settings import Settings - # Display the brightness tip screen + # Display the brightness tips screen show_brightness_tips = ( Settings.get_instance().get_value( SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS @@ -695,6 +695,7 @@ class QRDisplayScreen(BaseScreen): ) if show_brightness_tips: QRDisplayScreen.AdjustBrightnessTipsScreen().display() + # Loop whether the QR is a single frame or animated; each loop might adjust # brightness setting. while self.keep_running: From 1e11f09f56073d69b584cc4642f82317763ed983 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Sun, 25 Jun 2023 19:13:57 +0800 Subject: [PATCH 3/5] Fix the issue where clicking a button on the tips page may cause exiting from the QR code page. --- src/seedsigner/gui/screens/screen.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index 440ad2b4..b790b824 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -684,18 +684,6 @@ class QRDisplayScreen(BaseScreen): def run(self): - from seedsigner.models.settings import Settings - - # Display the brightness tips screen - show_brightness_tips = ( - Settings.get_instance().get_value( - SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS - ) - == SettingsConstants.OPTION__ENABLED - ) - if show_brightness_tips: - QRDisplayScreen.AdjustBrightnessTipsScreen().display() - # Loop whether the QR is a single frame or animated; each loop might adjust # brightness setting. while self.keep_running: @@ -717,6 +705,16 @@ class QRDisplayScreen(BaseScreen): settings = Settings.get_instance() self.qr_brightness = ThreadsafeCounter(initial_value=settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS)) + # Display the brightness tips screen + show_brightness_tips = ( + settings.get_value( + SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS + ) + == SettingsConstants.OPTION__ENABLED + ) + if show_brightness_tips: + QRDisplayScreen.AdjustBrightnessTipsScreen().display() + self.threads.append(QRDisplayScreen.QRDisplayThread( qr_encoder=self.qr_encoder, qr_brightness=self.qr_brightness, From b232e22b2d0f08aa53dc8106ec9b83fd8b9df4f1 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Sun, 9 Jul 2023 17:07:31 +0800 Subject: [PATCH 4/5] Use Toast to display the brightness tip --- src/seedsigner/gui/screens/screen.py | 117 +++++++++++++++++++-------- 1 file changed, 85 insertions(+), 32 deletions(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index b790b824..d45e2cdc 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -8,9 +8,11 @@ from seedsigner.gui.renderer import Renderer from seedsigner.models.threads import BaseThread, ThreadsafeCounter from seedsigner.models.encode_qr import EncodeQR -from seedsigner.models.settings import Settings, SettingsConstants +from seedsigner.models.settings import SettingsConstants -from ..components import (FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton, LargeIconButton, SeedSignerCustomIconConstants, TopNav, TextArea, load_image, ToastOverlay) +from ..components import (FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton, + LargeIconButton, SeedSignerCustomIconConstants, TopNav, TextArea, load_image, ToastOverlay, + Fonts) from seedsigner.hardware.buttons import HardwareButtonsConstants, HardwareButtons @@ -660,42 +662,98 @@ class LargeButtonScreen(BaseTopNavScreen): class QRDisplayScreen(BaseScreen): qr_encoder: EncodeQR = None - class AdjustBrightnessTipsScreen(BaseScreen): - def __post_init__(self): - super().__post_init__() - - text_area = TextArea( - text="Use joystick Up/Down for adjusting brightness", - is_text_centered=True, - screen_y=self.canvas_height // 2, - ) - self.components.append(text_area) - - def _run(self): - # show the screen for 3 seconds - time.sleep(3) - class QRDisplayThread(BaseThread): - def __init__(self, qr_encoder: EncodeQR, qr_brightness: ThreadsafeCounter, renderer: Renderer): + def __init__(self, qr_encoder: EncodeQR, qr_brightness: ThreadsafeCounter, renderer: Renderer, + tips_start_time: ThreadsafeCounter): super().__init__() self.qr_encoder = qr_encoder self.qr_brightness = qr_brightness self.renderer = renderer + self.tips_start_time = tips_start_time + def add_brightness_tips(self, image: Image.Image) -> None: + rectangle_height, rectangle_width = 60, 240 + rectangle = Image.new('RGBA', (rectangle_width, rectangle_height), (0, 0, 0, 0)) + img_draw = ImageDraw.Draw(rectangle) + img_draw.rounded_rectangle((0, 0, rectangle_width - 1, rectangle_height - 1), outline=(0, 0, 0, 0), + width=1, radius=10, fill=(0, 0, 0, 224)) + + font = Fonts.get_font(GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, 16) + content_fill = (255, 255, 255) + # blank space(10) + font height(14) + blank space(12) + font height(14) + blank space(10) = 58 + img_draw.text((50, 10), "Brighter", fill=content_fill, font=font) + img_draw.text((50, 36), "Darker", fill=content_fill, font=font) + + # draw arrows + arrow_init_x, arrow_init_y = 20, 10 + arrow_height, arrow_width = 14, 8 + # up arrow + img_draw.line( + ( + arrow_init_x, + arrow_height + arrow_init_y, + arrow_init_x + arrow_width, + arrow_init_y, + ), + fill=content_fill, + ) + img_draw.line( + ( + arrow_init_x + arrow_width, + arrow_init_y, + arrow_init_x + arrow_width * 2, + arrow_init_y + arrow_height, + ), + fill=content_fill, + ) + # down arrow + img_draw.line( + ( + arrow_init_x, + 60 - (arrow_height + arrow_init_y), + arrow_init_x + arrow_width, + (60 - arrow_init_y), + ), + fill=content_fill, + ) + img_draw.line( + ( + arrow_init_x + arrow_width, + 60 - arrow_init_y, + arrow_init_x + arrow_width * 2, + 60 - (arrow_init_y + arrow_height), + ), + fill=content_fill, + ) + image.paste(rectangle, (0, 240 - rectangle_height), rectangle) def run(self): + from seedsigner.models.settings import Settings + settings = Settings.get_instance() + show_brightness_tips = ( + settings.get_value( + SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS + ) + == SettingsConstants.OPTION__ENABLED + ) + # Loop whether the QR is a single frame or animated; each loop might adjust # brightness setting. while self.keep_running: # convert the self.qr_brightness integer (31-255) into hex triplets hex_color = (hex(self.qr_brightness.cur_count).split('x')[1]) * 3 - image = self.qr_encoder.next_part_image(240,240, border=2, background_color=hex_color) + image = self.qr_encoder.next_part_image(240, 240, border=2, background_color=hex_color) + + # Display the brightness tips toast + duration = 10 ** 9 * 1 # 1 seconds + if show_brightness_tips and time.time_ns() - self.tips_start_time.cur_count < duration: + self.add_brightness_tips(image) + with self.renderer.lock: self.renderer.show_image(image) # Target n held frames per second before rendering next QR image - time.sleep(5/30.0) - + time.sleep(5 / 30.0) def __post_init__(self): from seedsigner.models.settings import Settings @@ -703,22 +761,15 @@ class QRDisplayScreen(BaseScreen): # Shared coordination var so the display thread can detect success settings = Settings.get_instance() - self.qr_brightness = ThreadsafeCounter(initial_value=settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS)) - - # Display the brightness tips screen - show_brightness_tips = ( - settings.get_value( - SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS - ) - == SettingsConstants.OPTION__ENABLED - ) - if show_brightness_tips: - QRDisplayScreen.AdjustBrightnessTipsScreen().display() + self.qr_brightness = ThreadsafeCounter( + initial_value=settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS)) + self.tips_start_time = ThreadsafeCounter(initial_value=time.time_ns()) self.threads.append(QRDisplayScreen.QRDisplayThread( qr_encoder=self.qr_encoder, qr_brightness=self.qr_brightness, renderer=self.renderer, + tips_start_time=self.tips_start_time )) @@ -739,10 +790,12 @@ class QRDisplayScreen(BaseScreen): if user_input == HardwareButtonsConstants.KEY_DOWN: # Reduce QR code background brightness self.qr_brightness.set_value(max(31, self.qr_brightness.cur_count - 31)) + self.tips_start_time.set_value(time.time_ns()) elif user_input == HardwareButtonsConstants.KEY_UP: # Incrase QR code background brightness self.qr_brightness.set_value(min(self.qr_brightness.cur_count + 31, 255)) + self.tips_start_time.set_value(time.time_ns()) else: # Any other input exits the screen From ef70a8dc0b7bc8164140db4031471baa498794eb Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 15 Jul 2023 11:28:55 -0500 Subject: [PATCH 5/5] Refactoring PR #389 (#1) * Refactor to use existing components * final tweaks * Update screen.py * Adding comments, TODO --- src/seedsigner/gui/components.py | 6 +- src/seedsigner/gui/screens/screen.py | 123 +++++++++++++++------------ 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 8b91b1d7..429890f9 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -73,6 +73,8 @@ class FontAwesomeIconConstants: CARET_LEFT = "\uf0d9" CARET_RIGHT = "\uf0da" CARET_UP = "\uf0d8" + CHEVRON_UP = "\uf077" + CHEVRON_DOWN = "\uf078" SOLID_CIRCLE_CHECK = "\uf058" CIRCLE = "\uf111" CIRCLE_CHEVRON_RIGHT = "\uf138" @@ -405,7 +407,7 @@ class TextArea(BaseComponent): # with bicubic resampling. # TODO: Store resulting super-sampled image as a member var in __post_init__ and # just re-paste it here. - img = Image.new("RGB", (self.supersampled_width, self.supersampled_height), self.background_color) + img = Image.new("RGBA", (self.supersampled_width, self.supersampled_height), self.background_color) draw = ImageDraw.Draw(img) cur_y = self.text_y @@ -578,7 +580,7 @@ class ToastOverlay(BaseComponent): def __post_init__(self): super().__post_init__() - + self.icon = Icon( image_draw=self.image_draw, canvas=self.canvas, diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index d45e2cdc..ea08a4f5 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -671,71 +671,83 @@ class QRDisplayScreen(BaseScreen): self.renderer = renderer self.tips_start_time = tips_start_time + def add_brightness_tips(self, image: Image.Image) -> None: - rectangle_height, rectangle_width = 60, 240 + # TODO: Refactor ToastOverlay to support two lines of icon + text and use + # that instead of this more manual approach. + + # Instantiate a temp Image and ImageDraw object to draw on + rectangle_width = image.width + rectangle_height = GUIConstants.COMPONENT_PADDING * 2 + GUIConstants.BODY_FONT_SIZE * 2 + GUIConstants.BODY_LINE_SPACING rectangle = Image.new('RGBA', (rectangle_width, rectangle_height), (0, 0, 0, 0)) img_draw = ImageDraw.Draw(rectangle) - img_draw.rounded_rectangle((0, 0, rectangle_width - 1, rectangle_height - 1), outline=(0, 0, 0, 0), - width=1, radius=10, fill=(0, 0, 0, 224)) - font = Fonts.get_font(GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, 16) - content_fill = (255, 255, 255) - # blank space(10) + font height(14) + blank space(12) + font height(14) + blank space(10) = 58 - img_draw.text((50, 10), "Brighter", fill=content_fill, font=font) - img_draw.text((50, 36), "Darker", fill=content_fill, font=font) + overlay_opacity = 224 - # draw arrows - arrow_init_x, arrow_init_y = 20, 10 - arrow_height, arrow_width = 14, 8 - # up arrow - img_draw.line( - ( - arrow_init_x, - arrow_height + arrow_init_y, - arrow_init_x + arrow_width, - arrow_init_y, - ), - fill=content_fill, + # Create a semi-transparent background for the overlay, rounded edges, w/a 1-pixel gap from the edges + img_draw.rounded_rectangle((1, 0, rectangle_width - 2, rectangle_height - 1), radius=8, fill=(0, 0, 0, overlay_opacity)) + + chevron_up_icon = Icon( + image_draw=img_draw, + canvas=rectangle, + screen_x=GUIConstants.EDGE_PADDING*2 + 1, + screen_y=GUIConstants.COMPONENT_PADDING + 4, # +4 fudge factor to account for where the chevron is drawn relative to baseline + icon_name=FontAwesomeIconConstants.CHEVRON_UP, + icon_size=GUIConstants.BODY_FONT_SIZE, ) - img_draw.line( - ( - arrow_init_x + arrow_width, - arrow_init_y, - arrow_init_x + arrow_width * 2, - arrow_init_y + arrow_height, - ), - fill=content_fill, + chevron_up_icon.render() + + chevron_down_icon = Icon( + image_draw=img_draw, + canvas=rectangle, + screen_x=chevron_up_icon.screen_x, + screen_y=chevron_up_icon.screen_y + chevron_up_icon.icon_size + GUIConstants.BODY_LINE_SPACING, + icon_name=FontAwesomeIconConstants.CHEVRON_DOWN, + icon_size=chevron_up_icon.icon_size, ) - # down arrow - img_draw.line( - ( - arrow_init_x, - 60 - (arrow_height + arrow_init_y), - arrow_init_x + arrow_width, - (60 - arrow_init_y), - ), - fill=content_fill, - ) - img_draw.line( - ( - arrow_init_x + arrow_width, - 60 - arrow_init_y, - arrow_init_x + arrow_width * 2, - 60 - (arrow_init_y + arrow_height), - ), - fill=content_fill, - ) - image.paste(rectangle, (0, 240 - rectangle_height), rectangle) + chevron_down_icon.render() + + TextArea( + image_draw=img_draw, + canvas=rectangle, + text="Brighter", + font_size=GUIConstants.BODY_FONT_SIZE, + font_name=GUIConstants.BUTTON_FONT_NAME, + background_color=(0, 0, 0, overlay_opacity), + edge_padding=0, + is_text_centered=False, + auto_line_break=False, + width=int(rectangle_width/2), + screen_x=chevron_up_icon.screen_x + GUIConstants.ICON_INLINE_FONT_SIZE, + screen_y=chevron_up_icon.screen_y - 2, # -2 to account for Icon's positioning + allow_text_overflow=False + ).render() + + TextArea( + image_draw=img_draw, + canvas=rectangle, + text="Darker", + font_size=GUIConstants.BODY_FONT_SIZE, + font_name=GUIConstants.BUTTON_FONT_NAME, + background_color=(0, 0, 0, overlay_opacity), + edge_padding=0, + is_text_centered=False, + auto_line_break=False, + width=int(rectangle_width/2), + screen_x=chevron_down_icon.screen_x + GUIConstants.ICON_INLINE_FONT_SIZE, + screen_y=chevron_down_icon.screen_y - 2, # -2 to account for Icon's positioning + allow_text_overflow=False + ).render() + + # Write our temp Image onto the main image + image.paste(rectangle, (0, image.height - rectangle_height - 1), rectangle) + def run(self): from seedsigner.models.settings import Settings settings = Settings.get_instance() - show_brightness_tips = ( - settings.get_value( - SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS - ) - == SettingsConstants.OPTION__ENABLED - ) + cur_brightness_setting = settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS) + show_brightness_tips = cur_brightness_setting == SettingsConstants.OPTION__ENABLED # Loop whether the QR is a single frame or animated; each loop might adjust # brightness setting. @@ -745,7 +757,7 @@ class QRDisplayScreen(BaseScreen): image = self.qr_encoder.next_part_image(240, 240, border=2, background_color=hex_color) # Display the brightness tips toast - duration = 10 ** 9 * 1 # 1 seconds + duration = 10 ** 9 * 1.2 # 1.2 seconds if show_brightness_tips and time.time_ns() - self.tips_start_time.cur_count < duration: self.add_brightness_tips(image) @@ -755,6 +767,7 @@ class QRDisplayScreen(BaseScreen): # Target n held frames per second before rendering next QR image time.sleep(5 / 30.0) + def __post_init__(self): from seedsigner.models.settings import Settings super().__post_init__()