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()
Esempio n. 2
0
 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 = ''
Esempio n. 3
0
    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()
Esempio n. 4
0
    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)
Esempio n. 5
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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')
Esempio n. 9
0
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)
Esempio n. 11
0
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