view_function is now View_cls; first CBVs

Button text v-centering
This commit is contained in:
kdmukai
2021-09-28 21:08:46 -05:00
parent b8d33dd4ec
commit 66389017cf
8 changed files with 266 additions and 359 deletions
+68 -212
View File
@@ -9,10 +9,6 @@ from binascii import hexlify
from threading import Thread
# Internal file class dependencies
from .views import (View, MenuView, SeedToolsView, SigningToolsView,
SettingsToolsView, IOTestView, OpeningSplashView, ScreensaverView)
from .gui import Renderer
from .helpers import Buttons, B, Path
from .models import (EncodeQRDensity, QRType, Seed, SeedStorage, Settings, ConfigurableSingleton, DecodeQR, DecodeQRStatus, EncodeQR, PSBTParser)
@@ -39,6 +35,10 @@ class Controller(ConfigurableSingleton):
@classmethod
def configure_instance(cls, config=None):
from .gui import Renderer
from .helpers import Buttons
from .views import ScreensaverView
super().configure_instance(config)
# Instantiate the one and only Controller instance
@@ -64,11 +64,11 @@ class Controller(ConfigurableSingleton):
controller.renderer = Renderer.get_instance()
# Views
controller.menu_view = MenuView()
controller.seed_tools_view = SeedToolsView()
controller.io_test_view = IOTestView()
controller.signing_tools_view = SigningToolsView(controller.storage)
controller.settings_tools_view = SettingsToolsView()
# controller.menu_view = MenuView()
# controller.seed_tools_view = SeedToolsView()
# controller.io_test_view = IOTestView()
# controller.signing_tools_view = SigningToolsView(controller.storage)
# controller.settings_tools_view = SettingsToolsView()
controller.screensaver = ScreensaverView(controller.buttons)
controller.back_stack = []
@@ -90,241 +90,97 @@ class Controller(ConfigurableSingleton):
def start(self) -> None:
from .views import OpeningSplashView, MainMenuView
opening_splash = OpeningSplashView()
# opening_splash.start()
""" Functions can be stored as variables in python!
""" Class references can be stored as variables in python!
This loop receives a view function to execute and stores it in the
`view_function` var along with any input arguments in the `function_args`
dict.
This loop receives a View class to execute and stores it in the `View_cls`
var along with any input arguments in the `run_args` dict.
The `view_function` is executed w/its `function_args`. Each view returns either
a new view function to execute next or None.
The `View_cls` is instantiated and run with `run_args` passed in. It returns
either a new View class to execute next or None.
Example:
def my_view(some_arg, other_arg):
print(other_arg)
class MyView(View)
def run(self, some_arg, other_arg):
print(other_arg)
def other_view():
return (my_view, {"some_arg": 1, "other_arg": "hello"})
class OtherView():
def run(self):
return (MyView, {"some_arg": 1, "other_arg": "hello"})
When `other_view` is run, we capture its return values:
When `OtherView` is instantiated and run, we capture its return values:
(view_function, function_args) = other_view()
(View_cls, run_args) = OtherView().run()
And then we can execute that view function:
And then we can instantiate and run that View class:
view_function(**function_args)
View_cls().run(**run_args)
"""
try:
view_function = None
function_args = {}
View_cls = None
run_args = {}
while True:
if not view_function:
if not View_cls or View_cls == MainMenuView:
# None is a special case; render the Home screen
view_function = self.menu_view.display_main_menu
function_args = {}
View_cls = MainMenuView
run_args = {}
# Home always wipes the back_stack
self.back_stack = []
print(f"Executing {view_function}")
print(f"Executing {View_cls.__name__}")
# Each view returns the next view function to display plus any
# function_args in an argument dict.
(view_function, function_args) = view_function(**function_args)
# Instantiate the `View_cls` and run() it with the `run_args` dict
result = View_cls().run(**run_args)
print(f"view_function: {view_function}")
if view_function == self.pop_back_stack:
# "Back" arrow was clicked; load the previous view
(view_function, function_args) = self.pop_back_stack()
if type(result) is tuple:
View_cls = result[0]
run_args = result[1]
else:
self.back_stack.append((view_function, function_args))
View_cls = result
run_args = {}
print(f"View_cls: {View_cls.__name__ if View_cls else 'None'}")
if View_cls == self.pop_back_stack:
# "Back" arrow was clicked; load the previous view
(View_cls, run_args) = self.pop_back_stack()
else:
self.back_stack.append((View_cls, run_args))
finally:
# Clear the screen when exiting
self.renderer.display_blank_screen()
# if self.DEBUG:
# # Let Exceptions halt execution
# try:
# self.show_main_menu()
# finally:
# # Clear the screen when exiting
# self.renderer.display_blank_screen()
from seedsigner.gui.templates import BaseScreen, ButtonListScreen, FontTesterScreen, BottomButtonScreen
from seedsigner.gui.components import Fonts
# tests = [
# ("OpenSans-SemiBold", 16),
# ("OpenSans-SemiBold", 18),
# ("OpenSans-SemiBold", 20),
# ]
# screens = []
# screens.append(
# BottomButtonScreen(
# title="Supersampling: off",
# button_data=[{"text":"Proceed"}],
# is_button_text_centered=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# body_text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
# is_body_text_centered=True,
# body_font_name="OpenSans-Regular",
# body_font_size=17,
# body_font_color="white",
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# supersampling_factor=1
# )
# )
# screens.append(
# BottomButtonScreen(
# title="Supersampling: 2",
# button_data=[{"text":"Proceed"}],
# is_button_text_centered=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# body_text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
# is_body_text_centered=True,
# body_font_name="OpenSans-Regular",
# body_font_size=17,
# body_font_color="white",
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# supersampling_factor=2
# )
# )
# screens.append(
# BottomButtonScreen(
# title="Regular 16px w/SS",
# button_data=[{"text":"Proceed"}],
# is_button_text_centered=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# body_text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
# is_body_text_centered=True,
# body_font_name="OpenSans-Regular",
# body_font_size=16,
# body_font_color="white",
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# supersampling_factor=2
# )
# )
# screens.append(
# BottomButtonScreen(
# title="SemiBold 16px, no SS",
# button_data=[{"text":"Proceed"}],
# is_button_text_centered=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# body_text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
# is_body_text_centered=True,
# body_font_name="OpenSans-SemiBold",
# body_font_size=16,
# body_font_color="white",
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# supersampling_factor=1
# )
# )
# screens.append(
# BottomButtonScreen(
# title="SemiBold 17px, no SS",
# button_data=[{"text":"Proceed"}],
# is_button_text_centered=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# body_text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
# is_body_text_centered=True,
# body_font_name="OpenSans-SemiBold",
# body_font_size=17,
# body_font_color="white",
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# supersampling_factor=1
# )
# )
# screens.append(
# ButtonListScreen(
# title="Color: Orange",
# button_data=[
# {"text": "cfd0883d"},
# {"text": "72f9a6bf"},
# ],
# is_button_text_centered=False,
# is_bottom_list=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# button_selected_color="orange"
# )
# )
# screens.append(
# ButtonListScreen(
# title="Color: #f7941d",
# button_data=[
# {"text": "cfd0883d"},
# {"text": "72f9a6bf"},
# ],
# is_button_text_centered=False,
# is_bottom_list=False,
# title_font=Fonts.get_font("OpenSans-SemiBold", 18),
# button_font=Fonts.get_font("OpenSans-SemiBold", 18),
# button_selected_color="#f7941d"
# )
# )
# index = 0
# while True:
# # test = tests[index]
# # screen = FontTesterScreen(
# # title="In-Memory Seeds",
# # button_data=[
# # {"text": "cfd0883d"},
# # {"text": "72f9a6bf"},
# # {"text": f"{test[0].split('.')[0]} {test[1]}"},
# # ],
# # is_button_text_centered=False,
# # is_bottom_list=False,
# # )
# screens[index].render()
# else:
# # Handle Unexpected crashes by restarting up to 3 times
# crash_cnt = 0
# while True:
# if index == 0:
# input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_UP, B.KEY_DOWN])
# elif index < len(screens) - 1:
# input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT, B.KEY_UP, B.KEY_DOWN])
# else:
# input = self.buttons.wait_for([B.KEY_LEFT, B.KEY_UP, B.KEY_DOWN])
# try:
# self.show_main_menu()
# except Exception as error:
# if crash_cnt >= 3:
# break
# else:
# print('Caught this error: ' + repr(error)) # debug
# self.renderer.draw_modal(["Crashed ..."], "", "restarting")
# time.sleep(5)
# if input == B.KEY_RIGHT:
# index += 1
# break
# elif input == B.KEY_LEFT:
# index -= 1
# break
# else:
# screens[index].update_from_input(input)
# crash_cnt += 1
# time.sleep(180)
if self.DEBUG:
# Let Exceptions halt execution
try:
self.show_main_menu()
finally:
# Clear the screen when exiting
self.renderer.display_blank_screen()
else:
# Handle Unexpected crashes by restarting up to 3 times
crash_cnt = 0
while True:
try:
self.show_main_menu()
except Exception as error:
if crash_cnt >= 3:
break
else:
print('Caught this error: ' + repr(error)) # debug
self.renderer.draw_modal(["Crashed ..."], "", "restarting")
time.sleep(5)
crash_cnt += 1
self.renderer.draw_modal(["Crashed ..."], "", "requires hard restart")
# self.renderer.draw_modal(["Crashed ..."], "", "requires hard restart")
def start_screensaver(self):
+16 -7
View File
@@ -63,8 +63,8 @@ class TextArea(BaseComponent):
height: int
background_color: str = "black"
font_name: str = "OpenSans-Regular"
font_size: int = 16
font_color: str = "orange"
font_size: int = 17
font_color: str = "white"
is_text_centered: bool = True
supersampling_factor: int = None
@@ -182,14 +182,23 @@ class Button(BaseComponent):
def __post_init__(self):
super().__post_init__()
if not self.font:
self.font = Fonts.get_font("OpenSans-SemiBold", 24)
self.font = Fonts.get_font("OpenSans-SemiBold", 18)
# see: https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html#text-anchors
offset_x, offset_y = self.font.getoffset(self.text)
(box_left, box_top, box_right, box_bottom) = self.font.getbbox(self.text, anchor='lt')
ascent, descent = self.font.getmetrics()
# print(f"----- {self.text} -----")
# print(f"offset_x, offset_y: ({offset_x}, {offset_y})")
# print(f"(box_left, box_top, box_right, box_bottom): ({box_left}, {box_top}, {box_right}, {box_bottom})")
# print(f"ascent, descent: ({ascent}, {descent})")
self.text_width, self.text_height = self.font.getsize(self.text)
if self.is_text_centered:
self.text_x = self.screen_x + int((self.width - self.text_width) / 2)
self.text_x = self.screen_x + int((self.width - (box_right - offset_x)) / 2) - offset_x
else:
self.text_x = self.screen_x + COMPONENT_PADDING
self.text_y = self.screen_y + int((self.height - self.text_height) / 2)
self.text_y = self.screen_y + int((self.height - (ascent - offset_y)) / 2) - offset_y
def render(self):
@@ -211,7 +220,7 @@ class TopNav(BaseComponent):
width: int
height: int
background_color: str = "black"
font: ImageFont = Fonts.get_font("OpenSans-SemiBold", 18)
font: ImageFont = Fonts.get_font("OpenSans-SemiBold", 19)
font_color: str = "white"
@@ -5,7 +5,6 @@ from .components import (EDGE_PADDING, COMPONENT_PADDING, Button, TopNav,
from dataclasses import dataclass
from PIL import ImageFont
from seedsigner.helpers import B, Buttons
from seedsigner.views import View
@@ -21,18 +20,23 @@ class BaseScreen:
self.renderer = Renderer.get_instance()
self.hw_inputs = Buttons.get_instance()
self.canvas_width = self.renderer.canvas_width
self.canvas_height = self.renderer.canvas_height
def render(self, show_image: bool = True):
def display(self):
self._render()
self.renderer.show_image()
return self._run()
def _render(self):
# Clear the whole canvas
self.renderer.draw.rectangle((0, 0, self.renderer.canvas_width, self.renderer.canvas_height), fill=0)
if show_image:
# Will be false when called by child classes; they'll want to keep writing
# their own UI content before calling show_image() themselves.
self.renderer.show_image()
self.renderer.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), fill=0)
def run(self):
def _run(self):
"""
Screen can run on its own until it returns a final exit input from the user.
@@ -64,18 +68,56 @@ class BaseTopNavScreen(BaseScreen):
super().__post_init__()
self.top_nav = TopNav(
text=self.title,
width=self.renderer.canvas_width,
height=(2 * EDGE_PADDING) + int(self.renderer.canvas_width * 2.0 / 15.0), # 32px on a 240x240 screen
width=self.canvas_width,
height=(2 * EDGE_PADDING) + int(self.canvas_width * 2.0 / 15.0), # 32px on a 240x240 screen
)
def render(self, show_image: bool = True):
super().render(show_image=False) # Child always tells parent to wait
def _render(self):
super()._render()
self.top_nav.render()
if show_image:
self.renderer.show_image()
def _run(self):
raise Exception("Must implement in a child class")
@dataclass
class TextTopNavScreen(BaseTopNavScreen):
title: str # Not necessary to include this since it's in parent; just here for clarity
text: str
is_text_centered: bool = True
text_font_name: str = "OpenSans-Regular"
text_font_size: int = 17
supersampling_factor: int = None
def __post_init__(self):
super().__post_init__()
if self.text_font_size < 18 and not self.supersampling_factor or self.supersampling_factor == 1:
self.supersampling_factor = 2
self.text_area = TextArea(
text=self.text,
screen_x=0,
screen_y=self.top_nav.height,
width=self.canvas_width,
height=self.canvas_height - self.top_nav.height,
font_name=self.text_font_name,
font_size=self.text_font_size,
is_text_centered=self.is_text_centered,
supersampling_factor=self.supersampling_factor
)
def _render(self):
super()._render()
self.top_nav.render()
self.text_area.render()
def _run(self):
# No interactive UI to handle
pass
@@ -92,16 +134,20 @@ class ButtonListScreen(BaseTopNavScreen):
def __post_init__(self):
super().__post_init__()
button_height = int(self.renderer.canvas_height * 3.0 / 20.0) # 36px on a 240x240 screen
button_height = int(self.canvas_height * 3.0 / 20.0) # 36px on a 240x240 screen
if len(self.button_labels) == 1:
button_list_height = button_height
else:
button_list_height = (len(self.button_labels) * button_height) + (COMPONENT_PADDING * (len(self.button_labels) - 1))
if self.is_bottom_list:
button_list_y = self.renderer.canvas_height - (button_list_height + EDGE_PADDING)
button_list_y = self.canvas_height - (button_list_height + EDGE_PADDING)
else:
button_list_y = self.top_nav.height + int((self.renderer.canvas_height - self.top_nav.height - button_list_height) / 2)
button_list_y = self.top_nav.height + int((self.canvas_height - self.top_nav.height - button_list_height) / 2)
if button_list_y < self.top_nav.height:
# The button list is too long; force it to run off the bottom of the screen.
button_list_y = self.top_nav.height + COMPONENT_PADDING
self.buttons = []
for i, button_label in enumerate(self.button_labels):
@@ -109,7 +155,7 @@ class ButtonListScreen(BaseTopNavScreen):
text=button_label,
screen_x=EDGE_PADDING,
screen_y=button_list_y + i * (button_height + COMPONENT_PADDING),
width=self.renderer.canvas_width - (2 * EDGE_PADDING),
width=self.canvas_width - (2 * EDGE_PADDING),
height=button_height,
is_text_centered=self.is_button_text_centered,
font=self.button_font,
@@ -121,16 +167,13 @@ class ButtonListScreen(BaseTopNavScreen):
self.selected_button = 0
def render(self, show_image: bool = True):
super().render(show_image=False) # Child always tells parent to wait
def _render(self):
super()._render()
for button in self.buttons:
button.render()
self.renderer.show_image()
def run(self):
def _run(self):
while True:
user_input = self.hw_inputs.wait_for([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS], check_release=True, release_keys=[B.KEY_PRESS])
if user_input == B.KEY_UP:
@@ -189,7 +232,7 @@ class BottomButtonScreen(ButtonListScreen):
text=body_text,
screen_x=0,
screen_y=self.top_nav.height,
width=self.renderer.canvas_width,
width=self.canvas_width,
height=self.buttons[0].screen_y - self.top_nav.height,
font_name=body_font_name,
font_size=body_font_size,
@@ -199,8 +242,8 @@ class BottomButtonScreen(ButtonListScreen):
)
def render(self):
self.renderer.draw.rectangle((0, 0, self.renderer.canvas_width, self.renderer.canvas_height), fill=0)
def _render(self):
self.renderer.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), fill=0)
self.top_nav.render()
self.body_textscreen.render()
for button in self.buttons:
+3 -1
View File
@@ -1,3 +1,6 @@
# Must import crucial base class first!
from .singleton import Singleton, ConfigurableSingleton
from .seed import *
from .qr_type import *
from .encode_qr_density import *
@@ -6,4 +9,3 @@ from .encode_qr import *
from .psbt_parser import *
from .seed_storage import *
from .settings import *
from .singleton import *
+1
View File
@@ -1,4 +1,5 @@
from .view import * # base class has to be first
from .io_test_view import *
from .menu_view import *
from .seed_tools_view import *
+41 -78
View File
@@ -2,7 +2,7 @@
from . import View
from seedsigner.gui.components import Fonts
from seedsigner.gui.templates import ButtonListScreen
from seedsigner.gui.screens import ButtonListScreen
from seedsigner.helpers import B, Path, Buttons
from seedsigner.models import SeedStorage, Settings, Seed
@@ -11,6 +11,46 @@ import time
import re
class SeedToolsMenuView(View):
def run(self, **kwargs):
from . import MainMenuView
title = "Store a Seed"
if self.controller.storage.num_of_saved_seeds() > 0:
if self.controller.storage.num_of_saved_seeds() < 3:
title = "View/Store Seeds"
else:
title = "View Seeds"
selected_menu_num = ButtonListScreen(
title=title,
button_labels=["... [ Return to Main ]",
"Temp Seed Storage",
"Seed Passphrase",
"Export xPub",
"Calc Last Word",
"New Seed w/Dice",
"New Seed w/Image"]
).display()
if selected_menu_num == 0:
print("returning MainMenuView")
return (MainMenuView, {})
elif selected_menu_num == 1:
return Path.SAVE_SEED
elif selected_menu_num == 2:
return Path.PASSPHRASE_SEED
elif selected_menu_num == 3:
return Path.GEN_XPUB
elif selected_menu_num == 4:
return Path.GEN_LAST_WORD
elif selected_menu_num == 5:
return Path.DICE_GEN_SEED
elif selected_menu_num == 6:
return Path.IMAGE_GEN_SEED
class MenuView(View):
def __init__(self) -> None:
@@ -20,83 +60,6 @@ class MenuView(View):
self.selected_menu_num = 1
###
### Main Navigation
###
### Main Menu
def display_main_menu(self, sub_menu = None) -> int:
button_labels = [
"Seed Tools",
"Scan QR",
"Settings",
"Power Off"
]
# if sub_menu == Path.SEED_TOOLS_SUB_MENU:
# return self.display_seed_tools_menu()
# elif sub_menu == Path.SIGNING_TOOLS_SUB_MENU:
# return Path.SIGN_TRANSACTION
# elif sub_menu == Path.SETTINGS_SUB_MENU:
# return self.display_settings_menu()
menu_screen = ButtonListScreen(title="SeedSigner", button_labels=button_labels, selected_button=0)
menu_screen.render()
selected_menu_num = menu_screen.run()
print(f"selected_menu_num: {selected_menu_num}")
if selected_menu_num == 0:
print("returning selected_menu_num")
return (self.display_seed_tools_menu, {})
elif selected_menu_num == 1:
ret_val = Path.SIGN_TRANSACTION
elif selected_menu_num == 2:
return (self.display_settings_menu, {})
elif selected_menu_num == 3:
ret_val = Path.POWER_OFF
# if ret_val != Path.MAIN_MENU: # When no main menu, return to controller
# return ret_val
### Seed Tools Menu
def display_seed_tools_menu(self) -> int:
seed_storage_line = "Store a Seed (temp)"
if self.controller.storage.num_of_saved_seeds() > 0:
if self.controller.storage.num_of_saved_seeds() < 3:
seed_storage_line = "View/Store Seeds (temp)"
else:
seed_storage_line = "View Seeds (temp)"
lines = ["... [ Return to Main ]", "Temp Seed Storage", "Seed Passphrase", "xPub from Seed", "Calculate Last Word", "Generate Seed with Dice", "Generate Seed with Image"]
self.draw_menu(lines, 1)
input = 0
# Wait for Button Input (specifically menu selection/press)
while True:
input = self.buttons.wait_for([B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS], check_release=True, release_keys=[B.KEY_PRESS])
if input == B.KEY_UP:
self.menu_up()
elif input == B.KEY_DOWN:
self.menu_down()
elif input == B.KEY_PRESS:
if self.selected_menu_num == 1:
return Path.MAIN_MENU
elif self.selected_menu_num == 2:
return Path.SAVE_SEED
elif self.selected_menu_num == 3:
return Path.PASSPHRASE_SEED
elif self.selected_menu_num == 4:
return Path.GEN_XPUB
elif self.selected_menu_num == 5:
return Path.GEN_LAST_WORD
elif self.selected_menu_num == 6:
return Path.DICE_GEN_SEED
elif self.selected_menu_num == 7:
return Path.IMAGE_GEN_SEED
### Signing Tools Menu
def display_signing_tools_menu(self) -> None:
lines = ["... [ Return to Main ]", "Generate xPub", "Sign a Transaction"]
+1 -1
View File
@@ -4,7 +4,7 @@ import time
from PIL import Image, ImageDraw
from . import View
from .view import View
from seedsigner.helpers import B
from seedsigner.gui.components import Fonts
+64 -31
View File
@@ -1,19 +1,33 @@
# External Dependencies
from PIL import Image, ImageDraw, ImageFont
import os
import pathlib
import time
from multiprocessing import Queue
from seedsigner.helpers import B
from seedsigner.gui.components import Fonts
from seedsigner.gui.screens import ButtonListScreen, TextTopNavScreen
"""
Views contain the biz logic to handle discrete tasks, exactly analogous to a Flask
request/response function or a Django View. Each page/screen displayed to the user
should be implemented in its own View.
In a web context, the View would prepare data for the html/css/js presentation
templates. We have to implement our own presentation layer (implemented as `Screen`
objects). For the sake of code cleanliness and separation of concerns, the View code
should not know anything about pixel-level rendering.
Sequences that require multiple pages/screens can be should be implemented as a
series of separate Views. Exceptions can be made for complex interactive sequences,
but in general, if your View is instantiating multiple Screens, you're probably
putting too much functionality in that View.
As with http requests, Views can receive input vars to inform their behavior. Views
can also prepare the next set of vars to set up the next View that should be
displayed (akin to `return redirect(url, param1=x, param2=y))`).
"""
class View:
# TODO: Obviated by seedsigner.gui.components.TopNav
previous_button_width: int = None
def __init__(self) -> None:
# Import here to avoid circular imports
from seedsigner.controller import Controller
@@ -29,37 +43,17 @@ class View:
self.buttons = self.controller.buttons
self.color = self.controller.color
self.queue = Queue()
def run(self, **kwargs):
raise Exception("Must implement in the child class")
###
### Power Off Screen
###
def display_power_off_screen(self):
self.renderer.draw.rectangle((0, 0, self.canvas_width, self.canvas_height), outline=0, fill=0)
line1 = "Powering Down..."
line2 = "Please wait about"
line3 = "30 seconds before"
line4 = "disconnecting power."
tw, th = self.renderer.draw.textsize(line1, font=Fonts.get_font("Assistant-Medium", 22))
self.renderer.draw.text(((240-tw)/2, 45), line1, fill=self.color, font=Fonts.get_font("Assistant-Medium", 22))
tw, th = self.renderer.draw.textsize(line2, font=Fonts.get_font("Assistant-Medium", 20))
self.renderer.draw.text(((240-tw)/2, 100), line2, fill=self.color, font=Fonts.get_font("Assistant-Medium", 20))
tw, th = self.renderer.draw.textsize(line3, font=Fonts.get_font("Assistant-Medium", 20))
self.renderer.draw.text(((240-tw)/2, 130), line3, fill=self.color, font=Fonts.get_font("Assistant-Medium", 20))
tw, th = self.renderer.draw.textsize(line4, font=Fonts.get_font("Assistant-Medium", 20))
self.renderer.draw.text(((240-tw)/2, 160), line4, fill=self.color, font=Fonts.get_font("Assistant-Medium", 20))
self.renderer.show_image()
###
### Reusable components
###
# TODO: Obviated by seedsigner.gui.components.TopNav
def render_previous_button(self, highlight=False):
# Set up the "back" arrow in the upper left
arrow = "<"
@@ -80,3 +74,42 @@ class View:
#########################################################################################
#
# Root level Views don't have a sub-module home so they live at the top level here.
#
#########################################################################################
class MainMenuView(View):
def run(self):
from .menu_view import SeedToolsMenuView
selected_menu_num = ButtonListScreen(
title="SeedSigner",
button_labels=["Seed Tools",
"Scan QR",
"Settings",
"Power Off"]
).display()
if selected_menu_num == 0:
return SeedToolsMenuView
elif selected_menu_num == 1:
ret_val = Path.SIGN_TRANSACTION
elif selected_menu_num == 2:
return self.display_settings_menu
elif selected_menu_num == 3:
return PowerOffView
class PowerOffView(View):
def run(self):
from subprocess import call
TextTopNavScreen(
title="Powering Down",
text="Please wait about 30 seconds before disconnecting power.",
text_font_size=22
).display()
# call("sudo shutdown --poweroff now", shell=True)
time.sleep(10)