def __init__(self, title='Import Wallet', seed_len=12, verify_phrase=None): super().__init__(self.ENTER_WORDS) self.title = title self.seed_len = seed_len self.verify_phrase = verify_phrase self.curr_word = 0 self.words = [] self.user_input = [] self.is_seed_valid = False self.font = FontSmall self.pagination_font = FontTiny self.highlighted_word_index = 0 self.last_word_lookup = '' self.input = KeyInputHandler(down='23456789lrudxy*', up='xy') # Initialize input and word info num_words = len(self.words) for w in range(self.seed_len): if w < num_words: self.user_input.append(word_to_keypad_numbers(self.words[w])) else: # Assume blank input self.user_input.append('') self.words.append('') # Update state based on current info self.update()
def __init__(self): self.dis = dis self.font = FontSmall self.running = True self.state = READY_TO_PLAY self.prev_time = 0 self.speed = SPEED_SLOW self.input = KeyInputHandler(down='udplrxy', up='xy') self.map = Map() self.falling_block = Block(self.dis, BLOCK_FALLING_X, BLOCK_FALLING_Y) self.next_block = Block(self.dis, BLOCK_NEXT_X, BLOCK_NEXT_Y) self.temp_block = Block(self.dis, 0, 0) self.left_btn = '' self.right_btn = ''
def __init__(self): self.dis = dis self.font = FontSmall self.running = True self.pending_direction = SLITHER_RIGHT self.prev_time = 0 self.state = READY_TO_PLAY self.score = 0 self.speed = 100 self.highscore = 0 self.snake = Snake(self.dis, 3) self.snack = Snack(self.dis) self.input = KeyInputHandler(down='udplrxy', up='xy') # ensure initial snack spawn isn't on snake while (self.snake.isCollision(self.snack.x, self.snack.y)): self.snack.newPosition()
def __init__(self, menu_items, chooser_mode=False, chosen=None, should_cont=None, space_indicators=False, title="Passport"): self.should_continue = should_cont or (lambda: True) self.replace_items(menu_items) self.space_indicators = space_indicators self.chooser_mode = chooser_mode self.chosen = chosen self.title = title self.input = KeyInputHandler(down='rlduxy', up='xy') self.shutdown_btn_enabled = False # Setup font self.font = FontSmall # number of full lines per screen self.max_lines = ( Display.HEIGHT - Display.HEADER_HEIGHT - Display.FOOTER_HEIGHT) // self.font.leading if chosen is not None: self.goto_idx(chosen)
async def snake_game(): # Game functions and settings s = Settings() s.load() game = Game() game.highscore = s.get('snake_highscore', 0) input = KeyInputHandler(down='udplrxy', up='xy') while game.running: event = await input.get_event() if event != None: # Handle key event and update game state key, event_type = event if event_type == 'down': if key == 'l': game.move(SLITHER_LEFT) elif key == 'r': game.move(SLITHER_RIGHT) elif key == 'u': game.move(SLITHER_UP) elif key == 'd': game.move(SLITHER_DOWN) elif event_type == 'up': if key == 'x': if game.state == THE_GAMES_AFOOT: game.state = TRYING_TO_QUIT elif game.state == TRYING_TO_QUIT: game.state = THE_GAMES_AFOOT elif game.state == READY_TO_PLAY or game.state == GAME_OVER: game.running = False elif key == 'y': if game.state == READY_TO_PLAY or game.state == GAME_OVER: game.start() elif game.state == TRYING_TO_QUIT: game.running = False game.update(utime.ticks_ms()) game.render() await sleep_ms(1) s.set('snake_highscore', game.highscore) s.save() return None
async def battery_mon(): isRunning = True input = KeyInputHandler(down='udplrxy', up='xy') powermon = Powermon() prev_time = 0 (n1, _) = noise.read() FILENAME = 'battery_mon_test_' + str(n1) + '.txt' while (True): try: with CardSlot() as card: # fname, nice = card.pick_filename(fname_pattern) fname = FILENAME # do actual write with open(fname, 'wb') as fd: print("writing to SD card...") fd.write('Time, Current, Voltage\n') while isRunning: event = await input.get_event() if event != None: key, event_type = event if event_type == 'up': if key == 'x': isRunning = False update(input, utime.ticks_ms(), powermon, fd, prev_time, isRunning) await sleep_ms(1) fd.close() break except Exception as e: # includes CardMissingError import sys sys.print_exception(e) # catch any error ch = await ux_show_story( 'Failed to write! Please insert formated microSD card, ' 'and press OK to try again.\n\nX to cancel.\n\n\n' + str(e)) if ch == 'x': break continue return None
async def stacksats_game(): game = Game() s = Settings() s.load() game.map.highscore = s.get('stacksats_highscore', 0) input = KeyInputHandler(down='udplrxy', up='xy') while game.running: event = await input.get_event() if input.is_pressed('d'): game.speed = SPEED_FAST else: game.speed = SPEED_SLOW - game.map.score if event != None: key, event_type = event if event_type == 'down': if key == 'l': if not game.isCollision(key): game.falling_block.moveBlock(key) elif key == 'r': if not game.isCollision(key): game.falling_block.moveBlock(key) elif key == 'u': if not game.isCollision('o'): game.falling_block.rotateBlock() elif key == 'd': pass elif key == 'x': pass elif key == 'y': pass elif event_type == 'up': if key == 'x': print("x-up") if game.state == GAME_IN_PROGRESS: game.state = TRYING_TO_QUIT elif game.state == TRYING_TO_QUIT: game.state = GAME_IN_PROGRESS elif (game.state == READY_TO_PLAY) or (game.state == GAME_OVER): game.running = False elif key == 'y': print("y-up") if (game.state == READY_TO_PLAY) or (game.state == GAME_OVER): game.start() elif game.state == GAME_IN_PROGRESS: game.state = PAUSED elif game.state == PAUSED: game.state = GAME_IN_PROGRESS elif game.state == TRYING_TO_QUIT: game.running = False game.update(utime.ticks_ms()) game.render() await sleep_ms(10) s.set('stacksats_highscore', game.map.highscore) s.save() return None
class Game: def __init__(self): self.dis = dis self.font = FontSmall self.running = True self.state = READY_TO_PLAY self.prev_time = 0 self.speed = SPEED_SLOW self.input = KeyInputHandler(down='udplrxy', up='xy') self.map = Map() self.falling_block = Block(self.dis, BLOCK_FALLING_X, BLOCK_FALLING_Y) self.next_block = Block(self.dis, BLOCK_NEXT_X, BLOCK_NEXT_Y) self.temp_block = Block(self.dis, 0, 0) self.left_btn = '' self.right_btn = '' def blockInSpawn(self): for i in range(0, 4): if ( (self.falling_block.y + ((self.falling_block.block_map[self.falling_block.type][ self.falling_block.rot][i] // 4) % 4)) >= self.map.height ): # or ((self.falling_block.y + ((self.falling_block.block_map[self.falling_block.type][self.falling_block.rot][i] // 4) % 4)) < 0): print("blockInSpawn() = True") return True print("blockInSpawn() = True") return False # direction must be either 'o', 'r', 'l', 'd' def isCollision(self, direction): self.temp_block.x = self.falling_block.x self.temp_block.y = self.falling_block.y self.temp_block.type = self.falling_block.type self.temp_block.rot = self.falling_block.rot if direction == 'o': self.temp_block.rotateBlock() else: self.temp_block.moveBlock(direction) for i in range(4): if (direction == 'r' or direction == 'o') and ( (self.temp_block.x + (self.temp_block.block_map[self.temp_block.type][ self.temp_block.rot][i] % 4)) >= self.map.width): return True elif (direction == 'l' or direction == 'o') and ( (self.temp_block.x + (self.temp_block.block_map[ self.temp_block.type][self.temp_block.rot][i] % 4)) < 0): return True elif (direction == 'd' or direction == 'o') and ( (self.temp_block.y + ((self.temp_block.block_map[self.temp_block.type][ self.temp_block.rot][i] // 4) % 4)) < 0): return True # is part of block colliding with block embedded in map.grid[row][col] elif self.map.grid[(self.temp_block.y + ( (self.temp_block.block_map[self.temp_block.type][ self.temp_block.rot][i] // 4) % 4))][( self.temp_block.x + (self.temp_block.block_map[self.temp_block.type][ self.temp_block.rot][i] % 4))] >= 0: return True return False def drawBackground(self): self.dis.draw_rect(0, Display.HEADER_HEIGHT, Display.WIDTH, Display.HEIGHT - Display.FOOTER_HEIGHT, 0, fill_color=1), # self.dis.draw_rect(MIN_X + BLOCK_SIZE, MIN_Y, 12 * BLOCK_SIZE, 20 * BLOCK_SIZE, 2) self.dis.draw_rect(MIN_X + 26 * BLOCK_SIZE // 2 + 3, MAX_Y - 33 * BLOCK_SIZE // 2, 5 * BLOCK_SIZE, 5 * BLOCK_SIZE, 2) self.dis.text(MIN_X + 27 * BLOCK_SIZE // 2 + 3, MAX_Y - 37 * BLOCK_SIZE // 2, 'Next:', invert=1) self.dis.text(MIN_X + 27 * BLOCK_SIZE // 2 + 3, 150, 'Stack', invert=1) self.dis.text(MIN_X + 27 * BLOCK_SIZE // 2 + 3, 150 + self.font.leading, 'Sats!', invert=1) def embedBlockInMap(self): print("embedBlockInMap()") # Block must be checked to be entirly within map bounds before embedding using isCollision for i in range(4): # print("checkrow[" + str(i) + "] = " + str(self.falling_block.y + ((self.falling_block.block_map[self.falling_block.type][self.falling_block.rot][i] // 4) % 4))) # print("checkcol[" + str(i) + "] = " + str(self.falling_block.x + (self.falling_block.block_map[self.falling_block.type][self.falling_block.rot][i] % 4))) self.map.grid[self.falling_block.y + ( (self.falling_block.block_map[self.falling_block.type][ self.falling_block.rot][i] // 4) % 4)][self.falling_block.x + (self.falling_block.block_map[ self.falling_block.type][self.falling_block.rot][i] % 4)] = self.falling_block.type def spawnBlock(self): self.falling_block = self.next_block self.falling_block.x = BLOCK_FALLING_X self.falling_block.y = BLOCK_FALLING_Y self.next_block = Block(self.dis, BLOCK_NEXT_X, BLOCK_NEXT_Y) def start(self): self.score = 0 for col in range(self.map.width): for row in range(self.map.height): self.map.grid[row][col] = -1 # clear game map self.state = GAME_IN_PROGRESS def render(self): self.dis.clear() self.drawBackground() self.map.draw() if self.state == READY_TO_PLAY: self.left_btn = 'BACK' self.right_btn = 'PLAY' elif self.state == GAME_IN_PROGRESS: self.left_btn = 'BACK' self.right_btn = 'PAUSE' self.falling_block.draw() self.next_block.draw() elif self.state == PAUSED: self.left_btn = 'BACK' self.right_btn = 'RESUME' self.falling_block.draw() self.next_block.draw() POPUP_WIDTH = 180 POPUP_HEIGHT = 100 POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2) POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2) SCORE_Y = Display.HALF_HEIGHT - 12 self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4) self.dis.text(None, SCORE_Y, 'Paused') elif self.state == GAME_OVER: self.left_btn = 'BACK' self.right_btn = 'PLAY' POPUP_WIDTH = 180 POPUP_HEIGHT = 100 POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2) POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2) SCORE_Y = Display.HALF_HEIGHT - 12 self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4) self.dis.text(None, SCORE_Y - self.font.leading, 'GAME OVER!') self.dis.text(None, SCORE_Y, 'Score: ' + str(self.map.score)) self.dis.text(None, SCORE_Y + self.font.leading, 'High: ' + str(self.map.highscore)) elif self.state == TRYING_TO_QUIT: self.left_btn = 'NO' self.right_btn = 'YES' POPUP_WIDTH = 180 POPUP_HEIGHT = 100 POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2) POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2) TEXT_Y = POPUP_Y + POPUP_HEIGHT // 2 - self.font.leading self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4) self.dis.text(None, TEXT_Y, 'Are you sure you') self.dis.text(None, TEXT_Y + self.font.leading, 'want to quit? ') # draw header over blocks so they dont draw over it when 'out-of-bounds' self.dis.draw_header('Score: {}'.format(self.map.score), left_text=str(self.map.highscore)) # Draw over the header's white bottom border self.dis.draw_rect(0, Display.HEADER_HEIGHT - 2, Display.WIDTH, 2, 0, fill_color=1), self.dis.draw_footer(self.left_btn, self.right_btn, self.input.is_pressed('x'), self.input.is_pressed('y')) self.dis.show() def update(self, now): if (now - self.prev_time > self.speed - (self.map.score * 5)): self.prev_time = now if self.state == GAME_IN_PROGRESS: if self.isCollision('d'): self.embedBlockInMap() if self.map.isGameOver(): self.state = GAME_OVER else: self.spawnBlock() self.map.deleteFilledRows() else: self.falling_block.moveBlock('d')
class Game: def __init__(self): self.dis = dis self.font = FontSmall self.running = True self.pending_direction = SLITHER_RIGHT self.prev_time = 0 self.state = READY_TO_PLAY self.score = 0 self.speed = 100 self.highscore = 0 self.snake = Snake(self.dis, 3) self.snack = Snack(self.dis) self.input = KeyInputHandler(down='udplrxy', up='xy') # ensure initial snack spawn isn't on snake while (self.snake.isCollision(self.snack.x, self.snack.y)): self.snack.newPosition() def isCollision(self, x1, y1, x2, y2): if x1 == x2 and y1 == y2: return True return False def move(self, direction): self.pending_direction = direction def render(self): self.dis.clear() self.dis.draw_header('Score: {}'.format(self.score), left_text=str(self.highscore)) # draw arena bounding box self.dis.draw_rect(MIN_X, MIN_Y, MAX_X, MAX_Y, 2) # rendering of snake, snacks, etc if self.state == READY_TO_PLAY: self.snake.draw() if self.state == THE_GAMES_AFOOT: self.snake.draw() self.snack.draw() # rendering of game over screen if self.state == GAME_OVER: POPUP_WIDTH = 180 POPUP_HEIGHT = 100 POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2) POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2) self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4) self.dis.text(None, Display.HALF_HEIGHT - 3 * self.font.leading // 4 - 9, 'GAME OVER!') self.dis.text(None, Display.HALF_HEIGHT - 9, 'Score: ' + str(self.score)) self.dis.text(None, Display.HALF_HEIGHT + 3 * self.font.leading // 4 - 9, 'Highscore: ' + str(self.highscore)) # rendering quit confirmation screen if self.state == TRYING_TO_QUIT: POPUP_WIDTH = 180 POPUP_HEIGHT = 200 POPUP_X = Display.HALF_WIDTH - (POPUP_WIDTH // 2) POPUP_Y = Display.HALF_HEIGHT - (POPUP_HEIGHT // 2) self.dis.draw_rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, 4) self.dis.text(None, Display.HALF_HEIGHT - 3 * self.font.leading // 4 - 9, 'Are you sure you') self.dis.text(None, Display.HALF_HEIGHT - 9, 'want to quit? ') # rendering of footer if self.state == READY_TO_PLAY or self.state == GAME_OVER: right_btn = 'START' left_btn = 'BACK' elif self.state == THE_GAMES_AFOOT: right_btn = '' left_btn = "BACK" elif self.state == TRYING_TO_QUIT: right_btn = 'YES' left_btn = 'NO' self.dis.draw_footer(left_btn, right_btn, self.input.is_pressed('x'), self.input.is_pressed('y')) self.dis.show() # tracking game logic and do collision checking. def update(self, now): if (now - self.prev_time > self.speed): self.prev_time = now if self.state == THE_GAMES_AFOOT: self.snake.direction = self.pending_direction self.snake.update() # snake is updated but not drawn yet, check for collisions.... # if snek leaves game arena, wrap to other side. if (self.snake.x[0] > MIN_X + MAX_X - BLOCK_SIZE): self.snake.x[0] = MIN_X elif (self.snake.x[0] < MIN_X): self.snake.x[0] = MIN_X + MAX_X - BLOCK_SIZE elif (self.snake.y[0] > MIN_Y + MAX_Y - BLOCK_SIZE): self.snake.y[0] = MIN_Y elif (self.snake.y[0] < MIN_Y): self.snake.y[0] = MIN_Y + MAX_Y - BLOCK_SIZE # if snek eat snac if (self.isCollision(self.snake.x[0], self.snake.y[0], self.snack.x, self.snack.y)): self.snake.addSegment() self.snack.newPosition() while (self.snake.isCollision(self.snack.x, self.snack.y)): self.snack.newPosition() self.score += 1 self.speed = 0.98 * self.speed # if snek eat snek for i in range(1, self.snake.length): if self.isCollision(self.snake.x[0], self.snake.y[0], self.snake.x[i], self.snake.y[i]): self.state = GAME_OVER if self.score > self.highscore: self.highscore = self.score def start(self): self.state = THE_GAMES_AFOOT self.score = 0 self.snake.length = 3 for i in range(0, self.snake.length): self.snake.x[i] = (MAX_X + MIN_X) // 2 + BLOCK_SIZE // 2 self.snake.y[i] = (MAX_Y + MIN_Y) // 2 + BLOCK_SIZE // 2 self.prev_time = utime.ticks_ms()
class SeedEntryUX(UXStateMachine): # States ENTER_WORDS = 1 VALIDATE_SEED = 2 INVALID_SEED = 3 VALID_SEED = 4 def __init__(self, title='Import Wallet', seed_len=12, verify_phrase=None): super().__init__(self.ENTER_WORDS) self.title = title self.seed_len = seed_len self.verify_phrase = verify_phrase self.curr_word = 0 self.words = [] self.user_input = [] self.is_seed_valid = False self.font = FontSmall self.pagination_font = FontTiny self.highlighted_word_index = 0 self.last_word_lookup = '' self.input = KeyInputHandler(down='23456789lrudxy*', up='xy') # Initialize input and word info num_words = len(self.words) for w in range(self.seed_len): if w < num_words: self.user_input.append(word_to_keypad_numbers(self.words[w])) else: # Assume blank input self.user_input.append('') self.words.append('') # Update state based on current info self.update() def render(self): dis.clear() dis.draw_header(self.title) # Draw the title y = Display.HEADER_HEIGHT + TEXTBOX_MARGIN dis.text(None, y, 'Enter Word {} of {}'.format(self.curr_word + 1, self.seed_len)) y += self.font.leading # Draw a bounding box around the input text area dis.draw_rect(TEXTBOX_MARGIN, y, Display.WIDTH - (TEXTBOX_MARGIN * 2), self.font.leading + 2, 1, fill_color=0, border_color=1) # Draw the text input dis.text_input(None, y + 4, self.user_input[self.curr_word], cursor_pos=len(self.user_input[self.curr_word])) # Draw a bounding box around the list of selectable words y += self.font.leading + TEXTBOX_MARGIN + 2 dis.draw_rect(TEXTBOX_MARGIN, y, Display.WIDTH - (TEXTBOX_MARGIN * 2), Display.HEIGHT - y - TEXTBOX_MARGIN - Display.FOOTER_HEIGHT - PAGINATION_HEIGHT, 1, fill_color=0, border_color=1) # Draw the selectable words for i in range(len(self.selectable_words)): if i == self.highlighted_word_index: # Draw inverted text with rect to indicate that this word will be selected # when user presses SELECT button. dis.draw_rect(TEXTBOX_MARGIN, y, Display.WIDTH - (TEXTBOX_MARGIN * 2), self.font.leading, 0, fill_color=1) dis.text(None, y, self.selectable_words[i], invert=1) else: dis.text(None, y, self.selectable_words[i]) y += self.font.leading - 1 # Draw the pagination more_width, more_height = dis.icon_size('more_right') y = Display.HEIGHT - Display.FOOTER_HEIGHT - more_height - 12 # Pagination constants PGN_COUNT = 7 PGN_MIDDLE = PGN_COUNT // 2 PGN_SEP = 2 PGN_W = 24 PGN_H = 22 x = (Display.WIDTH - ((PGN_W + PGN_SEP) * PGN_COUNT) + PGN_SEP) // 2 y += 1 # Calculate the framing of the pagination so that the curr_word is in the middle # whenever possible. print('PGN_MIDDLE={} PGN_COUNT={} seed_len={} curr_word={}'.format(PGN_MIDDLE, PGN_COUNT, self.seed_len, self.curr_word)) if self.curr_word <= PGN_MIDDLE: pgn_start = min(0, self.curr_word) print('case 1: pgn_start={}'.format(pgn_start)) elif self.curr_word <= self.seed_len - PGN_MIDDLE - 1: pgn_start = self.curr_word - PGN_MIDDLE print('case 2: pgn_start={}'.format(pgn_start)) else: pgn_start = self.seed_len - PGN_COUNT print('case 3: pgn_start={}'.format(pgn_start)) pgn_end = pgn_start + PGN_COUNT # Show icons only if there is something to that side to scroll to if pgn_start > 0: dis.icon(TEXTBOX_MARGIN, y + 4, 'more_left') if pgn_end < self.seed_len: dis.icon(Display.WIDTH - TEXTBOX_MARGIN - more_width, y + 4, 'more_right') for i in range(pgn_start, pgn_end): num_label = '{}'.format(i + 1) label_width = dis.width(num_label, self.pagination_font) tx = x + (PGN_W//2) - label_width // 2 ty = y + 3 invert_text = 0 if i == self.curr_word: # Draw with an inverted rectangle dis.draw_rect(x, y, PGN_W, PGN_H, 0, fill_color=1) invert_text = 1 elif len(self.words[i]) > 0: # Draw with a normal rectangle dis.draw_rect(x, y, PGN_W, PGN_H, 1, fill_color=0, border_color=1) dis.text(tx, ty, num_label, font=self.pagination_font, invert=invert_text) x += PGN_W + PGN_SEP dis.draw_footer('BACK', 'SELECT', self.input.is_pressed('x'), self.input.is_pressed('y')) dis.show() async def interact(self): # Wait for key inputs event = None while True: event = await self.input.get_event() if event != None: break if event != None: key, event_type = event if event_type == 'down': if key in '23456789': self.user_input[self.curr_word] += key c = self.user_input[self.curr_word] if len(self.user_input[self.curr_word]) > 0 else 'a' # Reset highlight to the top whenever we change the input self.highlighted_word_index = 0 # No word selected anymore since we changed the input self.words[self.curr_word] = '' elif key == 'l': self.curr_word = max(self.curr_word - 1, 0) print('curr_word={}'.format(self.curr_word)) self.highlighted_word_index = 0 elif key == 'r': if len(self.words[self.curr_word]) > 0: self.curr_word = min(self.curr_word + 1, len(self.words) - 1) elif key == 'u': self.highlighted_word_index = max(0, self.highlighted_word_index - 1) elif key == 'd': self.highlighted_word_index = min(len(self.selectable_words) - 1, self.highlighted_word_index + 1) elif key == '*': self.user_input[self.curr_word] = self.user_input[self.curr_word][0:-1] if event_type == 'up': if key == 'x': abort = ux_confirm('Are you sure you want to abort seed entry? All progress will be lost.') if abort: self.is_seed_valid = False return elif key == 'y': self.words[self.curr_word] = self.selectable_words[self.highlighted_word_index] if self.curr_word < self.seed_len - 1: self.curr_word += 1 self.highlighted_word_index = 0 else: self.goto(self.VALIDATE_SEED) def update(self): # User could have moved forward or back in the pages or up and down in the selectable_words list # or typed a number. # # Update the selectable words according to the current state print('words={} curr_word={}'.format(self.words, self.curr_word)) if self.words[self.curr_word] != None and len(self.words[self.curr_word]) > 0: print('single word case') # if a word has been selected, it will be stored here, so only show that word self.selectable_words = [self.words[self.curr_word]] # Also, set the input to the numbers for this word so the two are in sync self.user_input[self.curr_word] = str(word_to_keypad_numbers(self.words[self.curr_word])) self.highlighted_word_index = 0 else: print('normal input case') # No word has been selected yet, so lookup the matches c = self.user_input[self.curr_word] if len(self.user_input[self.curr_word]) > 0 else '2' print('c={}'.format(c)) self.selectable_words = get_words_matching_prefix(c, MAX_WORDS_TO_DISPLAY) print('selectable_words={} MAX={}'.format(self.selectable_words, MAX_WORDS_TO_DISPLAY)) self.last_word_lookup = c print('selectable_words={}'.format(self.selectable_words)) async def show(self): while True: print('show: state={}'.format(self.state)) if self.state == self.ENTER_WORDS: self.render() await self.interact() self.update() elif self.state == self.VALIDATE_SEED: if len(self.words) == self.seed_len: # Ensure that the checksum of the mnemonic words is correct mnemonic = ' '.join(self.words) print('Checking mnemonic: "{}"'.format(mnemonic)) if bip39.check(mnemonic): self.goto(self.VALID_SEED) else: self.goto(self.INVALID_SEED) elif self.state == self.VALID_SEED: # Return the words to the caller print('seed = {}'.format(self.words)) self.is_seed_valid = True return self.words elif self.state == self.INVALID_SEED: # Show a story that indicates the words are wrong - ABORT to return to previous menu or RETRY to try again result = await ux_show_story('Seed phrase checksum is invalid. One or more of your seed words is incorrect.', title='Invalid Seed', left_btn='ABORT', right_btn='RETRY', center="True", center_vertically=True) if result == 'x': self.is_seed_valid = False return None elif result == 'y': self.goto(self.ENTER_WORDS) else: while True: print('ERROR: Should never hit this else case!') from uasyncio import sleep_ms await sleep_ms(1000)
class MenuSystem: def __init__(self, menu_items, chooser_mode=False, chosen=None, should_cont=None, space_indicators=False, title="Passport"): self.should_continue = should_cont or (lambda: True) self.replace_items(menu_items) self.space_indicators = space_indicators self.chooser_mode = chooser_mode self.chosen = chosen self.title = title self.input = KeyInputHandler(down='rlduxy', up='xy') self.shutdown_btn_enabled = False # Setup font self.font = FontSmall # number of full lines per screen self.max_lines = ( Display.HEIGHT - Display.HEADER_HEIGHT - Display.FOOTER_HEIGHT) // self.font.leading if chosen is not None: self.goto_idx(chosen) # subclasses: override us # def late_draw(self, dis): pass def early_draw(self, dis): pass def update_contents(self): # something changed in system state; maybe re-construct menu contents pass def replace_items(self, menu_items, keep_position=False): # only safe to keep position if you know number of items isn't changing if not keep_position: self.cursor = 0 self.ypos = 0 self.items = [m for m in menu_items if not getattr( m, 'predicate', None) or m.predicate()] self.count = len(self.items) def show(self): from common import dis # # Redraw the menu. # dis.clear() # print('cursor=%d ypos=%d' % (self.cursor, self.ypos)) # subclass hook self.early_draw(dis) # Header wm = True if self.title == None else False dis.draw_header(self.title) menu_item_height = self.font.leading menu_item_left = 6 sel_w, sel_h = dis.icon_size('selected') if self.chooser_mode: menu_item_left += sel_w show_scrollbar = True if self.count > self.max_lines else False x, y = (menu_item_left, Display.HEADER_HEIGHT) for n in range(self.ypos + self.max_lines + 1): if n+self.ypos >= self.count: break msg = self.items[n+self.ypos].label is_sel = (self.cursor == n+self.ypos) if is_sel: wedge_w, wedge_h = dis.icon_size('wedge') dis.dis.fill_rect(0, y, Display.WIDTH, menu_item_height - 1, 1) if not self.chooser_mode: wedge_offset = 12 if show_scrollbar else 6 dis.icon(dis.WIDTH - wedge_w - wedge_offset, y + (menu_item_height - wedge_h) // 2, 'wedge', invert=1) # TODO: Font was adjusted down by 1px here dis.text(x, y + 2, msg, font=self.font, invert=1) else: # TODO: Font was adjusted down by 1px here dis.text(x, y + 2, msg, font=self.font) if msg[0] == ' ' and self.space_indicators: dis.icon(x-2, y + 11, 'space', invert=is_sel) if self.chosen is not None and (n+self.ypos) == self.chosen: dis.icon(0, y + 4, 'selected', invert=is_sel) y += menu_item_height if y > Display.HEIGHT - Display.FOOTER_HEIGHT: break # subclass hook self.late_draw(dis) if show_scrollbar: dis.scrollbar(self.ypos / self.count, self.max_lines / self.count) self.shutdown_btn_enabled = the_ux.is_top_level() left_btn = 'SHUTDOWN' if self.shutdown_btn_enabled else 'BACK' dis.draw_footer(left_btn, 'SELECT', self.input.is_pressed('x'), self.input.is_pressed('y')) dis.show() def down(self): if self.cursor < self.count-1: self.cursor += 1 if self.cursor - self.ypos > (self.max_lines-1): self.ypos += 1 def up(self): if self.cursor > 0: self.cursor -= 1 if self.cursor < self.ypos: self.ypos -= 1 def top(self): self.cursor = 0 self.ypos = 0 def goto_n(self, n): # goto N from top of (current) screen # change scroll only if needed to make it visible self.cursor = max(min(n + self.ypos, self.count-1), 0) self.ypos = max(self.cursor - n, 0) def goto_idx(self, n): # print('type of n is ', type(n)) # print('type of self.count is ', type(self.count)) # print('value of n is ', n) # print('value of self.count is ', self.count) # skip to any item, force cursor near middle of screen # NOTE: If we get a string error here, it probably means we have # passed the title to a MenuSystem() call as the second parameter instead of as a named parameter n = self.count-1 if n >= self.count else n n = 0 if n < 0 else n self.cursor = n if n < self.max_lines - 1: self.ypos = 0 else: self.ypos = n - 2 def page(self, n): # relative page dn/up if n == 1: for i in range(self.max_lines): self.down() else: for i in range(self.max_lines): self.up() # events def on_cancel(self): # override me if the_ux.pop(): # top of stack (main top-level menu) self.top() async def activate(self, idx): # Activate a specific choice in our menu. if idx is None: # "go back" or cancel or something if self.shutdown_btn_enabled: if not self.input.kcode_imminent(): await ux_shutdown() else: self.on_cancel() else: if idx >= 0 and idx < self.count: ch = self.items[idx] await ch.activate(self, idx) async def interact(self): # Only public entry point: do stuff. while self.should_continue() and the_ux.top_of_stack() == self: ch = await self.wait_choice() gc.collect() await self.activate(ch) async def wait_choice(self): # Wait until a menu choice is picked; let them move around # the menu, keep redrawing it and so on. key = None while 1: self.show() event = None while True: event = await self.input.get_event() if event != None: break key, event_type = event # print('key={} event_type={}'.format(key, event_type)) if event_type == 'down' or event_type == 'repeat': if not self.input.kcode_imminent(): if key == 'u': self.up() elif key == 'd': self.down() if event_type == 'up': if self.input.kcode_complete(): self.input.kcode_reset(); print('SHOW SECRET GAMES MENU!') from flow import GamesMenu menu_item = MenuItem('Games', GamesMenu, menu_title='Games') await menu_item.activate(self, 0) return -1 # So that the caller does nothing elif not self.input.kcode_imminent(): if key == 'y': # selected return self.cursor elif key == 'x': # abort/nothing selected/back out? return None