mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 15:08:25 +00:00
Generalizing reusable components
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
from .buttons import *
|
from .buttons import *
|
||||||
from .camera_process import *
|
from .camera_process import *
|
||||||
from .keyboard import Keyboard
|
from .keyboard import Keyboard, TextEntryDisplay
|
||||||
from .path import *
|
from .path import *
|
||||||
from .qr import *
|
from .qr import *
|
||||||
from .ST7789 import *
|
from .ST7789 import *
|
||||||
@@ -341,3 +341,36 @@ class Keyboard:
|
|||||||
raise Exception(f"""`selected_letter` "{selected_letter}" not found in keyboard""")
|
raise Exception(f"""`selected_letter` "{selected_letter}" not found in keyboard""")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TextEntryDisplay:
|
||||||
|
draw: any
|
||||||
|
rect: (int,int,int,int)
|
||||||
|
font: any
|
||||||
|
font_color: any
|
||||||
|
is_centered: bool = True
|
||||||
|
cur_text: str = " "
|
||||||
|
|
||||||
|
|
||||||
|
def render(self, cur_text=None):
|
||||||
|
""" Render the live text entry display """
|
||||||
|
if cur_text:
|
||||||
|
self.cur_text = cur_text
|
||||||
|
self.draw.rectangle(self.rect, fill="black")
|
||||||
|
|
||||||
|
cursor_block_width = 18
|
||||||
|
cursor_block_height = 33
|
||||||
|
|
||||||
|
# Draw n-1 of the selected letters
|
||||||
|
tw, th = self.font.getsize(self.cur_text[:-1])
|
||||||
|
if self.is_centered:
|
||||||
|
word_offset = int(self.rect[2] - tw - cursor_block_width)/2
|
||||||
|
else:
|
||||||
|
word_offset = 0
|
||||||
|
self.draw.text((word_offset, 2), self.cur_text[:-1], fill=self.font_color, font=self.font)
|
||||||
|
|
||||||
|
# Draw the highlighted cursor block
|
||||||
|
cursor_block_offset = word_offset + tw - 1
|
||||||
|
self.draw.rectangle((cursor_block_offset,2, cursor_block_offset + cursor_block_width, cursor_block_height), fill="#111")
|
||||||
|
self.draw.text((cursor_block_offset + 1, 2), self.cur_text[-1], fill=self.font_color, font=self.font)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import time
|
|||||||
|
|
||||||
# Internal file class dependencies
|
# Internal file class dependencies
|
||||||
from . import View
|
from . import View
|
||||||
from seedsigner.helpers import B, QR, CameraPoll, Keyboard
|
from seedsigner.helpers import B, QR, CameraPoll, Keyboard, TextEntryDisplay
|
||||||
|
|
||||||
|
|
||||||
class SeedToolsView(View):
|
class SeedToolsView(View):
|
||||||
@@ -198,49 +198,10 @@ class SeedToolsView(View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def render_previous_button(highlight=False):
|
|
||||||
# Set up the "back" arrow in the upper left
|
|
||||||
arrow = "<"
|
|
||||||
word_font = View.ROBOTOCONDENSED_BOLD_26
|
|
||||||
tw, th = word_font.getsize(arrow)
|
|
||||||
if highlight:
|
|
||||||
View.draw.rectangle((0,0, tw + 6, th + 6), fill=View.color)
|
|
||||||
View.draw.text((3, 3), arrow, fill="black", font=word_font)
|
|
||||||
else:
|
|
||||||
View.draw.rectangle((0,0, tw + 6, th + 6), fill="black")
|
|
||||||
View.draw.text((3, 3), arrow, fill=View.color, font=word_font)
|
|
||||||
|
|
||||||
return tw + 6
|
|
||||||
|
|
||||||
|
|
||||||
def render_text_entry_display(draw=View.draw):
|
|
||||||
""" Internal helper method to render the live text entry display at the top.
|
|
||||||
(has access to all vars in the parent's context)
|
|
||||||
"""
|
|
||||||
draw.rectangle((previous_button_width,0, View.canvas_width,text_entry_display_height), fill="black")
|
|
||||||
|
|
||||||
# Render the live text entry display
|
|
||||||
title = f"#{num_word}: {''.join(self.letters)}"
|
|
||||||
|
|
||||||
cursor_block_width = 18
|
|
||||||
cursor_block_height = 33
|
|
||||||
|
|
||||||
# Draw n-1 of the selected letters
|
|
||||||
word_font = View.ROBOTOCONDENSED_BOLD_26
|
|
||||||
tw, th = word_font.getsize(title[:-1])
|
|
||||||
word_offset = int(View.canvas_width - tw - cursor_block_width)/2
|
|
||||||
draw.text((word_offset, 2), title[:-1], fill=View.color, font=word_font)
|
|
||||||
|
|
||||||
# Draw the highlighted cursor block
|
|
||||||
cursor_block_offset = word_offset + tw - 1
|
|
||||||
draw.rectangle((cursor_block_offset,2, cursor_block_offset + cursor_block_width, cursor_block_height), fill="#111")
|
|
||||||
draw.text((cursor_block_offset + 1, 2), ''.join(self.letters[-1:]), fill=View.color, font=word_font)
|
|
||||||
|
|
||||||
|
|
||||||
# Clear the screen
|
# Clear the screen
|
||||||
View.draw.rectangle((0,0, View.canvas_width,View.canvas_height), fill="black")
|
View.draw.rectangle((0,0, View.canvas_width,View.canvas_height), fill="black")
|
||||||
|
|
||||||
previous_button_width = render_previous_button()
|
self.render_previous_button()
|
||||||
previous_button_is_active = False
|
previous_button_is_active = False
|
||||||
arrow_up_is_active = False
|
arrow_up_is_active = False
|
||||||
arrow_down_is_active = False
|
arrow_down_is_active = False
|
||||||
@@ -261,8 +222,18 @@ class SeedToolsView(View):
|
|||||||
rect=(0,text_entry_display_height + 1, keyboard_width,240),
|
rect=(0,text_entry_display_height + 1, keyboard_width,240),
|
||||||
auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT])
|
auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT])
|
||||||
|
|
||||||
# Initialize the current letters/current matches
|
# Render the top text entry display
|
||||||
self.letters = initial_letters
|
self.letters = initial_letters
|
||||||
|
text_entry_display = TextEntryDisplay(
|
||||||
|
View.draw,
|
||||||
|
rect=(self.previous_button_width,0, View.canvas_width,text_entry_display_height),
|
||||||
|
font=View.ROBOTOCONDENSED_BOLD_26,
|
||||||
|
font_color=View.color,
|
||||||
|
cur_text="".join(self.letters)
|
||||||
|
)
|
||||||
|
text_entry_display.render()
|
||||||
|
|
||||||
|
# Initialize the current matches
|
||||||
self.possible_words = []
|
self.possible_words = []
|
||||||
if len(self.letters) > 1:
|
if len(self.letters) > 1:
|
||||||
self.letters.append(" ") # "Lock in" the last letter as if KEY_PRESS
|
self.letters.append(" ") # "Lock in" the last letter as if KEY_PRESS
|
||||||
@@ -274,9 +245,6 @@ class SeedToolsView(View):
|
|||||||
keyboard.render_keys()
|
keyboard.render_keys()
|
||||||
render_possible_matches()
|
render_possible_matches()
|
||||||
|
|
||||||
# Render the top text entry display
|
|
||||||
render_text_entry_display()
|
|
||||||
|
|
||||||
View.DispShowImage()
|
View.DispShowImage()
|
||||||
|
|
||||||
# Start the interactive update loop
|
# Start the interactive update loop
|
||||||
@@ -295,13 +263,13 @@ class SeedToolsView(View):
|
|||||||
input = Keyboard.ENTER_BOTTOM
|
input = Keyboard.ENTER_BOTTOM
|
||||||
# Re-render it without the highlight
|
# Re-render it without the highlight
|
||||||
previous_button_is_active = False
|
previous_button_is_active = False
|
||||||
render_previous_button()
|
self.render_previous_button()
|
||||||
|
|
||||||
elif input == B.KEY_DOWN:
|
elif input == B.KEY_DOWN:
|
||||||
input = Keyboard.ENTER_TOP
|
input = Keyboard.ENTER_TOP
|
||||||
# Re-render it without the highlight
|
# Re-render it without the highlight
|
||||||
previous_button_is_active = False
|
previous_button_is_active = False
|
||||||
render_previous_button()
|
self.render_previous_button()
|
||||||
|
|
||||||
elif input in [B.KEY_RIGHT, B.KEY_LEFT]:
|
elif input in [B.KEY_RIGHT, B.KEY_LEFT]:
|
||||||
# no action in this context
|
# no action in this context
|
||||||
@@ -310,7 +278,7 @@ class SeedToolsView(View):
|
|||||||
ret_val = keyboard.update_from_input(input)
|
ret_val = keyboard.update_from_input(input)
|
||||||
|
|
||||||
if ret_val in Keyboard.EXIT_DIRECTIONS:
|
if ret_val in Keyboard.EXIT_DIRECTIONS:
|
||||||
render_previous_button(highlight=True)
|
self.render_previous_button(highlight=True)
|
||||||
previous_button_is_active = True
|
previous_button_is_active = True
|
||||||
|
|
||||||
elif ret_val in Keyboard.ADDITIONAL_KEYS:
|
elif ret_val in Keyboard.ADDITIONAL_KEYS:
|
||||||
@@ -378,7 +346,7 @@ class SeedToolsView(View):
|
|||||||
# Animate the selection storage, then return the word to the caller
|
# Animate the selection storage, then return the word to the caller
|
||||||
self.letters = list(final_selection + " ")
|
self.letters = list(final_selection + " ")
|
||||||
render_possible_matches(highlight_word=final_selection)
|
render_possible_matches(highlight_word=final_selection)
|
||||||
render_text_entry_display()
|
text_entry_display.render("".join(self.letters))
|
||||||
View.DispShowImage()
|
View.DispShowImage()
|
||||||
|
|
||||||
return final_selection
|
return final_selection
|
||||||
@@ -433,11 +401,48 @@ class SeedToolsView(View):
|
|||||||
render_possible_matches()
|
render_possible_matches()
|
||||||
|
|
||||||
# Render the text entry display and cursor block
|
# Render the text entry display and cursor block
|
||||||
render_text_entry_display()
|
text_entry_display.render("".join(self.letters))
|
||||||
|
|
||||||
View.DispShowImage()
|
View.DispShowImage()
|
||||||
|
|
||||||
|
|
||||||
|
def draw_passphrase_keyboard_entry(self):
|
||||||
|
|
||||||
|
# Clear the screen
|
||||||
|
View.draw.rectangle((0,0, View.canvas_width,View.canvas_height), fill="black")
|
||||||
|
|
||||||
|
self.render_previous_button()
|
||||||
|
previous_button_is_active = False
|
||||||
|
|
||||||
|
# Have to ensure that we don't carry any effects from a previous run
|
||||||
|
# TODO: This shouldn't be a member var
|
||||||
|
self.passphrase = " "
|
||||||
|
|
||||||
|
# Set up the keyboard params
|
||||||
|
right_key_nav_width = 50
|
||||||
|
keyboard_width = View.canvas_width - right_key_nav_width
|
||||||
|
text_entry_display_height = 39
|
||||||
|
|
||||||
|
keyboard = Keyboard(View.draw,
|
||||||
|
charset=list("1234567890").append(SeedToolsView.ALPHABET),
|
||||||
|
rows=4,
|
||||||
|
cols=10,
|
||||||
|
rect=(0,text_entry_display_height + 1, keyboard_width,240),
|
||||||
|
auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT, Keyboard.WRAP_TOP, Keyboard.WRAP_BOTTOM])
|
||||||
|
|
||||||
|
# Render the top text entry display
|
||||||
|
text_entry_display = TextEntryDisplay(
|
||||||
|
View.draw,
|
||||||
|
rect=(self.previous_button_width,0, View.canvas_width,text_entry_display_height),
|
||||||
|
font=View.ROBOTOCONDENSED_BOLD_26,
|
||||||
|
font_color=View.color,
|
||||||
|
is_centered=False,
|
||||||
|
cur_text=''.join(self.passphrase)
|
||||||
|
)
|
||||||
|
text_entry_display.render()
|
||||||
|
|
||||||
|
View.DispShowImage()
|
||||||
|
|
||||||
def gather_words_up(self):
|
def gather_words_up(self):
|
||||||
View.draw.polygon([(8 + ((len(self.letters)-1)*30), 85) , (14 + ((len(self.letters)-1)*30), 69) , (20 + ((len(self.letters)-1)*30), 85 )], outline=View.color, fill=View.color)
|
View.draw.polygon([(8 + ((len(self.letters)-1)*30), 85) , (14 + ((len(self.letters)-1)*30), 69) , (20 + ((len(self.letters)-1)*30), 85 )], outline=View.color, fill=View.color)
|
||||||
View.DispShowImage()
|
View.DispShowImage()
|
||||||
@@ -551,10 +556,10 @@ class SeedToolsView(View):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
### Display Gather PassPhrase Screen
|
### Display Gather PassPhrase Screen
|
||||||
###
|
###
|
||||||
|
|
||||||
def display_gather_passphrase_screen(self, slot_num = 0) -> str:
|
def display_gather_passphrase_screen(self, slot_num = 0) -> str:
|
||||||
self.reset()
|
self.reset()
|
||||||
self.pass_letter = "a"
|
self.pass_letter = "a"
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class View:
|
|||||||
bus = 0
|
bus = 0
|
||||||
device = 0
|
device = 0
|
||||||
disp = None
|
disp = None
|
||||||
|
previous_button_width = None
|
||||||
|
|
||||||
def __init__(self, controller) -> None:
|
def __init__(self, controller) -> None:
|
||||||
|
|
||||||
@@ -259,3 +260,24 @@ class View:
|
|||||||
def display_blank_screen(self):
|
def display_blank_screen(self):
|
||||||
View.draw.rectangle((0, 0, View.canvas_width, View.canvas_height), outline=0, fill=0)
|
View.draw.rectangle((0, 0, View.canvas_width, View.canvas_height), outline=0, fill=0)
|
||||||
View.DispShowImage()
|
View.DispShowImage()
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
### Reusable components
|
||||||
|
###
|
||||||
|
def render_previous_button(self, highlight=False):
|
||||||
|
# Set up the "back" arrow in the upper left
|
||||||
|
arrow = "<"
|
||||||
|
word_font = View.ROBOTOCONDENSED_BOLD_26
|
||||||
|
padding = 3
|
||||||
|
tw, th = word_font.getsize(arrow)
|
||||||
|
self.previous_button_width = tw + 2 * padding
|
||||||
|
if highlight:
|
||||||
|
View.draw.rectangle((0,0, tw + 2 * padding, th + 2 * padding), fill=View.color)
|
||||||
|
View.draw.text((padding, padding), arrow, fill="black", font=word_font)
|
||||||
|
else:
|
||||||
|
View.draw.rectangle((0,0, tw + 2 * padding, th + 2 * padding), fill="black")
|
||||||
|
View.draw.text((padding, padding), arrow, fill=View.color, font=word_font)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import qrcode
|
||||||
|
import sys
|
||||||
|
from embit import bip39
|
||||||
|
|
||||||
|
print(sys.argv)
|
||||||
|
seed_phrase = sys.argv[1:]
|
||||||
|
print(seed_phrase)
|
||||||
|
|
||||||
|
data = ""
|
||||||
|
for word in seed_phrase:
|
||||||
|
index = bip39.WORDLIST.index(word)
|
||||||
|
data += str("%04d" % index)
|
||||||
|
|
||||||
|
qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=5, border=3)
|
||||||
|
qr.add_data(data)
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr.make_image(fill_color="black", back_color="white").resize((240,240)).convert('RGB').show()
|
||||||
Reference in New Issue
Block a user